From 693b6c255c06d121899537129ca0d7920c9ee910 Mon Sep 17 00:00:00 2001 From: Carsten Hagemann Date: Sat, 9 Nov 2024 22:00:42 +0100 Subject: [PATCH 1/2] Introduce koin, add mockk for unit tests --- app/build.gradle.kts | 8 ++ app/src/main/AndroidManifest.xml | 1 + app/src/main/java/com/mensinator/app/App.kt | 32 +++++ ...{Calculations.kt => CalculationsHelper.kt} | 106 ++++++---------- .../java/com/mensinator/app/CalendarScreen.kt | 53 ++++---- .../java/com/mensinator/app/ExportImport.kt | 30 ++--- .../com/mensinator/app/ExportImportDialog.kt | 24 +++- .../com/mensinator/app/ICalculationsHelper.kt | 52 ++++++++ .../java/com/mensinator/app/IExportImport.kt | 8 ++ .../mensinator/app/INotificationScheduler.kt | 8 ++ .../mensinator/app/IOvulationPrediction.kt | 7 ++ .../com/mensinator/app/IPeriodPrediction.kt | 7 ++ .../com/mensinator/app/ManageSymptomScreen.kt | 35 +----- .../mensinator/app/NotificationScheduler.kt | 16 +-- .../com/mensinator/app/OvulationPrediction.kt | 33 +++-- .../java/com/mensinator/app/PeriodDatabase.kt | 91 ++++++++++++++ .../mensinator/app/PeriodDatabaseHelper.kt | 117 ++++++++++-------- .../com/mensinator/app/PeriodPrediction.kt | 26 ++-- .../java/com/mensinator/app/Prediction.kt | 11 -- .../java/com/mensinator/app/SettingsScreen.kt | 25 ++-- .../com/mensinator/app/StatisticsScreen.kt | 29 ++--- .../java/com/mensinator/app/SymptomDialogs.kt | 8 +- .../mensinator/app/navigation/BottomBar.kt | 26 +--- .../mensinator/app/PeriodPredictionTest.kt | 35 ++++++ gradle/libs.versions.toml | 7 ++ 25 files changed, 492 insertions(+), 303 deletions(-) create mode 100644 app/src/main/java/com/mensinator/app/App.kt rename app/src/main/java/com/mensinator/app/{Calculations.kt => CalculationsHelper.kt} (74%) create mode 100644 app/src/main/java/com/mensinator/app/ICalculationsHelper.kt create mode 100644 app/src/main/java/com/mensinator/app/IExportImport.kt create mode 100644 app/src/main/java/com/mensinator/app/INotificationScheduler.kt create mode 100644 app/src/main/java/com/mensinator/app/IOvulationPrediction.kt create mode 100644 app/src/main/java/com/mensinator/app/IPeriodPrediction.kt create mode 100644 app/src/main/java/com/mensinator/app/PeriodDatabase.kt delete mode 100644 app/src/main/java/com/mensinator/app/Prediction.kt create mode 100644 app/src/test/java/com/mensinator/app/PeriodPredictionTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a85ee54..b941ee9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -73,11 +73,19 @@ dependencies { implementation(libs.androidx.navigation.runtime.ktx) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.common.ktx) + + implementation(platform(libs.koin.bom)) + implementation(libs.koin) + implementation(libs.koin.compose) + testImplementation(libs.junit) + testImplementation(libs.mockk) + androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e72001..dde3a4e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ android:allowBackup="false" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="false" + android:name=".App" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" diff --git a/app/src/main/java/com/mensinator/app/App.kt b/app/src/main/java/com/mensinator/app/App.kt new file mode 100644 index 0000000..4cbc925 --- /dev/null +++ b/app/src/main/java/com/mensinator/app/App.kt @@ -0,0 +1,32 @@ +package com.mensinator.app + +import android.app.Application +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +class App : Application() { + + // Koin dependency injection definitions + private val appModule = module { + singleOf(::PeriodDatabaseHelper) { bind() } + singleOf(::CalculationsHelper) { bind() } + singleOf(::OvulationPrediction) { bind() } + singleOf(::PeriodPrediction) { bind() } + singleOf(::ExportImport) { bind() } + singleOf(::NotificationScheduler) { bind() } + } + + override fun onCreate() { + super.onCreate() + + startKoin { + androidLogger() + androidContext(this@App) + modules(appModule) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mensinator/app/Calculations.kt b/app/src/main/java/com/mensinator/app/CalculationsHelper.kt similarity index 74% rename from app/src/main/java/com/mensinator/app/Calculations.kt rename to app/src/main/java/com/mensinator/app/CalculationsHelper.kt index 8cbd5d3..75e0033 100644 --- a/app/src/main/java/com/mensinator/app/Calculations.kt +++ b/app/src/main/java/com/mensinator/app/CalculationsHelper.kt @@ -1,51 +1,40 @@ package com.mensinator.app -import android.content.Context import android.util.Log import java.time.LocalDate import kotlin.math.round -/** - * 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) +class CalculationsHelper( + private val dbHelper: IPeriodDatabaseHelper, +) : ICalculationsHelper { 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(): LocalDate { + override fun calculateNextPeriod(): LocalDate { val expectedPeriodDate: LocalDate - if(lutealCalculation==1){ + if (lutealCalculation == 1) { //go to advanced calculation using X latest ovulations (set by user in settings) Log.d("TAG", "Advanced calculation") expectedPeriodDate = advancedNextPeriod() - } else{ + } 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) val listPeriodDates = dbHelper.getLatestXPeriodStart(periodHistory) - if(listPeriodDates.isEmpty()){ + if (listPeriodDates.isEmpty()) { expectedPeriodDate = LocalDate.parse("1900-01-01") - } - else{ + } else { // Calculate the cycle lengths between consecutive periods val cycleLengths = mutableListOf() for (i in 0 until listPeriodDates.size - 1) { - val cycleLength = java.time.temporal.ChronoUnit.DAYS.between(listPeriodDates[i], listPeriodDates[i + 1]) + val cycleLength = java.time.temporal.ChronoUnit.DAYS.between( + listPeriodDates[i], + listPeriodDates[i + 1] + ) cycleLengths.add(cycleLength) } // Calculate the average cycle length @@ -57,11 +46,10 @@ 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. + * @return The next expected period date. */ private fun advancedNextPeriod(): LocalDate { // Get the list of the latest ovulation dates @@ -94,8 +82,9 @@ class Calculations (context: Context){ Log.d("TAG", "Ovulation is null") return LocalDate.parse("1900-01-01") } - val periodDates = dbHelper.getLatestXPeriodStart(ovulationHistory) //This always returns no+1 period dates - if(periodDates.isNotEmpty() && periodDates.last() > lastOvulation){ //Check the latest first periodDate + val periodDates = + dbHelper.getLatestXPeriodStart(ovulationHistory) //This always returns no+1 period dates + if (periodDates.isNotEmpty() && periodDates.last() > lastOvulation) { //Check the latest first periodDate //There is a period start date after last ovulation date //We need to recalculate according to next calculated ovulation val avgGrowthRate = averageFollicalGrowthInDays() @@ -103,8 +92,7 @@ class Calculations (context: Context){ val expectedPeriodDate = expectedOvulation.plusDays(averageLutealLength.toLong()) Log.d("TAG", "Calculating according to calculated ovulation: $expectedPeriodDate") return expectedPeriodDate - } - else{ + } else { val expectedPeriodDate = lastOvulation.plusDays(averageLutealLength.toLong()) Log.d("TAG", "Next expected period: $expectedPeriodDate") // Calculate the expected period date @@ -112,13 +100,8 @@ 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(): Double { + //Returns average number of days from first last period to ovulation for passed X periods + override fun averageFollicalGrowthInDays(): Double { val ovulationDates = dbHelper.getXLatestOvulationsDates(ovulationHistory) if (ovulationDates.isEmpty()) { // Return a meaningful message or handle the case where no ovulations are available @@ -128,7 +111,9 @@ class Calculations (context: Context){ for (d in ovulationDates) { val firstDatePreviousPeriod = dbHelper.getFirstPreviousPeriodDate(d) if (firstDatePreviousPeriod != null) { - growthRate.add(d.toEpochDay().toInt() - firstDatePreviousPeriod.toEpochDay().toInt()) + growthRate.add( + d.toEpochDay().toInt() - firstDatePreviousPeriod.toEpochDay().toInt() + ) } } if (growthRate.isEmpty()) { @@ -140,12 +125,7 @@ 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{ + override fun averageCycleLength(): Double { val periodDates = dbHelper.getLatestXPeriodStart(periodHistory) // Check if there are enough dates to calculate cycle lengths @@ -159,7 +139,8 @@ class Calculations (context: Context){ // Calculate the cycle lengths between consecutive periods for (i in 0 until periodDates.size - 1) { - val cycleLength = java.time.temporal.ChronoUnit.DAYS.between(periodDates[i], periodDates[i + 1]) + val cycleLength = + java.time.temporal.ChronoUnit.DAYS.between(periodDates[i], periodDates[i + 1]) cycleLengths.add(cycleLength) } @@ -169,13 +150,7 @@ 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 { + override fun averagePeriodLength(): Double { val daysInPeriod = mutableListOf() // Retrieve the start dates of the latest periods @@ -200,14 +175,9 @@ 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{ + override fun averageLutealLength(): Double { // List to hold the luteal lengths val lutealLengths = mutableListOf() @@ -222,11 +192,11 @@ class Calculations (context: Context){ // Check if the nextPeriodDate is not null if (nextPeriodDate != null) { // Calculate the number of days between the ovulation date and the next period date - val daysBetween = java.time.temporal.ChronoUnit.DAYS.between(ovulationDate, nextPeriodDate) + val daysBetween = + java.time.temporal.ChronoUnit.DAYS.between(ovulationDate, nextPeriodDate) // Add the result to the list lutealLengths.add(daysBetween) - } - else{ + } else { Log.d("TAG", "Next period date is null") return 0.0 } @@ -236,23 +206,17 @@ class Calculations (context: Context){ } - /** - * 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 - fun getLutealLengthForPeriod(date: LocalDate): Int { + override fun getLutealLengthForPeriod(date: LocalDate): Int { val firstNextPeriodDate = dbHelper.getFirstNextPeriodDate(date) - if(firstNextPeriodDate != null){ - val lutealLength = java.time.temporal.ChronoUnit.DAYS.between(date, firstNextPeriodDate).toInt() + if (firstNextPeriodDate != null) { + val lutealLength = + java.time.temporal.ChronoUnit.DAYS.between(date, firstNextPeriodDate).toInt() Log.d("TAG", "Luteal for single date PDH $firstNextPeriodDate $date: $lutealLength") return lutealLength - } - else{ + } else { return 0 } diff --git a/app/src/main/java/com/mensinator/app/CalendarScreen.kt b/app/src/main/java/com/mensinator/app/CalendarScreen.kt index da3ec2c..98ce987 100644 --- a/app/src/main/java/com/mensinator/app/CalendarScreen.kt +++ b/app/src/main/java/com/mensinator/app/CalendarScreen.kt @@ -4,17 +4,15 @@ package com.mensinator.app import android.content.Context import android.util.Log import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowForward -import androidx.compose.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -29,6 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.mensinator.app.data.DataSource import com.mensinator.app.ui.theme.isDarkMode +import org.koin.compose.koinInject import java.time.DayOfWeek import java.time.LocalDate import java.time.temporal.ChronoUnit @@ -36,21 +35,17 @@ import java.time.temporal.ChronoUnit /* This file creates the calendar. A sort of "main screen". */ - @Composable -fun CalendarScreen( -) { +fun CalendarScreen() { val context = LocalContext.current + val dbHelper: IPeriodDatabaseHelper = koinInject() + val ovulationPrediction: IOvulationPrediction = koinInject() + val periodPrediction: IPeriodPrediction = koinInject() + val notificationScheduler: INotificationScheduler = koinInject() - // new objects for prediction - val periodPrediction = PeriodPrediction(context) var nextPeriodDate = periodPrediction.getPredictedPeriodDate() - val ovulationPrediction = OvulationPrediction(context) var ovulationPredictionDate = ovulationPrediction.getPredictedOvulationDate() - // For accessing database functions - val dbHelper = remember { PeriodDatabaseHelper(context) } - val currentMonth = remember { mutableStateOf(LocalDate.now().withDayOfMonth(1)) } // Days selected in the calendar val selectedDates = remember { mutableStateOf(setOf()) } @@ -142,8 +137,22 @@ fun CalendarScreen( // Update button state based on selected dates val isSymptomsButtonEnabled by remember { derivedStateOf { selectedDates.value.isNotEmpty() } } - val isOvulationButtonEnabled by remember { derivedStateOf { selectedDates.value.size == 1 && (containsOvulationDate(selectedDates.value, ovulationDates.value) || !containsPeriodDate(selectedDates.value, periodDates.value))} } // Ovulation can only occur on one day - val isPeriodsButtonEnabled by remember { derivedStateOf { selectedDates.value.isNotEmpty() && (containsPeriodDate(selectedDates.value, periodDates.value) || !containsOvulationDate(selectedDates.value, ovulationDates.value))} } + val isOvulationButtonEnabled by remember { + derivedStateOf { + selectedDates.value.size == 1 && (containsOvulationDate( + selectedDates.value, + ovulationDates.value + ) || !containsPeriodDate(selectedDates.value, periodDates.value)) + } + } // Ovulation can only occur on one day + val isPeriodsButtonEnabled by remember { + derivedStateOf { + selectedDates.value.isNotEmpty() && (containsPeriodDate( + selectedDates.value, + periodDates.value + ) || !containsOvulationDate(selectedDates.value, ovulationDates.value)) + } + } // Here is where the calendar is generated LaunchedEffect(currentMonth.value) { @@ -427,7 +436,8 @@ fun CalendarScreen( * Make sure that if two or more days are selected (and at least one is already marked as period), * we should make sure that all days are removed. */ - val datesAlreadyMarkedAsPeriod = selectedDates.value.intersect(periodDates.value.keys) + val datesAlreadyMarkedAsPeriod = + selectedDates.value.intersect(periodDates.value.keys) if (datesAlreadyMarkedAsPeriod.isEmpty()) { selectedDates.value.forEach { val periodId = dbHelper.newFindOrCreatePeriodID(it) @@ -460,6 +470,7 @@ fun CalendarScreen( if (reminderDays > 0 && nextPeriodDate != LocalDate.parse("1900-01-01") && nextPeriodDate >= LocalDate.now()) { newSendNotification( context, + notificationScheduler, reminderDays, nextPeriodDate ) @@ -533,6 +544,7 @@ fun CalendarScreen( if (reminderDays > 0 && nextPeriodDate != LocalDate.parse("1900-01-01") && nextPeriodDate >= LocalDate.now()) { newSendNotification( context, + notificationScheduler, reminderDays, nextPeriodDate ) @@ -601,8 +613,7 @@ fun containsOvulationDate(selectedDates: Set, ovulationDates: Set Unit, onExportClick: (String) -> Unit, modifier: Modifier = Modifier, ) { val context = LocalContext.current - val exportImport = remember { ExportImport() } val isBeingPreviewed = LocalInspectionMode.current val exportPath = remember { @@ -72,12 +72,12 @@ fun ExportDialog( @Composable fun ImportDialog( + exportImport: IExportImport, onDismissRequest: () -> Unit, onImportClick: (String) -> Unit, modifier: Modifier = Modifier, ) { val context = LocalContext.current - val exportImport = remember { ExportImport() } val importPath = remember { mutableStateOf("") } val impSuccess = stringResource(id = R.string.import_success_toast) @@ -88,7 +88,7 @@ fun ImportDialog( if (uri == null) return@rememberLauncherForActivityResult val inputStream = context.contentResolver.openInputStream(uri) - val file = File(exportImport.getDefaultImportFilePath(context)) + val file = File(exportImport.getDefaultImportFilePath()) val outputStream = FileOutputStream(file) try { inputStream?.copyTo(outputStream) @@ -146,11 +146,21 @@ fun ImportDialog( } +private val fakeExportImport = object : IExportImport { + override fun getDocumentsExportFilePath() = "ExportPath" + override fun getDefaultImportFilePath() = "ImportPath" + override fun exportDatabase(filePath: String) {} + override fun importDatabase(filePath: String) {} +} + @Preview @Composable private fun ExportDialogPreview() { MensinatorTheme { - ExportDialog(onDismissRequest = {}, onExportClick = {}) + ExportDialog( + exportImport = fakeExportImport, + onDismissRequest = {}, + onExportClick = {}) } } @@ -158,6 +168,10 @@ private fun ExportDialogPreview() { @Composable private fun ImportDialogPreview() { MensinatorTheme { - ImportDialog(onDismissRequest = {}, onImportClick = {}) + ImportDialog( + exportImport = fakeExportImport, + onDismissRequest = {}, + onImportClick = {} + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/mensinator/app/ICalculationsHelper.kt b/app/src/main/java/com/mensinator/app/ICalculationsHelper.kt new file mode 100644 index 0000000..354bfe7 --- /dev/null +++ b/app/src/main/java/com/mensinator/app/ICalculationsHelper.kt @@ -0,0 +1,52 @@ +package com.mensinator.app + +import java.time.LocalDate + +/** + * This helper provides methods to calculate menstrual cycle related data + * such as next period date, average cycle length, and average luteal length. + */ +interface ICalculationsHelper { + /** + * Calculates the next expected period date. + * + * @return The next expected period date. + */ + fun calculateNextPeriod(): LocalDate + + /** + * 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(): Double + + /** + * 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 + + /** + * Calculates the average period length using the latest period start dates. + * + * @return The average period length as a double. + */ + fun averagePeriodLength(): Double + + /** + * Calculates the average luteal phase length using the latest ovulation dates. + * + * @return The average luteal phase length as a double. + */ + fun averageLutealLength(): Double + + /** + * Calculates the luteal phase length for a specific cycle. + * + * @param date The ovulation date. + * @return The luteal phase length as an integer. + */ + fun getLutealLengthForPeriod(date: LocalDate): Int +} diff --git a/app/src/main/java/com/mensinator/app/IExportImport.kt b/app/src/main/java/com/mensinator/app/IExportImport.kt new file mode 100644 index 0000000..0fd77b1 --- /dev/null +++ b/app/src/main/java/com/mensinator/app/IExportImport.kt @@ -0,0 +1,8 @@ +package com.mensinator.app + +interface IExportImport { + fun getDocumentsExportFilePath(): String + fun getDefaultImportFilePath(): String + fun exportDatabase(filePath: String) + fun importDatabase(filePath: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/mensinator/app/INotificationScheduler.kt b/app/src/main/java/com/mensinator/app/INotificationScheduler.kt new file mode 100644 index 0000000..42c4c48 --- /dev/null +++ b/app/src/main/java/com/mensinator/app/INotificationScheduler.kt @@ -0,0 +1,8 @@ +package com.mensinator.app + +import java.time.LocalDate + +interface INotificationScheduler { + fun scheduleNotification(notificationDate: LocalDate) + fun cancelNotification(notificationId: Int) +} diff --git a/app/src/main/java/com/mensinator/app/IOvulationPrediction.kt b/app/src/main/java/com/mensinator/app/IOvulationPrediction.kt new file mode 100644 index 0000000..ab86ae8 --- /dev/null +++ b/app/src/main/java/com/mensinator/app/IOvulationPrediction.kt @@ -0,0 +1,7 @@ +package com.mensinator.app + +import java.time.LocalDate + +interface IOvulationPrediction { + fun getPredictedOvulationDate(): LocalDate +} diff --git a/app/src/main/java/com/mensinator/app/IPeriodPrediction.kt b/app/src/main/java/com/mensinator/app/IPeriodPrediction.kt new file mode 100644 index 0000000..eec35cd --- /dev/null +++ b/app/src/main/java/com/mensinator/app/IPeriodPrediction.kt @@ -0,0 +1,7 @@ +package com.mensinator.app + +import java.time.LocalDate + +interface IPeriodPrediction { + fun getPredictedPeriodDate(): LocalDate +} diff --git a/app/src/main/java/com/mensinator/app/ManageSymptomScreen.kt b/app/src/main/java/com/mensinator/app/ManageSymptomScreen.kt index 694519e..7279d77 100644 --- a/app/src/main/java/com/mensinator/app/ManageSymptomScreen.kt +++ b/app/src/main/java/com/mensinator/app/ManageSymptomScreen.kt @@ -2,43 +2,18 @@ package com.mensinator.app import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -48,6 +23,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.mensinator.app.data.DataSource import com.mensinator.app.ui.theme.isDarkMode +import org.koin.compose.koinInject //Maps Database keys to res/strings.xml for multilanguage support @@ -55,8 +31,7 @@ import com.mensinator.app.ui.theme.isDarkMode fun ManageSymptomScreen( showCreateSymptom: MutableState, ) { - val context = LocalContext.current - val dbHelper = remember { PeriodDatabaseHelper(context) } + val dbHelper: IPeriodDatabaseHelper = koinInject() var initialSymptoms = remember { dbHelper.getAllSymptoms() } var savedSymptoms by remember { mutableStateOf(initialSymptoms) } diff --git a/app/src/main/java/com/mensinator/app/NotificationScheduler.kt b/app/src/main/java/com/mensinator/app/NotificationScheduler.kt index 88996b7..5e0f3bf 100644 --- a/app/src/main/java/com/mensinator/app/NotificationScheduler.kt +++ b/app/src/main/java/com/mensinator/app/NotificationScheduler.kt @@ -10,9 +10,15 @@ import androidx.core.app.NotificationManagerCompat import java.time.LocalDate import java.time.ZoneId -class NotificationScheduler(private val context: Context) { +class NotificationScheduler( + private val context: Context +) : INotificationScheduler { - fun scheduleNotification(notificationDate: LocalDate) { + companion object { + private const val ACTION_NOTIFICATION = "com.mensinator.app.SEND_NOTIFICATION" + } + + override fun scheduleNotification(notificationDate: LocalDate) { val notificationManager = NotificationManagerCompat.from(context) notificationManager.cancelAll() Log.d("NotificationScheduler", "Notification canceled") @@ -38,11 +44,7 @@ class NotificationScheduler(private val context: Context) { Log.d("NotificationScheduler", "Notification scheduled for $notificationDate") } - companion object { - private const val ACTION_NOTIFICATION = "com.mensinator.app.SEND_NOTIFICATION" - } - - fun cancelNotification(notificationId: Int) { + override fun cancelNotification(notificationId: Int) { val notificationManager = NotificationManagerCompat.from(context) notificationManager.cancel(notificationId) } diff --git a/app/src/main/java/com/mensinator/app/OvulationPrediction.kt b/app/src/main/java/com/mensinator/app/OvulationPrediction.kt index 5de1eba..43fe31f 100644 --- a/app/src/main/java/com/mensinator/app/OvulationPrediction.kt +++ b/app/src/main/java/com/mensinator/app/OvulationPrediction.kt @@ -1,19 +1,23 @@ package com.mensinator.app -import android.content.Context import android.util.Log import java.time.LocalDate -class OvulationPrediction (context: Context) : Prediction(context) { - private val periodPrediction = PeriodPrediction(context) - private var periodPredictionDate = periodPrediction.getPredictedPeriodDate() - private lateinit var ovulationDatePrediction: LocalDate - private val lastOvulationDate = dbHelper.getNewestOvulationDate() - private val firstDayOfNextMonth = LocalDate.now().withDayOfMonth(1).plusMonths(1) - private val previousFirstPeriodDate = dbHelper.getFirstPreviousPeriodDate(firstDayOfNextMonth) +class OvulationPrediction( + private val dbHelper: IPeriodDatabaseHelper, + private val calcHelper: ICalculationsHelper, + private val periodPrediction: IPeriodPrediction, +) : IOvulationPrediction { - fun getPredictedOvulationDate(): LocalDate { + override fun getPredictedOvulationDate(): LocalDate { + val periodCount = dbHelper.getPeriodCount() val ovulationCount = dbHelper.getOvulationCount() + val periodPredictionDate = periodPrediction.getPredictedPeriodDate() + val lastOvulationDate = dbHelper.getNewestOvulationDate() + val firstDayOfNextMonth = LocalDate.now().withDayOfMonth(1).plusMonths(1) + val previousFirstPeriodDate = dbHelper.getFirstPreviousPeriodDate(firstDayOfNextMonth) + + val ovulationDatePrediction: LocalDate // No data at all in database if (lastOvulationDate == null || previousFirstPeriodDate == null) { @@ -40,15 +44,14 @@ class OvulationPrediction (context: Context) : Prediction(context) { val follicleGrowthDays = calcHelper.averageFollicalGrowthInDays() val follicleGrowthDaysLong = follicleGrowthDays.toLong() - if(follicleGrowthDaysLong > 0){ + if (follicleGrowthDaysLong > 0) { ovulationDatePrediction = periodPredictionDate.plusDays(follicleGrowthDaysLong) - } - else{ + } else { // Return a default value, not enough data to predict ovulation, averageFollicalGrowthInDays() returns 0 ovulationDatePrediction = LocalDate.parse("1900-01-01") } } - else{ + else { Log.d("TAG", "THERE ARE NOW OVULATIONS") // Return a default value, not enough data to predict ovulation, ovulationCount < 2 ovulationDatePrediction = LocalDate.parse("1900-01-01") @@ -56,8 +59,4 @@ class OvulationPrediction (context: Context) : Prediction(context) { // Valid prediction return ovulationDatePrediction } - -// fun getOvulationDatePrediction(): LocalDate { -// return ovulationDatePrediction -// } } \ No newline at end of file diff --git a/app/src/main/java/com/mensinator/app/PeriodDatabase.kt b/app/src/main/java/com/mensinator/app/PeriodDatabase.kt new file mode 100644 index 0000000..e5d6163 --- /dev/null +++ b/app/src/main/java/com/mensinator/app/PeriodDatabase.kt @@ -0,0 +1,91 @@ +package com.mensinator.app + +import android.database.sqlite.SQLiteDatabase +import java.time.LocalDate + +interface IPeriodDatabaseHelper { + + // TODO: The database should only be accessible via the functions of this interface. + // Refactor this soon! + val readableDb: SQLiteDatabase + + // TODO: The database should only be accessible via the functions of this interface. + // Refactor this soon! + val writableDb: SQLiteDatabase + + fun addDateToPeriod(date: LocalDate, periodId: Int) + + fun getPeriodDatesForMonth(year: Int, month: Int): Map + + fun getPeriodCount(): Int + + fun removeDateFromPeriod(date: LocalDate) + + fun getAllSymptoms(): List + + fun createNewSymptom(symptomName: String) + + fun getSymptomDatesForMonth(year: Int, month: Int): Set + + fun updateSymptomDate(dates: List, symptomId: List) + + fun getSymptomsFromDate(date: LocalDate): List + + fun getSymptomColorForDate(date: LocalDate): List + + fun getAllSettings(): List + + fun updateSetting(key: String, value: String): Boolean + + fun getSettingByKey(key: String): Setting? + + fun updateOvulationDate(date: LocalDate) + + fun getOvulationDatesForMonth(year: Int, month: Int): Set + + fun getOvulationCount(): Int + + fun newFindOrCreatePeriodID(date: LocalDate): Int + + // Retrieve the previous period's start date from a given date + fun getFirstPreviousPeriodDate(date: LocalDate): LocalDate? + + // Retrieve the oldest period date in the database + fun getOldestPeriodDate(): LocalDate? + + // Retrieve the newest ovulation date in the database + fun getNewestOvulationDate(): LocalDate? + + // Update symptom's active status and color by symptom ID + fun updateSymptom(id: Int, active: Int, color: String) + + // Retrieve the latest X ovulation dates where they are followed by a period + fun getLatestXOvulationsWithPeriod(number: Int): List + + // Retrieve the most recent ovulation date + fun getLastOvulation(): LocalDate? + + // Retrieve the latest X period start dates + fun getLatestXPeriodStart(number: Int): List + + // Retrieve the next period start date after a given date + fun getFirstNextPeriodDate(date: LocalDate): LocalDate? + + // Get the number of dates in a given period + fun getNoOfDatesInPeriod(date: LocalDate): Int + + // Retrieve the latest X ovulation dates + fun getXLatestOvulationsDates(number: Int): List + + // Remove a symptom by its ID + fun deleteSymptom(symptomId: Int) + + // Get the database version + fun getDBVersion(): String + + // Rename a symptom by its ID + fun renameSymptom(symptomId: Int, newName: String) + + // Retrieve the latest period start date + fun getLatestPeriodStart(): LocalDate +} diff --git a/app/src/main/java/com/mensinator/app/PeriodDatabaseHelper.kt b/app/src/main/java/com/mensinator/app/PeriodDatabaseHelper.kt index b1dcce6..d5e0825 100644 --- a/app/src/main/java/com/mensinator/app/PeriodDatabaseHelper.kt +++ b/app/src/main/java/com/mensinator/app/PeriodDatabaseHelper.kt @@ -11,8 +11,9 @@ import java.time.LocalDate This file contains functions to get/set data into the database For handling database structure, see DatabaseUtils */ - -class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { +class PeriodDatabaseHelper(context: Context) : + SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION), + IPeriodDatabaseHelper { companion object { private const val DATABASE_NAME = "periods.db" @@ -23,6 +24,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS private const val COLUMN_PERIOD_ID = "period_id" private const val TABLE_SYMPTOMS = "symptoms" + //we are using variable COLUMN_ID for ID private const val COLUMN_SYMPTOM_NAME = "symptom_name" private const val COLUMN_SYMPTOM_ACTIVE = "active" @@ -32,6 +34,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS private const val COLUMN_SYMPTOM_ID = "symptom_id" private const val TABLE_APP_SETTINGS = "app_settings" + //we use $COLUMN_ID private const val COLUMN_SETTING_KEY = "setting_key" private const val COLUMN_SETTING_LABEL = "setting_label" @@ -41,6 +44,12 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS private const val TAG = "PeriodDatabaseHelper" } + override val readableDb: SQLiteDatabase + get() = readableDatabase + + override val writableDb: SQLiteDatabase + get() = writableDatabase + //See DatabaseUtils override fun onCreate(db: SQLiteDatabase) { DatabaseUtils.createDatabase(db) @@ -49,35 +58,34 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS //See DatabaseUtils override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - if(oldVersion < 3){ + if (oldVersion < 3) { DatabaseUtils.createAppSettings(db) } - if(oldVersion <4){ + if (oldVersion < 4) { db.execSQL("DROP TABLE IF EXISTS $TABLE_APP_SETTINGS") DatabaseUtils.createAppSettingsGroup(db) DatabaseUtils.createAppSettings(db) } - if(oldVersion < 5){ + if (oldVersion < 5) { DatabaseUtils.createOvulationStructure(db) } - if(oldVersion < 6){ + if (oldVersion < 6) { DatabaseUtils.insertLutealSetting(db) } - if(oldVersion < 7){ + if (oldVersion < 7) { DatabaseUtils.databaseVersion7(db) } - if(oldVersion < 8){ + if (oldVersion < 8) { DatabaseUtils.databaseVersion8(db) - } } //This function is used to add a date together with a period id to the periods table - fun addDateToPeriod(date: LocalDate, periodId: Int) { + override fun addDateToPeriod(date: LocalDate, periodId: Int) { val db = writableDatabase val values = ContentValues().apply { put(COLUMN_DATE, date.toString()) @@ -93,7 +101,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //Get all period dates for a given month - fun getPeriodDatesForMonth(year: Int, month: Int): Map { + override fun getPeriodDatesForMonth(year: Int, month: Int): Map { val dates = mutableMapOf() val db = readableDatabase @@ -137,7 +145,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //Returns how many periods that are in the database - fun getPeriodCount(): Int { + override fun getPeriodCount(): Int { val db = readableDatabase val countQuery = "SELECT COUNT(DISTINCT $COLUMN_PERIOD_ID) FROM $TABLE_PERIODS" val cursor = db.rawQuery(countQuery, null) @@ -151,7 +159,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to remove a date from the periods table - fun removeDateFromPeriod(date: LocalDate) { + override fun removeDateFromPeriod(date: LocalDate) { val db = writableDatabase val whereClause = "$COLUMN_DATE = ?" val whereArgs = arrayOf(date.toString()) @@ -165,7 +173,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to get all symptoms from the database - fun getAllSymptoms(): List { + override fun getAllSymptoms(): List { val db = readableDatabase val symptoms = mutableListOf() val query = @@ -192,7 +200,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function inserts new symptom into the Database - fun createNewSymptom(symptomName: String) { + override fun createNewSymptom(symptomName: String) { val db = writableDatabase val values = ContentValues().apply { put(COLUMN_SYMPTOM_NAME, symptomName) // Set the symptom name @@ -204,7 +212,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function returns all Symptom dates for given month - fun getSymptomDatesForMonth(year: Int, month: Int): Set { + override fun getSymptomDatesForMonth(year: Int, month: Int): Set { val dates = mutableSetOf() val db = readableDatabase @@ -250,7 +258,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to update symptom dates in the database - fun updateSymptomDate(dates: List, symptomId: List) { + override fun updateSymptomDate(dates: List, symptomId: List) { val db = writableDatabase // Convert dates to strings for database operations @@ -261,7 +269,11 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS // Delete existing symptoms for the specified dates db.execSQL( """ - DELETE FROM $TABLE_SYMPTOM_DATE WHERE $COLUMN_SYMPTOM_DATE IN (${dateStrings.joinToString(",") { "?" }}) + DELETE FROM $TABLE_SYMPTOM_DATE WHERE $COLUMN_SYMPTOM_DATE IN (${ + dateStrings.joinToString( + "," + ) { "?" } + }) """, dateStrings.toTypedArray() ) @@ -291,7 +303,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to get symptoms for a given date - fun getSymptomsFromDate(date: LocalDate): List { + override fun getSymptomsFromDate(date: LocalDate): List { val db = readableDatabase val symptoms = mutableListOf() @@ -316,7 +328,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS return symptoms } - fun getSymptomColorForDate(date: LocalDate): List { + override fun getSymptomColorForDate(date: LocalDate): List { val db = readableDatabase val query = """ SELECT s.color @@ -343,7 +355,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to get all settings from the database - fun getAllSettings(): List { + override fun getAllSettings(): List { val settings = mutableListOf() val db = readableDatabase val cursor = db.query(TABLE_APP_SETTINGS, null, null, null, null, null, null) @@ -361,18 +373,19 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used for updating settings in the database - fun updateSetting(key: String, value: String): Boolean { + override fun updateSetting(key: String, value: String): Boolean { val db = this.writableDatabase val contentValues = ContentValues().apply { put(COLUMN_SETTING_VALUE, value) } - val rowsUpdated = db.update(TABLE_APP_SETTINGS, contentValues, "$COLUMN_SETTING_KEY = ?", arrayOf(key)) + val rowsUpdated = + db.update(TABLE_APP_SETTINGS, contentValues, "$COLUMN_SETTING_KEY = ?", arrayOf(key)) db.close() return rowsUpdated > 0 } //This function is used to get a setting from the database - fun getSettingByKey(key: String): Setting? { + override fun getSettingByKey(key: String): Setting? { val db = readableDatabase val cursor = db.query( TABLE_APP_SETTINGS, @@ -400,7 +413,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used for adding/removing ovulation dates from the database - fun updateOvulationDate(date: LocalDate) { + override fun updateOvulationDate(date: LocalDate) { val db = writableDatabase // Convert LocalDate to String for SQLite compatibility @@ -425,7 +438,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to get ovulation date for a given month - fun getOvulationDatesForMonth(year: Int, month: Int): Set { + override fun getOvulationDatesForMonth(year: Int, month: Int): Set { val dates = mutableSetOf() val db = readableDatabase @@ -467,7 +480,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to get the number of ovulations in the database - fun getOvulationCount(): Int { + override fun getOvulationCount(): Int { val db = readableDatabase val countQuery = "SELECT COUNT(DISTINCT DATE) FROM OVULATIONS" val cursor = db.rawQuery(countQuery, null) @@ -482,7 +495,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS //This function checks if date input should be included in existing period //or if a new periodId should be created - fun newFindOrCreatePeriodID(date: LocalDate): Int { + override fun newFindOrCreatePeriodID(date: LocalDate): Int { val db = readableDatabase val dateStr = date.toString() var periodId = 1 @@ -504,7 +517,8 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS // Merge periods val updateQuery = "UPDATE PERIODS SET PERIOD_ID = ? WHERE PERIOD_ID = ?" - val updateArgs = arrayOf(primaryPeriodID.toString(), secondaryPeriodID.toString()) + val updateArgs = + arrayOf(primaryPeriodID.toString(), secondaryPeriodID.toString()) db.execSQL(updateQuery, updateArgs) periodId = primaryPeriodID @@ -515,9 +529,11 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS //Log.d(TAG, "Found existing periodId $periodId for date $date") } else { // Create a new period ID - val maxPeriodIdCursor = db.rawQuery("SELECT MAX($COLUMN_PERIOD_ID) FROM $TABLE_PERIODS", null) + val maxPeriodIdCursor = + db.rawQuery("SELECT MAX($COLUMN_PERIOD_ID) FROM $TABLE_PERIODS", null) if (maxPeriodIdCursor.moveToFirst()) { - val maxPeriodId = if (maxPeriodIdCursor.moveToFirst()) maxPeriodIdCursor.getInt(0) else 0 + val maxPeriodId = + if (maxPeriodIdCursor.moveToFirst()) maxPeriodIdCursor.getInt(0) else 0 periodId = maxPeriodId + 1 //Log.d(TAG, "No existing periodId found for date $date. Created new periodId $periodId") } @@ -535,7 +551,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to get the previous period start date from given input date - fun getFirstPreviousPeriodDate(date: LocalDate): LocalDate? { + override fun getFirstPreviousPeriodDate(date: LocalDate): LocalDate? { val db = readableDatabase var firstLatestDate: LocalDate? = null @@ -556,7 +572,8 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS val cursor = db.rawQuery(query, arrayOf(date.toString())) if (cursor.moveToFirst()) { - firstLatestDate = LocalDate.parse(cursor.getString(cursor.getColumnIndexOrThrow("date"))) + firstLatestDate = + LocalDate.parse(cursor.getString(cursor.getColumnIndexOrThrow("date"))) } cursor.close() @@ -566,7 +583,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is to get the oldest period date in the database - fun getOldestPeriodDate(): LocalDate?{ + override fun getOldestPeriodDate(): LocalDate? { val db = readableDatabase var oldestPeriodDate: LocalDate? = null @@ -585,7 +602,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is to get the oldest period date in the database - fun getNewestOvulationDate(): LocalDate?{ + override fun getNewestOvulationDate(): LocalDate? { val db = readableDatabase var newestOvulationDate: LocalDate? = null @@ -605,11 +622,12 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS //This function is used for updating symptom active status - fun updateSymptom(id: Int, active: Int, color: String) { + override fun updateSymptom(id: Int, active: Int, color: String) { val db = writableDatabase // Ensure that active is either 0 or 1 - val newActiveStatus = if (active in 0..1) active else throw IllegalArgumentException("Active status must be 0 or 1") + val newActiveStatus = + if (active in 0..1) active else throw IllegalArgumentException("Active status must be 0 or 1") val contentValues = ContentValues().apply { put("active", newActiveStatus) @@ -633,7 +651,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS // This function is used to get the latest X ovulation dates where they are followed by a period // Used for calculations - fun getLatestXOvulationsWithPeriod(number: Int): List { + override fun getLatestXOvulationsWithPeriod(number: Int): List { val ovulationDates = mutableListOf() val db = readableDatabase // only include ovulations that has a period coming afterwards @@ -661,7 +679,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS return ovulationDates } - fun getLastOvulation(): LocalDate? { + override fun getLastOvulation(): LocalDate? { val db = readableDatabase val query = """ SELECT DATE FROM OVULATIONS ORDER BY DATE DESC LIMIT 1 @@ -680,7 +698,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS return ovulationDate } - fun getLatestXPeriodStart(number: Int): List { + override fun getLatestXPeriodStart(number: Int): List { val dateList = mutableListOf() val db = readableDatabase val numberOfPeriods = number + 1 // Include the current ongoing period if applicable @@ -714,7 +732,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS return dateList } - fun getFirstNextPeriodDate(date: LocalDate): LocalDate? { + override fun getFirstNextPeriodDate(date: LocalDate): LocalDate? { val db = readableDatabase var firstNextDate: LocalDate? = null @@ -739,7 +757,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS return firstNextDate } - fun getNoOfDatesInPeriod(date: LocalDate): Int { + override fun getNoOfDatesInPeriod(date: LocalDate): Int { val db = readableDatabase val query = """ SELECT COUNT(DATE) FROM PERIODS WHERE period_id in (SELECT period_id FROM PERIODS WHERE date = ?) @@ -755,7 +773,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS return count } - fun getXLatestOvulationsDates(number: Int): List { + override fun getXLatestOvulationsDates(number: Int): List { val db = readableDatabase val ovulationDates = mutableListOf() val query = """ @@ -767,7 +785,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS val dateString = cursor.getString(0) val date = LocalDate.parse(dateString) ovulationDates.add(date) - }while (cursor.moveToNext()) + } while (cursor.moveToNext()) } cursor.close() db.close() @@ -775,7 +793,7 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } //This function is used to remove a date from the periods table - fun deleteSymptom(symptomId: Int) { + override fun deleteSymptom(symptomId: Int) { val db = writableDatabase val whereClause = "id = ?" val whereArgs = arrayOf(symptomId.toString()) @@ -788,11 +806,11 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS db.close() } - fun getDBVersion(): String { + override fun getDBVersion(): String { return DATABASE_VERSION.toString() } - fun renameSymptom(symptomId: Int, newName: String) { + override fun renameSymptom(symptomId: Int, newName: String) { val db = writableDatabase val contentValues = ContentValues().apply { put("symptom_name", newName) @@ -801,10 +819,11 @@ class PeriodDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABAS } - fun getLatestPeriodStart(): LocalDate { + override fun getLatestPeriodStart(): LocalDate { val latestPeriodStart = LocalDate.parse("1900-01-01") val db = readableDatabase - val query = "SELECT date FROM periods where period_id = (SELECT MAX(period_id) FROM periods) ORDER BY date asc LIMIT 1" + val query = + "SELECT date FROM periods where period_id = (SELECT MAX(period_id) FROM periods) ORDER BY date asc LIMIT 1" val cursor = db.rawQuery(query, null) if (cursor.moveToFirst()) { val dateString = cursor.getString(cursor.getColumnIndexOrThrow("date")) diff --git a/app/src/main/java/com/mensinator/app/PeriodPrediction.kt b/app/src/main/java/com/mensinator/app/PeriodPrediction.kt index 467530c..e959247 100644 --- a/app/src/main/java/com/mensinator/app/PeriodPrediction.kt +++ b/app/src/main/java/com/mensinator/app/PeriodPrediction.kt @@ -1,24 +1,18 @@ package com.mensinator.app -import android.content.Context import java.time.LocalDate -class PeriodPrediction(context: Context) : Prediction(context) { - private lateinit var periodDatePrediction: LocalDate +class PeriodPrediction( + private val dbHelper: IPeriodDatabaseHelper, + private val calcHelper: ICalculationsHelper, +) : IPeriodPrediction { - fun getPredictedPeriodDate() : LocalDate{ - - if(periodCount>=2){ - periodDatePrediction = calcHelper.calculateNextPeriod() - } - else{ - periodDatePrediction = LocalDate.parse("1900-01-01") + override fun getPredictedPeriodDate(): LocalDate { + val periodCount = dbHelper.getPeriodCount() + return if (periodCount >= 2) { + calcHelper.calculateNextPeriod() + } else { + LocalDate.parse("1900-01-01") } - return periodDatePrediction } - -// fun getPredictedPeriodDate(date: Date): LocalDate { -// return periodDatePrediction -// } - } \ No newline at end of file diff --git a/app/src/main/java/com/mensinator/app/Prediction.kt b/app/src/main/java/com/mensinator/app/Prediction.kt deleted file mode 100644 index c47971e..0000000 --- a/app/src/main/java/com/mensinator/app/Prediction.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.mensinator.app - -import android.content.Context - -open class Prediction(context: Context) { - - val dbHelper = PeriodDatabaseHelper(context) - val calcHelper = Calculations(context) - val periodCount = dbHelper.getPeriodCount() - -} \ No newline at end of file diff --git a/app/src/main/java/com/mensinator/app/SettingsScreen.kt b/app/src/main/java/com/mensinator/app/SettingsScreen.kt index 04515c8..2e269fb 100644 --- a/app/src/main/java/com/mensinator/app/SettingsScreen.kt +++ b/app/src/main/java/com/mensinator/app/SettingsScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.sp import androidx.core.app.NotificationManagerCompat import com.mensinator.app.data.DataSource import com.mensinator.app.ui.theme.isDarkMode +import org.koin.compose.koinInject //Maps Database keys to res/strings.xml for multilanguage support object ResourceMapper { @@ -80,7 +81,8 @@ fun SettingsScreen(onSwitchProtectionScreen: (Boolean) -> Unit) { Log.d("SettingsDialog", "SettingsDialog recomposed") val context = LocalContext.current - val dbHelper = remember { PeriodDatabaseHelper(context) } + val dbHelper: IPeriodDatabaseHelper = koinInject() + val exportImport: IExportImport = koinInject() // Fetch current settings from the database val settings by remember { mutableStateOf(dbHelper.getAllSettings()) } @@ -481,9 +483,10 @@ fun SettingsScreen(onSwitchProtectionScreen: (Boolean) -> Unit) { // Showing the ExportImportDialog when the user triggers it if (exportImportDialog) { ExportDialog( + exportImport = exportImport, onDismissRequest = { exportImportDialog = false }, onExportClick = { exportPath -> - handleExport(context, exportPath) + handleExport(context, exportImport, exportPath) } ) } @@ -491,9 +494,10 @@ fun SettingsScreen(onSwitchProtectionScreen: (Boolean) -> Unit) { // Showing the ExportImportDialog when the user triggers it if (showImportDialog) { ImportDialog( + exportImport = exportImport, onDismissRequest = { showImportDialog = false }, onImportClick = { importPath -> - handleImport(context, importPath) + handleImport(context, exportImport, importPath) } ) } @@ -503,10 +507,9 @@ fun SettingsScreen(onSwitchProtectionScreen: (Boolean) -> Unit) { } } -fun handleExport(context: Context, exportPath: String) { +fun handleExport(context: Context, exportImport: IExportImport, exportPath: String) { try { - val exportImport = ExportImport() - exportImport.exportDatabase(context, exportPath) + exportImport.exportDatabase(exportPath) Toast.makeText(context, "Data exported successfully to $exportPath", Toast.LENGTH_SHORT) .show() } catch (e: Exception) { @@ -515,23 +518,21 @@ fun handleExport(context: Context, exportPath: String) { } } -fun handleImport(context: Context, importPath: String) { +fun handleImport(context: Context, exportImport: IExportImport, importPath: String) { try { - val exportImport = ExportImport() - exportImport.importDatabase(context, importPath) + exportImport.importDatabase(importPath) Toast.makeText( context, "Data imported successfully from $importPath", Toast.LENGTH_SHORT - ) - .show() + ).show() } catch (e: Exception) { Toast.makeText(context, "Error during import: ${e.message}", Toast.LENGTH_SHORT).show() Log.e("Import", "Import error: ${e.message}", e) } } -fun saveData(savedSetting: List, dbHelper: PeriodDatabaseHelper, context: Context) { +fun saveData(savedSetting: List, dbHelper: IPeriodDatabaseHelper, context: Context) { Log.d("SettingsDialog", "Save button clicked") savedSetting.forEach { setting -> dbHelper.updateSetting(setting.key, setting.value) diff --git a/app/src/main/java/com/mensinator/app/StatisticsScreen.kt b/app/src/main/java/com/mensinator/app/StatisticsScreen.kt index 4a322da..03a3723 100644 --- a/app/src/main/java/com/mensinator/app/StatisticsScreen.kt +++ b/app/src/main/java/com/mensinator/app/StatisticsScreen.kt @@ -1,45 +1,33 @@ package com.mensinator.app -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.displayCutoutPadding -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import java.time.LocalDate @Composable -fun StatisticsScreen( -) { - val context = LocalContext.current - val dbHelper = remember { PeriodDatabaseHelper(context) } - val calcHelper = remember { Calculations(context) } +fun StatisticsScreen() { + val dbHelper: IPeriodDatabaseHelper = koinInject() + val calcHelper: ICalculationsHelper = koinInject() + val ovulationPrediction: IOvulationPrediction = koinInject() + val periodPrediction: IPeriodPrediction = koinInject() + val averageCycleLength = calcHelper.averageCycleLength() val periodCount = dbHelper.getPeriodCount() val ovulationCount = dbHelper.getOvulationCount() val averagePeriodLength = calcHelper.averagePeriodLength() val avgLutealLength = calcHelper.averageLutealLength() val follicleGrowthDays = calcHelper.averageFollicalGrowthInDays() - - val ovulationPrediction = OvulationPrediction(context) val ovulationPredictionDate = ovulationPrediction.getPredictedOvulationDate() - - val periodPrediction = PeriodPrediction(context) val periodPredictionDate = periodPrediction.getPredictedPeriodDate() val scrollState = rememberScrollState() @@ -102,7 +90,6 @@ fun StatisticsScreen( stringResource(id = R.string.average_luteal_length), (Math.round(avgLutealLength * 10) / 10.0).toString() + " " + stringResource(id = R.string.days) ) - } } diff --git a/app/src/main/java/com/mensinator/app/SymptomDialogs.kt b/app/src/main/java/com/mensinator/app/SymptomDialogs.kt index 889c52f..6caaf00 100644 --- a/app/src/main/java/com/mensinator/app/SymptomDialogs.kt +++ b/app/src/main/java/com/mensinator/app/SymptomDialogs.kt @@ -8,19 +8,19 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment 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 import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.mensinator.app.ui.theme.MensinatorTheme +import java.time.LocalDate @Composable fun SymptomsDialog( date: LocalDate, symptoms: List, - dbHelper: PeriodDatabaseHelper, + dbHelper: IPeriodDatabaseHelper, onSave: (List) -> Unit, onCancel: () -> Unit, modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/mensinator/app/navigation/BottomBar.kt b/app/src/main/java/com/mensinator/app/navigation/BottomBar.kt index 44123ee..840b49f 100644 --- a/app/src/main/java/com/mensinator/app/navigation/BottomBar.kt +++ b/app/src/main/java/com/mensinator/app/navigation/BottomBar.kt @@ -10,22 +10,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp @@ -35,12 +24,9 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.mensinator.app.CalendarScreen -import com.mensinator.app.ManageSymptomScreen -import com.mensinator.app.PeriodDatabaseHelper +import com.mensinator.app.* import com.mensinator.app.R -import com.mensinator.app.SettingsScreen -import com.mensinator.app.StatisticsScreen +import org.koin.compose.koinInject enum class Screens { Calendar, @@ -54,9 +40,7 @@ fun MensinatorBottomBar( navController: NavHostController = rememberNavController(), onScreenProtectionChanged: (Boolean) -> Unit?, ) { - val context = LocalContext.current - // For accessing database functions - val dbHelper = remember { PeriodDatabaseHelper(context) } + val dbHelper: IPeriodDatabaseHelper = koinInject() // If protectScreen is 1, it should protect the screen // If protectScreen is 0, should not protect screen(allows prints and screen visibility in recent apps) val protectScreen = dbHelper.getSettingByKey("screen_protection")?.value?.toIntOrNull() ?: 1 diff --git a/app/src/test/java/com/mensinator/app/PeriodPredictionTest.kt b/app/src/test/java/com/mensinator/app/PeriodPredictionTest.kt new file mode 100644 index 0000000..2df8e3d --- /dev/null +++ b/app/src/test/java/com/mensinator/app/PeriodPredictionTest.kt @@ -0,0 +1,35 @@ +package com.mensinator.app + +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import junit.framework.TestCase.assertEquals +import org.junit.Before +import org.junit.Test +import java.time.LocalDate + + +class PeriodPredictionTest { + + @MockK(relaxed = true) + private lateinit var dbHelper: IPeriodDatabaseHelper + + @MockK + private lateinit var calcHelper: ICalculationsHelper + + private lateinit var periodPrediction: IPeriodPrediction + + @Before + fun setup() { + MockKAnnotations.init(this) + } + + @Test + fun getPredictedPeriodDate_onlyOnePeriodEntered_fallbackDate() { + every { dbHelper.getPeriodCount() } returns 1 + + periodPrediction = PeriodPrediction(dbHelper, calcHelper) + + assertEquals(LocalDate.parse("1900-01-01"), periodPrediction.getPredictedPeriodDate()) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc6d274..ba96082 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,8 @@ composeBom = "2024.10.01" window = "1.3.0" appcompat = "1.7.0" navigation = "2.8.3" +koinBom = "4.0.0" +mockk = "1.13.13" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core" } @@ -32,6 +34,11 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "navigation" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } androidx-navigation-common-ktx = { group = "androidx.navigation", name = "navigation-common-ktx", version.ref = "navigation" } +koin = { group = "io.insert-koin", name = "koin-android" } +koin-compose = { group = "io.insert-koin", name = "koin-androidx-compose" } +koin-bom = { group = "io.insert-koin", name = "koin-bom", version.ref = "koinBom" } +mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 871ed8bec1a3fa25cbc95c65d30b19f97473b439 Mon Sep 17 00:00:00 2001 From: Carsten Hagemann Date: Sat, 9 Nov 2024 22:13:27 +0100 Subject: [PATCH 2/2] Move over comments --- ...iodDatabase.kt => IPeriodDatabaseHelper.kt} | 17 +++++++++++++++++ .../com/mensinator/app/PeriodDatabaseHelper.kt | 18 +----------------- 2 files changed, 18 insertions(+), 17 deletions(-) rename app/src/main/java/com/mensinator/app/{PeriodDatabase.kt => IPeriodDatabaseHelper.kt} (71%) diff --git a/app/src/main/java/com/mensinator/app/PeriodDatabase.kt b/app/src/main/java/com/mensinator/app/IPeriodDatabaseHelper.kt similarity index 71% rename from app/src/main/java/com/mensinator/app/PeriodDatabase.kt rename to app/src/main/java/com/mensinator/app/IPeriodDatabaseHelper.kt index e5d6163..602a9c0 100644 --- a/app/src/main/java/com/mensinator/app/PeriodDatabase.kt +++ b/app/src/main/java/com/mensinator/app/IPeriodDatabaseHelper.kt @@ -13,38 +13,55 @@ interface IPeriodDatabaseHelper { // Refactor this soon! val writableDb: SQLiteDatabase + // This function is used to add a date together with a period id to the periods table fun addDateToPeriod(date: LocalDate, periodId: Int) + // Get all period dates for a given month fun getPeriodDatesForMonth(year: Int, month: Int): Map + // Returns how many periods that are in the database fun getPeriodCount(): Int + // This function is used to remove a date from the periods table fun removeDateFromPeriod(date: LocalDate) + // This function is used to get all symptoms from the database fun getAllSymptoms(): List + // This function inserts new symptom into the Database fun createNewSymptom(symptomName: String) + // This function returns all Symptom dates for given month fun getSymptomDatesForMonth(year: Int, month: Int): Set + // This function is used to update symptom dates in the database fun updateSymptomDate(dates: List, symptomId: List) + // This function is used to get symptoms for a given date fun getSymptomsFromDate(date: LocalDate): List fun getSymptomColorForDate(date: LocalDate): List + // This function is used to get all settings from the database fun getAllSettings(): List + // This function is used for updating settings in the database fun updateSetting(key: String, value: String): Boolean + // This function is used to get a setting from the database fun getSettingByKey(key: String): Setting? + // This function is used for adding/removing ovulation dates from the database fun updateOvulationDate(date: LocalDate) + // This function is used to get ovulation date for a given month fun getOvulationDatesForMonth(year: Int, month: Int): Set + // This function is used to get the number of ovulations in the database fun getOvulationCount(): Int + // This function checks if date input should be included in existing period + // or if a new periodId should be created fun newFindOrCreatePeriodID(date: LocalDate): Int // Retrieve the previous period's start date from a given date diff --git a/app/src/main/java/com/mensinator/app/PeriodDatabaseHelper.kt b/app/src/main/java/com/mensinator/app/PeriodDatabaseHelper.kt index d5e0825..2f64b6a 100644 --- a/app/src/main/java/com/mensinator/app/PeriodDatabaseHelper.kt +++ b/app/src/main/java/com/mensinator/app/PeriodDatabaseHelper.kt @@ -84,7 +84,6 @@ class PeriodDatabaseHelper(context: Context) : } } - //This function is used to add a date together with a period id to the periods table override fun addDateToPeriod(date: LocalDate, periodId: Int) { val db = writableDatabase val values = ContentValues().apply { @@ -100,7 +99,6 @@ class PeriodDatabaseHelper(context: Context) : db.close() } - //Get all period dates for a given month override fun getPeriodDatesForMonth(year: Int, month: Int): Map { val dates = mutableMapOf() val db = readableDatabase @@ -144,7 +142,6 @@ class PeriodDatabaseHelper(context: Context) : return dates } - //Returns how many periods that are in the database override fun getPeriodCount(): Int { val db = readableDatabase val countQuery = "SELECT COUNT(DISTINCT $COLUMN_PERIOD_ID) FROM $TABLE_PERIODS" @@ -158,7 +155,6 @@ class PeriodDatabaseHelper(context: Context) : return count } - //This function is used to remove a date from the periods table override fun removeDateFromPeriod(date: LocalDate) { val db = writableDatabase val whereClause = "$COLUMN_DATE = ?" @@ -172,7 +168,6 @@ class PeriodDatabaseHelper(context: Context) : db.close() } - //This function is used to get all symptoms from the database override fun getAllSymptoms(): List { val db = readableDatabase val symptoms = mutableListOf() @@ -199,7 +194,6 @@ class PeriodDatabaseHelper(context: Context) : return symptoms } - //This function inserts new symptom into the Database override fun createNewSymptom(symptomName: String) { val db = writableDatabase val values = ContentValues().apply { @@ -211,7 +205,6 @@ class PeriodDatabaseHelper(context: Context) : db.close() // Close the database connection to free up resources } - //This function returns all Symptom dates for given month override fun getSymptomDatesForMonth(year: Int, month: Int): Set { val dates = mutableSetOf() val db = readableDatabase @@ -257,7 +250,6 @@ class PeriodDatabaseHelper(context: Context) : return dates } - //This function is used to update symptom dates in the database override fun updateSymptomDate(dates: List, symptomId: List) { val db = writableDatabase @@ -302,7 +294,6 @@ class PeriodDatabaseHelper(context: Context) : db.close() } - //This function is used to get symptoms for a given date override fun getSymptomsFromDate(date: LocalDate): List { val db = readableDatabase val symptoms = mutableListOf() @@ -354,7 +345,6 @@ class PeriodDatabaseHelper(context: Context) : return symptomColors } - //This function is used to get all settings from the database override fun getAllSettings(): List { val settings = mutableListOf() val db = readableDatabase @@ -372,7 +362,6 @@ class PeriodDatabaseHelper(context: Context) : return settings } - //This function is used for updating settings in the database override fun updateSetting(key: String, value: String): Boolean { val db = this.writableDatabase val contentValues = ContentValues().apply { @@ -384,7 +373,6 @@ class PeriodDatabaseHelper(context: Context) : return rowsUpdated > 0 } - //This function is used to get a setting from the database override fun getSettingByKey(key: String): Setting? { val db = readableDatabase val cursor = db.query( @@ -412,7 +400,6 @@ class PeriodDatabaseHelper(context: Context) : return setting } - //This function is used for adding/removing ovulation dates from the database override fun updateOvulationDate(date: LocalDate) { val db = writableDatabase @@ -437,7 +424,6 @@ class PeriodDatabaseHelper(context: Context) : db.close() } - //This function is used to get ovulation date for a given month override fun getOvulationDatesForMonth(year: Int, month: Int): Set { val dates = mutableSetOf() val db = readableDatabase @@ -479,7 +465,6 @@ class PeriodDatabaseHelper(context: Context) : return dates } - //This function is used to get the number of ovulations in the database override fun getOvulationCount(): Int { val db = readableDatabase val countQuery = "SELECT COUNT(DISTINCT DATE) FROM OVULATIONS" @@ -493,8 +478,7 @@ class PeriodDatabaseHelper(context: Context) : return count } - //This function checks if date input should be included in existing period - //or if a new periodId should be created + override fun newFindOrCreatePeriodID(date: LocalDate): Int { val db = readableDatabase val dateStr = date.toString()