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

Introduce koin, add mockk for unit tests #180

Merged
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
8 changes: 8 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
32 changes: 32 additions & 0 deletions app/src/main/java/com/mensinator/app/App.kt
Original file line number Diff line number Diff line change
@@ -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<IPeriodDatabaseHelper>() }
singleOf(::CalculationsHelper) { bind<ICalculationsHelper>() }
singleOf(::OvulationPrediction) { bind<IOvulationPrediction>() }
singleOf(::PeriodPrediction) { bind<IPeriodPrediction>() }
singleOf(::ExportImport) { bind<IExportImport>() }
singleOf(::NotificationScheduler) { bind<INotificationScheduler>() }
}

override fun onCreate() {
super.onCreate()

startKoin {
androidLogger()
androidContext(this@App)
modules(appModule)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Long>()
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
Expand All @@ -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
Expand Down Expand Up @@ -94,31 +82,26 @@ 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()
val expectedOvulation = periodDates.last().plusDays(avgGrowthRate.toInt().toLong())
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
return expectedPeriodDate
}
}

//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
Expand All @@ -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()) {
Expand All @@ -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
Expand All @@ -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)
}

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

// Retrieve the start dates of the latest periods
Expand All @@ -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<Long>()

Expand All @@ -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
}
Expand All @@ -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
}

Expand Down
Loading