Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Multi Language support! #55

Merged
merged 2 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
48 changes: 48 additions & 0 deletions app/src/main/java/com/mensinator/app/Calculations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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()) {
Expand All @@ -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)

Expand All @@ -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<Int>()

Expand All @@ -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{
Expand Down Expand Up @@ -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
Expand Down
54 changes: 41 additions & 13 deletions app/src/main/java/com/mensinator/app/CalendarScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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".
Expand Down Expand Up @@ -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()
Expand All @@ -208,18 +221,26 @@ 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)
)
Button(onClick = {
currentMonth.value = currentMonth.value.plusMonths(1)
}) {
Text(text = "Next")
Text(text = stringResource(id = R.string.next))
}
}
Spacer(modifier = Modifier.height(8.dp))
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -450,49 +473,54 @@ 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
modifier = Modifier
.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
modifier = Modifier
.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()
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/mensinator/app/DatabaseUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"""
Expand Down
24 changes: 15 additions & 9 deletions app/src/main/java/com/mensinator/app/ExportImportDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -30,22 +33,24 @@ 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)
importPath.value = file.absolutePath
// 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
Expand All @@ -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))
}
}
)
Expand Down
Loading