diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7a0c94e..320cff0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -61,6 +61,7 @@ dependencies { implementation(libs.androidx.window) implementation(libs.androidx.work.runtime.ktx) implementation(libs.places) + implementation(libs.androidx.appcompat) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/com/mensinator/app/Calculations.kt b/app/src/main/java/com/mensinator/app/Calculations.kt index eca1732..a0409c2 100644 --- a/app/src/main/java/com/mensinator/app/Calculations.kt +++ b/app/src/main/java/com/mensinator/app/Calculations.kt @@ -7,12 +7,25 @@ import kotlin.math.roundToInt // TODO REMOVE noPeriods+noOvulations USE GLOBAL VARIABLES INSTEAD +/** + * The `Calculations` class provides methods to calculate menstrual cycle related data + * such as next period date, average cycle length, and average luteal length. + * + * @param context The context used to access the database. + */ + class Calculations (context: Context){ private val dbHelper = PeriodDatabaseHelper(context) private val periodHistory = dbHelper.getSettingByKey("period_history")?.value?.toIntOrNull() ?: 5 private val ovulationHistory = dbHelper.getSettingByKey("ovulation_history")?.value?.toIntOrNull() ?: 5 private val lutealCalculation = dbHelper.getSettingByKey("luteal_period_calculation")?.value?.toIntOrNull() ?: 0 + + /** + * Calculates the next expected period date. + * + * @return The next expected period date as a string. + */ fun calculateNextPeriod(): String { val expectedPeriodDate: String @@ -21,6 +34,7 @@ class Calculations (context: Context){ Log.d("TAG", "Advanced calculation") expectedPeriodDate = advancedNextPeriod() } else{ + // Basic calculation using latest period start dates Log.d("TAG", "Basic calculation") //do basic calculation here //Use X latest periodstartdates (will return list of X+1) @@ -41,6 +55,12 @@ class Calculations (context: Context){ return expectedPeriodDate } + + /** + * Advanced calculation for the next expected period date using ovulation data. + * + * @return The next expected period date as a string. + */ private fun advancedNextPeriod(): String { // Get the list of the latest ovulation dates val ovulationDates = dbHelper.getLatestXOvulationsWithPeriod(ovulationHistory) @@ -91,6 +111,11 @@ class Calculations (context: Context){ } //Returns average no of days from first last period to ovulation for passed X periods + /** + * Calculates the average number of days from the first day of the last period to ovulation. + * + * @return The average follicular phase length as a string. + */ fun averageFollicalGrowthInDays(): String { val ovulationDates = dbHelper.getXLatestOvulationsDates(ovulationHistory) if (ovulationDates.isEmpty()) { @@ -113,6 +138,11 @@ class Calculations (context: Context){ // Takes the X (periodHistory) latest period start dates and calculates the average cycle length // X comes from app_settings in the database + /** + * Calculates the average cycle length using the latest period start dates. + * X comes from app_settings in the database + * @return The average cycle length as a double. + */ fun averageCycleLength(): Double{ val periodDates = dbHelper.getLatestXPeriodStart(periodHistory) @@ -137,6 +167,12 @@ class Calculations (context: Context){ return cycleLengthAverage } + + /** + * Calculates the average period length using the latest period start dates. + * + * @return The average period length as a double. + */ fun averagePeriodLength(): Double { val daysInPeriod = mutableListOf() @@ -162,6 +198,11 @@ class Calculations (context: Context){ } } + /** + * Calculates the average luteal phase length using the latest ovulation dates. + * + * @return The average luteal phase length as a double. + */ // Takes the X (ovulationHistory) latest ovulation dates and calculates the average luteal length // X comes from app_settings in the database fun averageLutealLength(): Double{ @@ -192,6 +233,13 @@ class Calculations (context: Context){ return lutealLengths.average() } + + /** + * Calculates the luteal phase length for a specific cycle. + * + * @param date The ovulation date. + * @return The luteal phase length as an integer. + */ // This function is used to get the luteal length of a cycle. // Date input should only be ovulation date // To be used with setting for calculating periods according to ovulation diff --git a/app/src/main/java/com/mensinator/app/CalendarScreen.kt b/app/src/main/java/com/mensinator/app/CalendarScreen.kt index fd33f3d..b851d26 100644 --- a/app/src/main/java/com/mensinator/app/CalendarScreen.kt +++ b/app/src/main/java/com/mensinator/app/CalendarScreen.kt @@ -23,6 +23,8 @@ import java.time.format.TextStyle import java.time.temporal.ChronoUnit import java.util.Locale import android.content.Context +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.os.LocaleListCompat import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.work.OneTimeWorkRequestBuilder @@ -32,6 +34,7 @@ import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit +import androidx.compose.ui.res.stringResource /* This file creates the calendar. A sort of "main screen". @@ -194,8 +197,18 @@ fun CalendarScreen() { updateCalculations() } - val daysOfWeek = listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") + //List of Days of Week + val daysOfWeek = listOf( + stringResource(id = R.string.mon), + stringResource(id = R.string.tue), + stringResource(id = R.string.wed), + stringResource(id = R.string.thu), + stringResource(id = R.string.fri), + stringResource(id = R.string.sat), + stringResource(id = R.string.sun) + ) + //UI Implementation Column( modifier = Modifier .fillMaxSize() @@ -208,10 +221,18 @@ fun CalendarScreen() { Button(onClick = { currentMonth.value = currentMonth.value.minusMonths(1) }) { - Text(text = "Previous") + Text(stringResource(id = R.string.previous)) + } + // Get the current locale from AppCompatDelegate + val localeList = AppCompatDelegate.getApplicationLocales() + val currentLocale = if (localeList.isEmpty) { + Locale.ENGLISH // Fallback to English if locale list is empty + } else { + localeList[0] // Retrieve the first locale from the list } + Text( - text = "${currentMonth.value.month.getDisplayName(TextStyle.FULL, Locale.ENGLISH)} ${currentMonth.value.year}", + text = "${currentMonth.value.month.getDisplayName(TextStyle.FULL, currentLocale)} ${currentMonth.value.year}", fontSize = 20.sp, textAlign = TextAlign.Center, modifier = Modifier.weight(1f) @@ -219,7 +240,7 @@ fun CalendarScreen() { Button(onClick = { currentMonth.value = currentMonth.value.plusMonths(1) }) { - Text(text = "Next") + Text(text = stringResource(id = R.string.next)) } } Spacer(modifier = Modifier.height(8.dp)) @@ -412,10 +433,12 @@ fun CalendarScreen() { Spacer(modifier = Modifier.height(16.dp)) + val emptyClick = stringResource(id = R.string.statistics_title) + val successSaved = stringResource(id = R.string.successfully_saved_alert) Button( onClick = { if (selectedDates.value.isEmpty()) { - Toast.makeText(context, "No dates to save or remove", Toast.LENGTH_SHORT).show() + Toast.makeText(context, emptyClick, Toast.LENGTH_SHORT).show() } else { for (date in selectedDates.value) { if (date in periodDates.value) { @@ -450,7 +473,7 @@ fun CalendarScreen() { { sendNotification(context, reminderDays, LocalDate.parse(nextPeriodStartCalculated)) } - Toast.makeText(context, "Changes saved successfully", Toast.LENGTH_SHORT).show() + Toast.makeText(context, successSaved, Toast.LENGTH_SHORT).show() } }, enabled = isPeriodsButtonEnabled, // Set the state of the Periods button @@ -458,16 +481,17 @@ fun CalendarScreen() { .fillMaxWidth() .padding(top = 16.dp) ) { - Text(text = "Period") + Text(text = stringResource(id = R.string.period_button)) } + val noDataSelected = stringResource(id = R.string.no_data_selected) Button( onClick = { //selectedDate = selectedDates.value.firstOrNull() // Select the first date as the date for the SymptomsDialog if (selectedDates.value.isNotEmpty()) { showSymptomsDialog = true } else { - Toast.makeText(context, "No dates selected", Toast.LENGTH_SHORT).show() + Toast.makeText(context,noDataSelected , Toast.LENGTH_SHORT).show() } }, enabled = isSymptomsButtonEnabled, // Set the state of the Symptoms button @@ -475,24 +499,28 @@ fun CalendarScreen() { .fillMaxWidth() .padding(top = 16.dp) ) { - Text(text = "Symptoms") + Text(text = stringResource(id = R.string.symptoms_button)) } //ovulation starts here + val onlyDayAlert = stringResource(id = R.string.only_day_alert) + val successSavedOvulation = stringResource(id = R.string.success_saved_ovulation) + val noDateSelectedOvulation = stringResource(id = R.string.no_date_selected_ovulation) + Button( onClick = { if(selectedDates.value.size > 1){ - Toast.makeText(context, "Only one day can be ovulation!", Toast.LENGTH_SHORT).show() + Toast.makeText(context, onlyDayAlert, Toast.LENGTH_SHORT).show() } else if(selectedDates.value.size == 1){ val date = selectedDates.value.first() dbHelper.updateOvulationDate(date) refreshOvulationDates() - Toast.makeText(context, "Ovulation saved successfully", Toast.LENGTH_SHORT).show() + Toast.makeText(context, successSavedOvulation, Toast.LENGTH_SHORT).show() } else{ - Toast.makeText(context, "No date selected for ovulation", Toast.LENGTH_SHORT).show() + Toast.makeText(context, noDateSelectedOvulation, Toast.LENGTH_SHORT).show() } selectedDates.value = emptySet() updateCalculations() @@ -507,7 +535,7 @@ fun CalendarScreen() { .fillMaxWidth() .padding(top = 16.dp) ) { - Text(text = "Ovulation") + Text(text = stringResource(id = R.string.ovulation_button)) } // Show the SymptomsDialog diff --git a/app/src/main/java/com/mensinator/app/DatabaseUtils.kt b/app/src/main/java/com/mensinator/app/DatabaseUtils.kt index 7169425..9217cb8 100644 --- a/app/src/main/java/com/mensinator/app/DatabaseUtils.kt +++ b/app/src/main/java/com/mensinator/app/DatabaseUtils.kt @@ -26,7 +26,7 @@ object DatabaseUtils { ) """) - val predefinedSymptoms = listOf("Heavy Flow", "Medium Flow", "Light Flow") + val predefinedSymptoms = listOf("Heavy_Flow", "Medium_Flow", "Light_Flow") predefinedSymptoms.forEach { symptom -> db.execSQL( """ diff --git a/app/src/main/java/com/mensinator/app/ExportImportDialog.kt b/app/src/main/java/com/mensinator/app/ExportImportDialog.kt index af88ad4..7ffb080 100644 --- a/app/src/main/java/com/mensinator/app/ExportImportDialog.kt +++ b/app/src/main/java/com/mensinator/app/ExportImportDialog.kt @@ -18,6 +18,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import java.io.File import java.io.FileOutputStream +import androidx.compose.ui.res.stringResource + + @Composable fun ExportImportDialog( @@ -30,11 +33,13 @@ fun ExportImportDialog( val exportPath = remember { mutableStateOf(exportImport.getDocumentsExportFilePath()) } val importPath = remember { mutableStateOf("") } + val impSuccess = stringResource(id = R.string.import_success_toast) + val impFailure = stringResource(id = R.string.import_failure_toast) val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { val inputStream = context.contentResolver.openInputStream(it) - val file = File(ExportImport().getDefaultImportFilePath(context)) + val file = File(exportImport.getDefaultImportFilePath(context)) val outputStream = FileOutputStream(file) try { inputStream?.copyTo(outputStream) @@ -42,10 +47,10 @@ fun ExportImportDialog( // Call the import function onImportClick(importPath.value) // Show success toast - Toast.makeText(context, "File was imported successfully", Toast.LENGTH_SHORT).show() + Toast.makeText(context, impSuccess, Toast.LENGTH_SHORT).show() } catch (e: Exception) { // Show error toast - Toast.makeText(context, "Failed to import file", Toast.LENGTH_SHORT).show() + Toast.makeText(context, impFailure, Toast.LENGTH_SHORT).show() Log.d("ExportImportDialog", "Failed to import file: ${e.message}, ${e.stackTraceToString()}") } finally { // Clean up @@ -57,32 +62,33 @@ fun ExportImportDialog( } } + val expSuccess = stringResource(id = R.string.export_success_toast, exportPath.value) AlertDialog( onDismissRequest = onDismissRequest, confirmButton = { Button(onClick = { onExportClick(exportPath.value) - Toast.makeText(context, "Exported to ${exportPath.value}", Toast.LENGTH_SHORT).show() + Toast.makeText(context, expSuccess, Toast.LENGTH_SHORT).show() onDismissRequest() }) { - Text("Export") + Text(stringResource(id = R.string.export_button)) } }, dismissButton = { Button(onClick = { importLauncher.launch("application/json") }) { - Text("Import") + Text(stringResource(id = R.string.import_button)) } }, title = { - Text("Export/Import Data") + Text(stringResource(id = R.string.export_import_title)) }, text = { Column { - Text("Export Path: ${exportPath.value}") + Text(stringResource(id = R.string.export_path_label, exportPath.value)) Spacer(modifier = Modifier.height(8.dp)) - Text("Import Path: ${importPath.value}") + Text(stringResource(id = R.string.import_path_label, importPath.value)) } } ) diff --git a/app/src/main/java/com/mensinator/app/FAQDialog.kt b/app/src/main/java/com/mensinator/app/FAQDialog.kt index 0954838..0199dfd 100644 --- a/app/src/main/java/com/mensinator/app/FAQDialog.kt +++ b/app/src/main/java/com/mensinator/app/FAQDialog.kt @@ -15,6 +15,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.res.stringResource + + + + @Composable fun FAQDialog( @@ -25,7 +30,7 @@ fun FAQDialog( AlertDialog( onDismissRequest = onDismissRequest, // Call the dismiss callback when dialog is dismissed title = { - Text(text = "Frequently Asked Questions") + Text(text = stringResource(id = R.string.faq_title)) }, text = { Column( @@ -36,7 +41,7 @@ fun FAQDialog( ) { // User Manual Header Text( - text = "User Manual", + text = stringResource(id = R.string.user_manual_header), style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), color = MaterialTheme.colorScheme.primary ) @@ -44,79 +49,67 @@ fun FAQDialog( // How to Use Text( - text = "How to Use:", + text = stringResource(id = R.string.how_to_use), style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold) ) Text( - text = "• Select Dates: Tap on a date to select or deselect it.", + text = stringResource(id = R.string.select_dates), style = MaterialTheme.typography.bodyMedium ) Text( - text = "• Add or Remove Dates for Period: Click the 'Add or Remove Dates' button after selecting dates.", + text = stringResource(id = R.string.add_or_remove_dates), style = MaterialTheme.typography.bodyMedium ) Text( - text = "• Symptoms: Click the 'Symptoms' button to view or add symptoms for the selected date.", + text = stringResource(id = R.string.symptoms), style = MaterialTheme.typography.bodyMedium ) Text( - text = "• Ovulation: Select a single date and click the ovulation button. To remove, select date and press button again.", + text = stringResource(id = R.string.ovulation), style = MaterialTheme.typography.bodyMedium ) Text( - text = "• Statistics: Click the 'Burger menu' and select statistics.", + text = stringResource(id = R.string.statistics), style = MaterialTheme.typography.bodyMedium ) Text( - text = "• Import/Export: Click the 'Import/Export' button to import or export your data. This is a MUST when moving the data to a new device!", + text = stringResource(id = R.string.import_export), style = MaterialTheme.typography.bodyMedium ) Spacer(modifier = Modifier.height(16.dp)) // Space between sections Text( - text = "• Calculations: " + - "\nPeriods are calculated according to average cycle length and period length." + - "\nPeriods _can_ be calculated using average luteal phase. The calculations will then look at the last 5 cycles" + - " to make an average lutal phase and use that to calculate the period. This is activated in the settings" + - "\nOvulation is calculated according to average ovulation cycle length.", + text = stringResource(id = R.string.calculations), style = MaterialTheme.typography.bodyMedium ) Text( - text = "\nFeatures Coming Soon:", + text = stringResource(id = R.string.features_coming_soon), style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold) ) Text( - text = "• Reminder for upcoming period to the toolbar. (This is shown in settings but not implemented )\n" + - "• New user interface (when I'm in the mood).\n" + - "• Other small UI improvements", + text = stringResource(id = R.string.upcoming_features), style = MaterialTheme.typography.bodyMedium ) Spacer(modifier = Modifier.height(16.dp)) // Space between sections // Our Story Header Text( - text = "Our Story", + text = stringResource(id = R.string.our_story_header), style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), color = MaterialTheme.colorScheme.primary ) Text( - text = "We are two women who were tired of selling our data to big corporations. " + - "Our app stores all your data locally on your device and we have **no access to it whatsoever**.\n" + - "We value your privacy and do not save or share any personal information." + - "Since launching this app we have grown and now have our own Discord-server with people who are passionate about this app and want to help us out!"+ - "\n\nJoin us on Discord here: https://discord.gg/tHA2k3bFRN", + text = stringResource(id = R.string.our_story), style = MaterialTheme.typography.bodyMedium ) Spacer(modifier = Modifier.height(16.dp)) // Space between sections // Disclaimer Header Text( - text = "Disclaimer:", + text = stringResource(id = R.string.disclaimer_header), style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold) ) Text( - text = "• This is a hobby-project and not intended for medical use. We are simply doing this for our own needs." + - "But we do welcome ideas and requests, join us on Discord or send us an email:\n" + - "mensinator.app@gmail.com", + text = stringResource(id = R.string.disclaimer), style = MaterialTheme.typography.bodyMedium ) } @@ -125,7 +118,7 @@ fun FAQDialog( Button( onClick = onDismissRequest // Call the dismiss callback when the button is clicked ) { - Text("Close") + Text(stringResource(id = R.string.close_button)) } }, modifier = Modifier diff --git a/app/src/main/java/com/mensinator/app/MainActivity.kt b/app/src/main/java/com/mensinator/app/MainActivity.kt index 147ea11..6bf15bc 100644 --- a/app/src/main/java/com/mensinator/app/MainActivity.kt +++ b/app/src/main/java/com/mensinator/app/MainActivity.kt @@ -3,9 +3,10 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity import com.mensinator.app.ui.theme.MensinatorTheme -class MainActivity : ComponentActivity() { +class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() diff --git a/app/src/main/java/com/mensinator/app/ManageSymptomDialog.kt b/app/src/main/java/com/mensinator/app/ManageSymptomDialog.kt index 43cb663..a66971f 100644 --- a/app/src/main/java/com/mensinator/app/ManageSymptomDialog.kt +++ b/app/src/main/java/com/mensinator/app/ManageSymptomDialog.kt @@ -25,9 +25,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp + + + +//Maps Database keys to res/strings.xml for multilanguage support @Composable fun ManageSymptom( onDismissRequest: () -> Unit, @@ -49,13 +54,16 @@ fun ManageSymptom( "Black" to Color.Black, "White" to Color.White, "Dark Gray" to Color.DarkGray, - "Light Gray" to Color.LightGray + "Light Gray" to Color.LightGray, + "Heavy_Flow" to R.string.heavy, + "Medium_Flow" to R.string.medium, + "Light_Flow" to R.string.light, ) AlertDialog( onDismissRequest = onDismissRequest, title = { - Text(text = "Manage Symptoms", fontSize = 20.sp) + Text(text = stringResource(id = R.string.manage_symptoms), fontSize = 20.sp) }, text = { Column( @@ -67,12 +75,15 @@ fun ManageSymptom( savedSymptoms.forEach { symptom -> var expanded by remember { mutableStateOf(false) } var selectedColorName by remember { mutableStateOf(symptom.color) } + val colorKey = ResourceMapper.getStringResourceId(selectedColorName) + val resKey = ResourceMapper.getStringResourceId(symptom.name) + Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { - Text(text = symptom.name, fontSize = 16.sp, textAlign = androidx.compose.ui.text.style.TextAlign.Left) + Text(text = colorKey?.let { stringResource(id = it) } ?:"Not found", fontSize = 16.sp, textAlign = androidx.compose.ui.text.style.TextAlign.Left) Switch( checked = symptom.active == 1, onCheckedChange = { checked -> @@ -90,7 +101,7 @@ fun ManageSymptom( .clickable { expanded = true } ) { Text( - text = selectedColorName, + text = colorKey?.let { stringResource(id = it) } ?:"Not found", textAlign = androidx.compose.ui.text.style.TextAlign.Left ) } @@ -100,8 +111,10 @@ fun ManageSymptom( onDismissRequest = { expanded = false } ) { predefinedColors.forEach { (colorName) -> + val colorKey = ResourceMapper.getStringResourceId(colorName) + DropdownMenuItem( - { Text(text = colorName, textAlign = androidx.compose.ui.text.style.TextAlign.Left) }, + { Text(text = colorKey?.let { stringResource(id = it) } ?:"Not found", textAlign = androidx.compose.ui.text.style.TextAlign.Left) }, onClick = { selectedColorName = colorName expanded = false @@ -126,12 +139,12 @@ fun ManageSymptom( onSave() onDismissRequest() // Close the dialog }) { - Text("Save") + Text(stringResource(id = R.string.save)) } }, dismissButton = { Button(onClick = onDismissRequest) { - Text("Close") + Text(stringResource(id = R.string.close)) } }, // To adjust the width of the AlertDialog based on screen size diff --git a/app/src/main/java/com/mensinator/app/SettingsDialog.kt b/app/src/main/java/com/mensinator/app/SettingsDialog.kt index f0bd18f..23ba129 100644 --- a/app/src/main/java/com/mensinator/app/SettingsDialog.kt +++ b/app/src/main/java/com/mensinator/app/SettingsDialog.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.provider.Settings import android.util.Log +import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -16,8 +17,54 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.graphics.Color import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.core.app.NotificationManagerCompat +import androidx.core.os.LocaleListCompat + +//Maps Database keys to res/strings.xml for multilanguage support +object ResourceMapper { + //maps res strings xml file to db keys + private val resourceMap = mapOf( + //settings + "app_settings" to R.string.app_settings, + "period_color" to R.string.period_color, + "selection_color" to R.string.selection_color, + "period_selection_color" to R.string.period_selection_color, + "expected_period_color" to R.string.expected_period_color, + "ovulation_color" to R.string.ovulation_color, + "expected_ovulation_color" to R.string.expected_ovulation_color, + "reminders" to R.string.reminders, + "reminder_days" to R.string.days_before_reminder, + "other_settings" to R.string.other_settings, + "luteal_period_calculation" to R.string.luteal_phase_calculation, + "period_history" to R.string.period_history, + "ovulation_history" to R.string.ovulation_history, + "lang" to R.string.language, + "cycle_numbers_show" to R.string.cycle_numbers_show, + "close" to R.string.close, + "save" to R.string.save, + "Heavy_Flow" to R.string.heavy, + "Medium_Flow" to R.string.medium, + "Light_Flow" to R.string.light, + // colors + "Red" to R.string.color_red, + "Green" to R.string.color_green, + "Blue" to R.string.color_blue, + "Yellow" to R.string.color_yellow, + "Cyan" to R.string.color_cyan, + "Magenta" to R.string.color_magenta, + "Black" to R.string.color_black, + "White" to R.string.color_white, + "DarkGray" to R.string.color_darkgray, + "Grey" to R.string.color_grey, + + ) + + fun getStringResourceId(key: String): Int? { + return resourceMap[key] + } +} @Composable fun SettingsDialog( @@ -52,7 +99,28 @@ fun SettingsDialog( // Here is available languages of the app // When more languages have been translated, add them here val predefinedLang = mapOf( - "English" to "EN" + "English" to "en", + "Swedish" to "sv", + "Tamil" to "ta", + "Hindi" to "hi" + /*"Chinese" to "zh", + "Spanish" to "es", + "Bengali" to "bn", + "Portuguese" to "pt", + "Russian" to "ru", + "Japanese" to "ja", + "Western Punjabi" to "pa", + "Marathi" to "mr", + "Telugu" to "te", + "Wu Chinese" to "wuu", + "Turkish" to "tr", + "Korean" to "ko", + "French" to "fr", + "German" to "de", + "Vietnamese" to "vi", + "Urdu" to "ur", + "Cantonese" to "yue"*/ + ) val predefinedReminders = (0..10).map { it.toString() } @@ -62,7 +130,7 @@ fun SettingsDialog( AlertDialog( onDismissRequest = onDismissRequest, title = { - Text(text = "App Settings", fontSize = 20.sp) + Text(text = stringResource(id = R.string.app_settings), fontSize = 20.sp) }, text = { Column( @@ -75,7 +143,7 @@ fun SettingsDialog( when (groupId) { 1 -> { Text( - text = "Colors", + text = stringResource(id = R.string.colors), fontSize = 18.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(vertical = 8.dp) @@ -84,6 +152,8 @@ fun SettingsDialog( settingsInGroup.forEach { setting -> var expanded by remember { mutableStateOf(false) } var selectedColorName by remember { mutableStateOf(setting.value) } + val settingsKey = ResourceMapper.getStringResourceId(setting.key) + val selectedColorKey = ResourceMapper.getStringResourceId(selectedColorName) Row( modifier = Modifier @@ -93,21 +163,24 @@ fun SettingsDialog( verticalAlignment = Alignment.CenterVertically ) { Text( - text = setting.label, + text = settingsKey?.let { stringResource(id = it) } ?:"Not found", fontSize = 14.sp, - modifier = Modifier.weight(1f).alignByBaseline() + modifier = Modifier + .weight(1f) + .alignByBaseline() ) Box(modifier = Modifier.alignByBaseline()) { TextButton(onClick = { expanded = !expanded }) { - Text(selectedColorName) + Text(selectedColorKey?.let { stringResource(id = it) } ?:"Not found") } DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false } ) { predefinedColors.forEach { (name, _) -> + val Colors = ResourceMapper.getStringResourceId(name) DropdownMenuItem( - text = { Text(name) }, + text = { Text(Colors?.let { stringResource(id = it) } ?:"Not found",) }, onClick = { selectedColorName = name savedSettings = savedSettings.map { @@ -124,7 +197,7 @@ fun SettingsDialog( } 2 -> { Text( - text = "Reminders", + text = stringResource(id = R.string.reminders), fontSize = 18.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(vertical = 8.dp) @@ -133,6 +206,7 @@ fun SettingsDialog( settingsInGroup.forEach { setting -> var expanded by remember { mutableStateOf(false) } var selectedReminder by remember { mutableStateOf(setting.value) } + val settingsKey = ResourceMapper.getStringResourceId(setting.key) Row( modifier = Modifier @@ -142,9 +216,11 @@ fun SettingsDialog( verticalAlignment = Alignment.CenterVertically ) { Text( - text = setting.label, + text = settingsKey?.let { stringResource(id = it) } ?: setting.label, fontSize = 14.sp, - modifier = Modifier.weight(1f).alignByBaseline() + modifier = Modifier + .weight(1f) + .alignByBaseline() ) Box(modifier = Modifier.alignByBaseline()) { TextButton(onClick = { expanded = !expanded }) { @@ -173,13 +249,15 @@ fun SettingsDialog( } else -> { Text( - text = "Other settings", + text = stringResource(id = R.string.other_settings), fontSize = 18.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(vertical = 8.dp) ) settingsInGroup.forEach { setting -> var isChecked by remember { mutableStateOf(setting.value == "1") } + val settingsKey = ResourceMapper.getStringResourceId(setting.key) + Row( modifier = Modifier @@ -189,7 +267,7 @@ fun SettingsDialog( verticalAlignment = Alignment.CenterVertically ) { Text( - text = setting.label, + text = settingsKey?.let { stringResource(id = it) } ?: setting.label, fontSize = 14.sp, modifier = Modifier.weight(1f) ) @@ -216,7 +294,7 @@ fun SettingsDialog( ) { predefinedReminders.forEach { reminder -> DropdownMenuItem( - text = { Text(reminder) }, + text = {reminder}, onClick = { selectedReminder = reminder savedSettings = savedSettings.map { @@ -252,6 +330,11 @@ fun SettingsDialog( if (it.key == setting.key) it.copy(value = code) else it } expanded = false + AppCompatDelegate.setApplicationLocales( + LocaleListCompat.forLanguageTags( + predefinedLang[name] + ) + ) } ) } @@ -284,12 +367,12 @@ fun SettingsDialog( } onDismissRequest() }) { - Text("Save") + Text(stringResource(id = R.string.save)) } }, dismissButton = { Button(onClick = onDismissRequest) { - Text("Close") + Text(stringResource(id = R.string.close)) } }, modifier = Modifier.width(LocalConfiguration.current.screenWidthDp.dp * 0.9f) diff --git a/app/src/main/java/com/mensinator/app/StatisticsDialog.kt b/app/src/main/java/com/mensinator/app/StatisticsDialog.kt index 2f8fa59..eac408e 100644 --- a/app/src/main/java/com/mensinator/app/StatisticsDialog.kt +++ b/app/src/main/java/com/mensinator/app/StatisticsDialog.kt @@ -15,6 +15,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.time.LocalDate +import androidx.compose.ui.res.stringResource + @@ -39,7 +41,7 @@ fun StatisticsDialog( AlertDialog( onDismissRequest = onDismissRequest, title = { - Text(text = "Statistics") + Text(text = stringResource(id = R.string.statistics_title)) }, text = { Column( @@ -49,50 +51,49 @@ fun StatisticsDialog( .verticalScroll(scrollState) // Add vertical scrolling capability ) { Text( - text = "Number of periods tracked: $periodCount", + text = stringResource(id = R.string.period_count, periodCount), fontSize = 16.sp ) Text( - text = "Average cycle length: ${"%.1f".format(averageCycleLength)} days", + text = stringResource(id = R.string.average_cycle_length, averageCycleLength), fontSize = 16.sp ) Text( - text = "Average period length: ${"%.1f".format(averagePeriodLength)} days", + text = stringResource(id = R.string.average_period_length, averagePeriodLength), fontSize = 16.sp ) Text( text = if (nextPeriodStart < LocalDate.now().toString()) { - "Next assumed period: $nextPeriodStart \n\nAssumed period date has passed!" + stringResource(id = R.string.next_period_start_past, nextPeriodStart) } else { - "Next assumed period: $nextPeriodStart" + stringResource(id = R.string.next_period_start_future, nextPeriodStart) }, fontSize = 16.sp ) // Ovulation statistics Text( - text = "\nNumber of ovulations tracked: $ovulationCount", + text = stringResource(id = R.string.ovulation_count, ovulationCount), fontSize = 16.sp ) Text( - text = "Average ovulation day: $follicleGrowthDays", + text = stringResource(id = R.string.average_ovulation_day, follicleGrowthDays), fontSize = 16.sp ) Text( text = nextPredictedOvulation?.let { - "Next predicted ovulation date: $it" - } ?: "Not enough data to predict next ovulation", + stringResource(id = R.string.next_predicted_ovulation, it) + } ?: stringResource(id = R.string.next_predicted_ovulation_default), fontSize = 16.sp ) - Text( - text = "Average luteal phase length: ${"%.1f".format(avgLutealLength)} days", + text = stringResource(id = R.string.average_luteal_length, avgLutealLength), fontSize = 16.sp ) } }, confirmButton = { Button(onClick = onDismissRequest) { - Text("Close") + Text(stringResource(id = R.string.close_button)) } }, modifier = Modifier diff --git a/app/src/main/java/com/mensinator/app/SymptomDialogs.kt b/app/src/main/java/com/mensinator/app/SymptomDialogs.kt index 87947ba..2c4f0a7 100644 --- a/app/src/main/java/com/mensinator/app/SymptomDialogs.kt +++ b/app/src/main/java/com/mensinator/app/SymptomDialogs.kt @@ -9,19 +9,23 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.time.LocalDate +import androidx.compose.ui.res.stringResource + + + + @Composable fun SymptomsDialog( - date: LocalDate, // last selected date from the calendar + date: LocalDate, symptoms: List, dbHelper: PeriodDatabaseHelper, onSave: (List) -> Unit, onCancel: () -> Unit, - onCreateNewSymptom: () -> Unit // Added callback for the new button + onCreateNewSymptom: () -> Unit ) { var selectedSymptoms by remember { mutableStateOf(emptySet()) } - // LaunchedEffect to fetch symptom IDs and initialize selectedSymptoms LaunchedEffect(date) { val symptomIdsForDate = dbHelper.getSymptomsFromDate(date).toSet() selectedSymptoms = symptoms.filter { it.id in symptomIdsForDate }.toSet() @@ -30,11 +34,12 @@ fun SymptomsDialog( AlertDialog( onDismissRequest = { onCancel() }, title = { - Text(text = "Symptoms for $date") // Display the date in the dialog title + Text(text = stringResource(id = R.string.symptoms_dialog_title, date)) }, text = { Column { symptoms.forEach { symptom -> + val symptomKey = ResourceMapper.getStringResourceId(symptom.name) Row( modifier = Modifier .fillMaxWidth() @@ -53,18 +58,17 @@ fun SymptomsDialog( onCheckedChange = null ) Spacer(modifier = Modifier.width(8.dp)) - Text(text = symptom.name, fontSize = 16.sp) + Text(text = symptomKey?.let { stringResource(id = it) } ?: "Not Found", fontSize = 16.sp) } } Spacer(modifier = Modifier.height(16.dp)) Button( onClick = { - onCreateNewSymptom() // Call the new button click handler + onCreateNewSymptom() }, - modifier = Modifier - .fillMaxWidth() + modifier = Modifier.fillMaxWidth() ) { - Text("Create New Symptom") + Text(text = stringResource(id = R.string.create_new_symptom_button)) } } }, @@ -75,7 +79,7 @@ fun SymptomsDialog( }, modifier = Modifier.fillMaxWidth() ) { - Text("Save symptoms") + Text(text = stringResource(id = R.string.save_symptoms_button)) } }, dismissButton = { @@ -85,12 +89,13 @@ fun SymptomsDialog( }, modifier = Modifier.fillMaxWidth() ) { - Text("Cancel") + Text(text = stringResource(id = R.string.cancel_button)) } } ) } + @Composable fun CreateNewSymptomDialog( newSymptom: String, @@ -98,15 +103,16 @@ fun CreateNewSymptomDialog( onCancel: () -> Unit ) { var symptomName by remember { mutableStateOf(newSymptom) } + val symptomKey = ResourceMapper.getStringResourceId(symptomName) AlertDialog( onDismissRequest = { onCancel() }, title = { - Text(text = "Create New Symptom") + Text(text = stringResource(id = R.string.create_new_symptom_dialog_title)) }, text = { TextField( - value = symptomName, + value = symptomKey?.let { stringResource(id = it) } ?: "Not Found", onValueChange = { symptomName = it }, label = { Text("Symptom Name") } ) @@ -117,7 +123,7 @@ fun CreateNewSymptomDialog( onSave(symptomName) } ) { - Text("Save") + Text(stringResource(id = R.string.save_button)) } }, dismissButton = { @@ -126,7 +132,7 @@ fun CreateNewSymptomDialog( onCancel() } ) { - Text("Cancel") + Text(stringResource(id = R.string.cancel_button)) } } ) diff --git a/app/src/main/java/com/mensinator/app/ui/theme/Color.kt b/app/src/main/java/com/mensinator/app/ui/theme/Color.kt index 0f01cac..bbe485a 100644 --- a/app/src/main/java/com/mensinator/app/ui/theme/Color.kt +++ b/app/src/main/java/com/mensinator/app/ui/theme/Color.kt @@ -2,10 +2,16 @@ package com.mensinator.app.ui.theme import androidx.compose.ui.graphics.Color +/** + * Shades of purple and pink colors at 80% opacity. + */ val Purple80 = Color(0xFFD0BCFF) val PurpleGrey80 = Color(0xFFCCC2DC) val Pink80 = Color(0xFFEFB8C8) -val Purple40 = Color(0xFF6650a4) -val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +/** + * Shades of purple and pink colors at 40% opacity. + */ +val Purple40 = Color(0xFF6650A4) +val PurpleGrey40 = Color(0xFF625B71) +val Pink40 = Color(0xFF7D5260) diff --git a/app/src/main/java/com/mensinator/app/ui/theme/Theme.kt b/app/src/main/java/com/mensinator/app/ui/theme/Theme.kt index c8e9edd..ade03c5 100644 --- a/app/src/main/java/com/mensinator/app/ui/theme/Theme.kt +++ b/app/src/main/java/com/mensinator/app/ui/theme/Theme.kt @@ -11,6 +11,8 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext + +// Define your custom colors for dark and light themes private val DarkColorScheme = darkColorScheme( primary = Purple80, secondary = PurpleGrey80, @@ -33,6 +35,7 @@ private val LightColorScheme = lightColorScheme( */ ) +// Composable function to apply the custom theme @Composable fun MensinatorTheme( darkTheme: Boolean = isSystemInDarkTheme(), diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml new file mode 100644 index 0000000..28af6a7 --- /dev/null +++ b/app/src/main/res/values-hi/strings.xml @@ -0,0 +1,128 @@ + + + + + Mensinator + + + Previous + Next + Period + Symptoms + ovulation + + No dates to save or remove + Changes saved successfully + No dates selected + Only one day can be ovulation! + Ovulation saved successfully + No date selected for ovulation + + + + App Settings + Colors + Period Color + Selection Color + Period Selection Color + Expected Period Color + Ovulation Color + Expected Ovulation Color + Reminders + Days Before Reminder + Other settings + Luteal Phase Calculation + Period history + Ovulation history + Language + Show cycle numbers + Close + Save + + Red + Green + Blue + Yellow + Cyan + Magenta + Black + White + DarkGray + Grey + + + Statistics + Number of periods tracked: %1$d + Average cycle length: %1$.1f days + Average period length: %1$.1f days + Next assumed period: %1$s \n\nAssumed period date has passed! + Next assumed period: %1$s + Number of ovulation tracked: %1$d + Average ovulation day: %1$s + Next predicted ovulation date: %1$s + Not enough data to predict next ovulation + Average luteal phase length: %1$.1f days + + + Symptoms for %1$s + Create New Symptom + Save symptoms + Cancel + Heavy Flow + Medium Flow + Light Flow + + + Create New Symptom + Symptom Name + Save + + Manage Symptoms + + + + Mon + Tue + Wed + Thu + Fri + Sat + Sun + + + Export/Import Data + Export + Import + Exported to %1$s + File was imported successfully + Failed to import file + Export Path: %1$s + Import Path: %1$s + + + + + Frequently Asked Questions + + + User Manual + How to Use: + • Select Dates: Tap on a date to select or deselect it. + • Add or Remove Dates for Period: Click the \'Add or Remove Dates\' button after selecting dates. + • Symptoms: Click the \'Symptoms\' button to view or add symptoms for the selected date. + • Ovulation: Select a single date and click the ovulation button. To remove, select the date and press the button again. + • Statistics: Click the \'Burger menu\' and select statistics. + • Import/Export: Click the \'Import/Export\' button to import or export your data. This is a MUST when moving the data to a new device! + • Calculations: \nPeriods are calculated according to average cycle length and period length.\nPeriods can be calculated using average luteal phase. The calculations will then look at the last 5 cycles to make an average luteal phase and use that to calculate the period. This is activated in the settings.\nOvulation is calculated according to average ovulation cycle length. + Features Coming Soon: + • Reminder for upcoming period in the toolbar. (This is shown in settings but not implemented)\n• New user interface (when I\'m in the mood).\n• Other small UI improvements + Our Story + We are two women who were tired of selling our data to big corporations. Our app stores all your data locally on your device and we have <b>no access to it whatsoever</b>.\nWe value your privacy and do not save or share any personal information. Since launching this app we have grown and now have our own Discord server with people who are passionate about this app and want to help us out!\n\nJoin us on Discord here: https://discord.gg/tHA2k3bFRN + Disclaimer: + • This is a hobby project and is not intended for medical use. We are simply doing this for our own needs. But we do welcome ideas and requests. Join us on Discord or send us an email:\nmensinator.app@gmail.com + + + Close + + + \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml new file mode 100644 index 0000000..79c9fac --- /dev/null +++ b/app/src/main/res/values-sv/strings.xml @@ -0,0 +1,124 @@ + + + Mensinator + + + Previous + Next + Period + Symptoms + ovulation + + No dates to save or remove + Changes saved successfully + No dates selected + Only one day can be ovulation! + Ovulation saved successfully + No date selected for ovulation + + + + App Settings + Colors + Period Color + Selection Color + Period Selection Color + Expected Period Color + Ovulation Color + Expected Ovulation Color + Reminders + Days Before Reminder + Other settings + Luteal Phase Calculation + Period history + Ovulation history + Language + Show cycle numbers + Close + Save + + Red + Green + Blue + Yellow + Cyan + Magenta + Black + White + DarkGray + Grey + + + Statistics + Number of periods tracked: %1$d + Average cycle length: %1$.1f days + Average period length: %1$.1f days + Next assumed period: %1$s \n\nAssumed period date has passed! + Next assumed period: %1$s + Number of ovulation tracked: %1$d + Average ovulation day: %1$s + Next predicted ovulation date: %1$s + Not enough data to predict next ovulation + Average luteal phase length: %1$.1f days + + + Symptoms for %1$s + Create New Symptom + Save symptoms + Cancel + Heavy Flow + Medium Flow + Light Flow + + + Create New Symptom + Symptom Name + Save + + Manage Symptoms + + + + Mon + Tue + Wed + Thu + Fri + Sat + Sun + + + Export/Import Data + Export + Import + Exported to %1$s + File was imported successfully + Failed to import file + Export Path: %1$s + Import Path: %1$s + + + + + Frequently Asked Questions + + + User Manual + How to Use: + • Select Dates: Tap on a date to select or deselect it. + • Add or Remove Dates for Period: Click the \'Add or Remove Dates\' button after selecting dates. + • Symptoms: Click the \'Symptoms\' button to view or add symptoms for the selected date. + • Ovulation: Select a single date and click the ovulation button. To remove, select the date and press the button again. + • Statistics: Click the \'Burger menu\' and select statistics. + • Import/Export: Click the \'Import/Export\' button to import or export your data. This is a MUST when moving the data to a new device! + • Calculations: \nPeriods are calculated according to average cycle length and period length.\nPeriods can be calculated using average luteal phase. The calculations will then look at the last 5 cycles to make an average luteal phase and use that to calculate the period. This is activated in the settings.\nOvulation is calculated according to average ovulation cycle length. + Features Coming Soon: + • Reminder for upcoming period in the toolbar. (This is shown in settings but not implemented)\n• New user interface (when I\'m in the mood).\n• Other small UI improvements + Our Story + We are two women who were tired of selling our data to big corporations. Our app stores all your data locally on your device and we have <b>no access to it whatsoever</b>.\nWe value your privacy and do not save or share any personal information. Since launching this app we have grown and now have our own Discord server with people who are passionate about this app and want to help us out!\n\nJoin us on Discord here: https://discord.gg/tHA2k3bFRN + Disclaimer: + • This is a hobby project and is not intended for medical use. We are simply doing this for our own needs. But we do welcome ideas and requests. Join us on Discord or send us an email:\nmensinator.app@gmail.com + + + Close + diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml new file mode 100644 index 0000000..b460e41 --- /dev/null +++ b/app/src/main/res/values-ta/strings.xml @@ -0,0 +1,123 @@ + + + மென்சினேட்டர் + + + முந்தைய + முந்தைய + காலம் + அறிகுறிகள் + அறிகுறிகள் + + சேமிக்க அல்லது அகற்ற தேதிகள் இல்லை + மாற்றங்கள் வெற்றிகரமாக சேமிக்கப்பட்டன + மாற்றங்கள் வெற்றிகரமாக சேமிக்கப்பட்டன + ஒரே ஒரு நாள் தான் அண்டவிடுப்பின் முடியும்! + அண்டவிடுப்பின் வெற்றிகரமாக சேமிக்கப்பட்டது + அண்டவிடுப்பின் தேதி எதுவும் தேர்ந்தெடுக்கப்படவில்லை + + + + பயன்பாட்டு அமைப்புகள் + வண்ணங்கள் + மாதவிடாய் நிறம் + தேர்வு நிறம் + மாதவிடாய் தேர்வு நிறம் + எதிர்பார்க்கப்படும் மாதவிடாய் நிறம் + அண்டவிடுப்பின் நிறம் + எதிர்பார்க்கப்படும் அண்டவிடுப்பின் நிறம் + நினைவூட்டல்கள் + நினைவூட்டலுக்கு நாட்களுக்கு முன் + பிற அமைப்புகள் + லூட்டல் கட்ட கணக்கீடு + கால வரலாறு + அண்டவிடுப்பின் வரலாறு + மொழி + சுழற்சி எண்களைக் காட்டு + மூடு + சேமிக்கவும் + + சிவப்பு + பச்சை + நீலம் + மஞ்சள் + சியான் + மெஜந்தா + கருப்பு + வெள்ளை + அடர் சாம்பல் + சாம்பல் + + + புள்ளிவிவரங்கள் + கண்காணிக்கப்பட்ட மாதவிடாய் எண்ணிக்கை: %1$d + சராசரி சுழற்சி நீளம்: %1$.1f days + சராசரி மாதவிடாய் நீளம்: %1$.1f days + \n\nஊகிக்கப்பட்ட காலக்கெடு கடந்துவிட்டது! + அடுத்ததாகக் கருதப்படும் மாதவிடாய்: %1$s + கண்காணிக்கப்பட்ட அண்டவிடுப்பின் எண்ணிக்கை: %1$d + சராசரி அண்டவிடுப்பின் நாள்: %1$s + அடுத்த கணிக்கப்பட்ட அண்டவிடுப்பின் தேதி: %1$s + அடுத்த அண்டவிடுப்பின் கணிக்க போதுமான தரவு இல்லை + சராசரி லூட்டல் கட்ட நீளம்: %1$.1f days + + + அறிகுறி %1$s + புதிய அறிகுறியை உருவாக்கவும் + அறிகுறிகளைச் சேமிக்கவும் + ரத்து + கனமான ஓட்டம் + நடுத்தர ஓட்டம் + ஒளி ஓட்டம் + + புதிய அறிகுறியை உருவாக்கவும் + அறிகுறி பெயர் + சேமி + + அறிகுறிகளை நிர்வகிக்கவும் + + + + தி + செ + பு + வி + வெ + + சூ + + + ஏற்றுமதி/இறக்குமதி தரவு + ஏற்றுமதி + இறக்குமதி + ஏற்றுமதி செய்யப்பட்டது %1$s + கோப்பு வெற்றிகரமாக இறக்குமதி செய்யப்பட்டது + கோப்பை இறக்குமதி செய்ய முடியவில்லை + ஏற்றுமதி பாதை: %1$s + இறக்குமதி பாதை: %1$s + + + + + அடிக்கடி கேட்கப்படும் கேள்விகள் + + + பயனர் கையேடு + எப்படி உபயோகிப்பது: + • தேதிகளைத் தேர்ந்தெடுக்கவும்: ஒரு தேதியைத் தேர்ந்தெடுக்க அல்லது தேர்வுநீக்க அதைத் தட்டவும். + • காலத்திற்கான தேதிகளைச் சேர்க்கவும் அல்லது அகற்றவும்: தேதிகளைத் தேர்ந்தெடுத்த பிறகு \'தேதிகளைச் சேர் அல்லது அகற்று\' பொத்தானைக் கிளிக் செய்யவும். + • அறிகுறிகள்: தேர்ந்தெடுக்கப்பட்ட தேதிக்கான அறிகுறிகளைப் பார்க்க அல்லது சேர்க்க \'அறிகுறிகள்\' பொத்தானைக் கிளிக் செய்யவும். + • அண்டவிடுப்பின்: ஒரு தேதியைத் தேர்ந்தெடுத்து, அண்டவிடுப்பின் பொத்தானைக் கிளிக் செய்யவும். அகற்ற, தேதியைத் தேர்ந்தெடுத்து மீண்டும் பொத்தானை அழுத்தவும். + • புள்ளி விவரங்கள்: \'பர்கர் மெனு\' என்பதைக் கிளிக் செய்து புள்ளிவிவரங்களைத் தேர்ந்தெடுக்கவும். + • இறக்குமதி/ஏற்றுமதி: உங்கள் தரவை இறக்குமதி செய்ய அல்லது ஏற்றுமதி செய்ய \'இறக்குமதி/ஏற்றுமதி\' பொத்தானைக் கிளிக் செய்யவும். புதிய சாதனத்திற்கு தரவை நகர்த்தும்போது இது அவசியம்! + • கணக்கீடுகள்: \nசராசரி சுழற்சி நீளம் மற்றும் காலத்தின் நீளத்தின் படி காலங்கள் கணக்கிடப்படுகின்றன.\nசராசரி லுடீயல் கட்டத்தைப் பயன்படுத்தி காலங்களை கணக்கிடலாம். கணக்கீடுகள் கடைசி 5 சுழற்சிகளைப் பார்த்து, சராசரி லுடீயல் கட்டத்தை உருவாக்கி, காலத்தைக் கணக்கிட அதைப் பயன்படுத்தும். இது அமைப்புகளில் செயல்படுத்தப்படுகிறது.\nசராசரி அண்டவிடுப்பின் சுழற்சி நீளத்தின்படி அண்டவிடுப்பின் கணக்கிடப்படுகிறது. + அம்சங்கள் விரைவில்: + • கருவிப்பட்டியில் வரவிருக்கும் காலத்திற்கான நினைவூட்டல். (இது அமைப்புகளில் காட்டப்பட்டுள்ளது, ஆனால் செயல்படுத்தப்படவில்லை)\n• புதிய பயனர் இடைமுகம் (நான் மனநிலையில் இருக்கும்போது).\n• பிற சிறிய UI மேம்பாடுகள் + நமது கதை + எங்கள் தரவை பெரிய நிறுவனங்களுக்கு விற்று சோர்வடைந்த இரண்டு பெண்கள் நாங்கள். எங்கள் ஆப்ஸ் உங்கள் எல்லா தரவையும் உங்கள் சாதனத்தில் உள்ளூரில் சேமித்து வைக்கிறது மேலும் எங்களிடம் <b>எந்தவிதமான அணுகலும் இல்லை</b>.\nஉங்கள் தனியுரிமையை நாங்கள் மதிக்கிறோம் மற்றும் எந்த தனிப்பட்ட தகவலையும் சேமிக்கவோ அல்லது பகிரவோ மாட்டோம். இந்த செயலியை அறிமுகப்படுத்தியதில் இருந்து நாங்கள் வளர்ந்துள்ளோம், இப்போது இந்த செயலியில் ஆர்வமுள்ள மற்றும் எங்களுக்கு உதவ விரும்பும் நபர்களுடன் எங்கள் சொந்த டிஸ்கார்ட் சேவையகத்தை வைத்திருக்கிறோம்!\n\nஇங்கே டிஸ்கார்டில் எங்களுடன் சேரவும்: https://discord.gg/tHA2k3bFRN + மறுப்பு: + • இது ஒரு பொழுதுபோக்கு திட்டம் மற்றும் மருத்துவ பயன்பாட்டிற்காக அல்ல. இதை நாம் நமது தேவைக்காக மட்டுமே செய்கிறோம். ஆனால் நாங்கள் யோசனைகளையும் கோரிக்கைகளையும் வரவேற்கிறோம். டிஸ்கார்டில் எங்களுடன் சேரவும் அல்லது எங்களுக்கு மின்னஞ்சல் அனுப்பவும்:\nmensinator.app@gmail.com + + + மூடு + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c87f0ae..79c9fac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,124 @@ + Mensinator - \ No newline at end of file + + + Previous + Next + Period + Symptoms + ovulation + + No dates to save or remove + Changes saved successfully + No dates selected + Only one day can be ovulation! + Ovulation saved successfully + No date selected for ovulation + + + + App Settings + Colors + Period Color + Selection Color + Period Selection Color + Expected Period Color + Ovulation Color + Expected Ovulation Color + Reminders + Days Before Reminder + Other settings + Luteal Phase Calculation + Period history + Ovulation history + Language + Show cycle numbers + Close + Save + + Red + Green + Blue + Yellow + Cyan + Magenta + Black + White + DarkGray + Grey + + + Statistics + Number of periods tracked: %1$d + Average cycle length: %1$.1f days + Average period length: %1$.1f days + Next assumed period: %1$s \n\nAssumed period date has passed! + Next assumed period: %1$s + Number of ovulation tracked: %1$d + Average ovulation day: %1$s + Next predicted ovulation date: %1$s + Not enough data to predict next ovulation + Average luteal phase length: %1$.1f days + + + Symptoms for %1$s + Create New Symptom + Save symptoms + Cancel + Heavy Flow + Medium Flow + Light Flow + + + Create New Symptom + Symptom Name + Save + + Manage Symptoms + + + + Mon + Tue + Wed + Thu + Fri + Sat + Sun + + + Export/Import Data + Export + Import + Exported to %1$s + File was imported successfully + Failed to import file + Export Path: %1$s + Import Path: %1$s + + + + + Frequently Asked Questions + + + User Manual + How to Use: + • Select Dates: Tap on a date to select or deselect it. + • Add or Remove Dates for Period: Click the \'Add or Remove Dates\' button after selecting dates. + • Symptoms: Click the \'Symptoms\' button to view or add symptoms for the selected date. + • Ovulation: Select a single date and click the ovulation button. To remove, select the date and press the button again. + • Statistics: Click the \'Burger menu\' and select statistics. + • Import/Export: Click the \'Import/Export\' button to import or export your data. This is a MUST when moving the data to a new device! + • Calculations: \nPeriods are calculated according to average cycle length and period length.\nPeriods can be calculated using average luteal phase. The calculations will then look at the last 5 cycles to make an average luteal phase and use that to calculate the period. This is activated in the settings.\nOvulation is calculated according to average ovulation cycle length. + Features Coming Soon: + • Reminder for upcoming period in the toolbar. (This is shown in settings but not implemented)\n• New user interface (when I\'m in the mood).\n• Other small UI improvements + Our Story + We are two women who were tired of selling our data to big corporations. Our app stores all your data locally on your device and we have <b>no access to it whatsoever</b>.\nWe value your privacy and do not save or share any personal information. Since launching this app we have grown and now have our own Discord server with people who are passionate about this app and want to help us out!\n\nJoin us on Discord here: https://discord.gg/tHA2k3bFRN + Disclaimer: + • This is a hobby project and is not intended for medical use. We are simply doing this for our own needs. But we do welcome ideas and requests. Join us on Discord or send us an email:\nmensinator.app@gmail.com + + + Close + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 1cef651..31793ba 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,5 +1,5 @@ -