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

Refactor Calendar screen to use vertical scrolling calendar #224

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0826b49
Squash commits
EmmaTellblom Jan 15, 2025
0609196
Add kotlinx.collections.immutable and use it across the CalendarScreen
carstenhag Jan 30, 2025
8071724
Introduce CalendarViewModel, refactor calendar screens
carstenhag Feb 2, 2025
80d70cc
Provide colors in VM, cleanup CalendarScreen
carstenhag Feb 14, 2025
c609f63
Fix clickable ripple bounds
carstenhag Feb 14, 2025
2c1c9ac
Provide background+text color combinations for calendar screen
carstenhag Feb 14, 2025
fd0fb27
Fix linter warning
carstenhag Feb 14, 2025
6a8ce3c
Remove unnecessary views & vertical scroll
carstenhag Feb 17, 2025
446856c
Add library
carstenhag Feb 17, 2025
d72de2b
Refactor db methods to be suspendable/run on Dispatchers.IO, refactor…
carstenhag Feb 17, 2025
4fbcc49
Reduce calendar to button margin
carstenhag Feb 17, 2025
3e0a2d1
solve linter warnings
carstenhag Feb 17, 2025
917a011
Improve gitignore
carstenhag Feb 17, 2025
aa42e64
Delete build files
carstenhag Feb 17, 2025
b800c46
Cleanup screen protection
carstenhag Feb 17, 2025
124983f
Improve padding on calendar screen and add border around symptom circles
carstenhag Feb 17, 2025
30a138f
Fetch data for 2 surrounding months to avoid UI glitches
carstenhag Feb 17, 2025
7d12829
Resolve TODO
carstenhag Feb 17, 2025
a42f5cb
Make calculation result nullable, resolve todos
carstenhag Feb 17, 2025
cf619a0
Fix test
carstenhag Feb 17, 2025
16b87f8
Implement deselection logic
carstenhag Feb 18, 2025
9b25032
Only use new symptom field, fixing bug
carstenhag Feb 18, 2025
ec9984d
Improve cycle number text color
carstenhag Feb 18, 2025
becdf2d
Improve current day border on dark mode
carstenhag Feb 18, 2025
30f570b
Implement tap on "Calendar" to jump to current month
carstenhag Feb 21, 2025
8929db9
Display calendar buttons in FlowRow, change Day aspectRatio for lands…
carstenhag Feb 21, 2025
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
3 changes: 3 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ output.json

# Kotlin Multiplatform specific
cinterop/


release/
13 changes: 13 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ android {
lint {
sarifReport = true
}
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
metricsDestination = layout.buildDirectory.dir("compose_compiler")
}
}

dependencies {
Expand All @@ -74,16 +78,22 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.material3.adaptive)
implementation(libs.androidx.material3.adaptive.layout)
implementation(libs.androidx.window)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.navigation.runtime.ktx)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.navigation.common.ktx)

implementation(libs.kizitonwose.calendar.compose)

implementation(platform(libs.koin.bom))
implementation(libs.koin)
implementation(libs.koin.compose)

implementation(libs.kotlinx.collections.immutable)

testImplementation(libs.junit)
testImplementation(libs.mockk)

Expand All @@ -94,4 +104,7 @@ dependencies {

debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)

// To be used to profile performance. Don't include in release builds
// implementation("androidx.compose.runtime:runtime-tracing")
}
Binary file removed app/release/baselineProfiles/0/app-release.dm
Binary file not shown.
Binary file removed app/release/baselineProfiles/1/app-release.dm
Binary file not shown.
37 changes: 0 additions & 37 deletions app/release/output-metadata.json

This file was deleted.

2 changes: 2 additions & 0 deletions app/src/main/java/com/mensinator/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.mensinator.app

import android.app.Application
import com.mensinator.app.business.*
import com.mensinator.app.calendar.CalendarViewModel
import com.mensinator.app.settings.SettingsViewModel
import com.mensinator.app.statistics.StatisticsViewModel
import com.mensinator.app.symptoms.ManageSymptomsViewModel
Expand All @@ -24,6 +25,7 @@ class App : Application() {
singleOf(::ExportImport) { bind<IExportImport>() }
singleOf(::NotificationScheduler) { bind<INotificationScheduler>() }

viewModel { CalendarViewModel(get(), get(), get(), get()) }
viewModel { ManageSymptomsViewModel(get()) }
viewModel { SettingsViewModel(get(), get(), get()) }
viewModel { StatisticsViewModel(get(), get(), get(), get(), get()) }
Expand Down
30 changes: 17 additions & 13 deletions app/src/main/java/com/mensinator/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand All @@ -20,19 +21,7 @@ class MainActivity : AppCompatActivity() {
setContent {
MensinatorTheme {
KoinAndroidContext {
MensinatorApp { isScreenProtectionEnabled ->
// Sets the flags for screen protection if
// isScreenProtectionEnabled == true
// If isScreenProtectionEnabled == false it removes the flags
if (isScreenProtectionEnabled) {
window?.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
MensinatorApp(onScreenProtectionChanged = ::handleScreenProtection)
}
}
}
Expand All @@ -51,4 +40,19 @@ class MainActivity : AppCompatActivity() {
val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
}

private fun handleScreenProtection(isScreenProtectionEnabled: Boolean) {
Log.d("screenProtectionUI", "protect screen value $isScreenProtectionEnabled")
// Sets the flags for screen protection if
// isScreenProtectionEnabled == true
// If isScreenProtectionEnabled == false it removes the flags
if (isScreenProtectionEnabled) {
window?.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
}
36 changes: 21 additions & 15 deletions app/src/main/java/com/mensinator/app/business/CalculationsHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,30 @@ package com.mensinator.app.business

import android.util.Log
import com.mensinator.app.extensions.roundToTwoDecimalPoints
import kotlinx.coroutines.runBlocking
import java.time.LocalDate

class CalculationsHelper(
private val dbHelper: IPeriodDatabaseHelper,
) : ICalculationsHelper {
private val periodHistory
get() = dbHelper.getSettingByKey("period_history")?.value?.toIntOrNull() ?: 5
get() = runBlocking {
dbHelper.getSettingByKey("period_history")?.value?.toIntOrNull() ?: 5
}
private val ovulationHistory
get() = dbHelper.getSettingByKey("ovulation_history")?.value?.toIntOrNull() ?: 5
get() = runBlocking {
dbHelper.getSettingByKey("ovulation_history")?.value?.toIntOrNull() ?: 5
}
private val lutealCalculation
get() = dbHelper.getSettingByKey("luteal_period_calculation")?.value?.toIntOrNull() ?: 0

override fun calculateNextPeriod(): LocalDate {
val expectedPeriodDate: LocalDate
get() = runBlocking {
dbHelper.getSettingByKey("luteal_period_calculation")?.value?.toIntOrNull() ?: 0
}

if (lutealCalculation == 1) {
override fun calculateNextPeriod(): LocalDate? {
return if (lutealCalculation == 1) {
//go to advanced calculation using X latest ovulations (set by user in settings)
Log.d("TAG", "Advanced calculation")
expectedPeriodDate = advancedNextPeriod()
advancedNextPeriod()
} else {
// Basic calculation using latest period start dates
Log.d("TAG", "Basic calculation")
Expand All @@ -29,7 +34,7 @@ class CalculationsHelper(
val listPeriodDates = dbHelper.getLatestXPeriodStart(periodHistory)

if (listPeriodDates.isEmpty()) {
expectedPeriodDate = LocalDate.parse("1900-01-01")
null
} else {
// Calculate the cycle lengths between consecutive periods
val cycleLengths = mutableListOf<Long>()
Expand All @@ -42,26 +47,27 @@ class CalculationsHelper(
}
// Calculate the average cycle length
val averageLength = cycleLengths.average()
expectedPeriodDate = listPeriodDates.last().plusDays(averageLength.toLong())

listPeriodDates.last().plusDays(averageLength.toLong())
}.also {
Log.d("TAG", "Expected period date Basic: $it")
}
}
Log.d("TAG", "Expected period date Basic: $expectedPeriodDate")
return expectedPeriodDate
}

/**
* Advanced calculation for the next expected period date using ovulation data.
*
* @return The next expected period date.
*/
private fun advancedNextPeriod(): LocalDate {
private fun advancedNextPeriod(): LocalDate? {
// Get the list of the latest ovulation dates
val ovulationDates = dbHelper.getLatestXOvulationsWithPeriod(ovulationHistory)
//Log.d("TAG", "Ovulation dates: $ovulationDates")
if (ovulationDates.isEmpty()) {
// Return null or handle the case where no ovulations are available
Log.d("TAG", "Ovulationdates are empty")
return LocalDate.parse("1900-01-01")
return null
}

var lutealLength = 0
Expand All @@ -83,7 +89,7 @@ class CalculationsHelper(
if (lastOvulation == null) {
// Return null or handle the case where no last ovulation date is available
Log.d("TAG", "Ovulation is null")
return LocalDate.parse("1900-01-01")
return null
}
val periodDates =
dbHelper.getLatestXPeriodStart(ovulationHistory) //This always returns no+1 period dates
Expand Down
14 changes: 13 additions & 1 deletion app/src/main/java/com/mensinator/app/business/DatabaseUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ object DatabaseUtils {
databaseVersion7(db)
databaseVersion8(db)
databaseVersion9(db)
databaseVersion10(db)
}

fun createAppSettingsGroup(db: SQLiteDatabase) {
Expand Down Expand Up @@ -84,11 +85,11 @@ object DatabaseUtils {
INSERT INTO app_settings (setting_key, setting_label, setting_value, group_label_id) VALUES
('period_color', 'Period Color', 'Red', 1),
('selection_color', 'Selection Color', 'LightGray', 1),
('period_selection_color', 'Period Selection Color', 'DarkGray', 1),
('expected_period_color', 'Expected Period Color', 'Yellow', 1),
('reminder_days', 'Days Before Reminder', '0', 2),
('luteal_period_calculation', 'Luteal Phase Calculation', '0', 3)
""")
// ('period_selection_color', 'Period Selection Color', 'DarkGray', 1), - deprecated
}

fun createOvulationStructure(db: SQLiteDatabase){
Expand Down Expand Up @@ -192,4 +193,15 @@ object DatabaseUtils {
"""
)
}

fun databaseVersion10(db: SQLiteDatabase) {
// Remove old setting for period_selection_color
db.execSQL(
"""
DELETE FROM app_settings WHERE setting_key = 'period_selection_color';
"""
)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface ICalculationsHelper {
*
* @return The next expected period date.
*/
fun calculateNextPeriod(): LocalDate
fun calculateNextPeriod(): LocalDate?

/**
* Calculates the average number of days from the first day of the last period to ovulation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package com.mensinator.app.business
import java.time.LocalDate

interface IOvulationPrediction {
fun getPredictedOvulationDate(): LocalDate
fun getPredictedOvulationDate(): LocalDate?
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.mensinator.app.business

import android.database.sqlite.SQLiteDatabase
import androidx.annotation.WorkerThread
import com.mensinator.app.data.Symptom
import com.mensinator.app.data.Setting
import com.mensinator.app.data.Symptom
import java.time.LocalDate

interface IPeriodDatabaseHelper {
Expand All @@ -17,10 +16,13 @@ interface IPeriodDatabaseHelper {
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)
fun addDateToPeriod(date: LocalDate, periodId: PeriodId)

// Get all period dates for a given month
fun getPeriodDatesForMonth(year: Int, month: Int): Map<LocalDate, Int>
fun getPeriodDatesForMonth(year: Int, month: Int): Map<LocalDate, PeriodId>

// NEW! Testing new function for getting all period dates month-1, month, month+1
suspend fun getPeriodDatesForMonthNew(year: Int, month: Int): Map<LocalDate, PeriodId>

// Returns how many periods that are in the database
fun getPeriodCount(): Int
Expand All @@ -29,19 +31,23 @@ interface IPeriodDatabaseHelper {
fun removeDateFromPeriod(date: LocalDate)

// This function is used to get all symptoms from the database
fun getAllSymptoms(): List<Symptom>
suspend fun getAllSymptoms(): List<Symptom>

// 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<LocalDate>
// NEW! Testing new function for getting all symptom dates month-1, month, month+1
suspend fun getSymptomDatesForMonthNew(year: Int, month: Int): Set<LocalDate>

suspend fun getSymptomsForDates(): Map<LocalDate, Set<Symptom>>

// This function is used to update symptom dates in the database
fun updateSymptomDate(dates: List<LocalDate>, symptomId: List<Int>)

// This function is used to get symptoms for a given date
fun getActiveSymptomIdsForDate(date: LocalDate): List<Int>
suspend fun getActiveSymptomIdsForDate(date: LocalDate): List<Int>

fun getSymptomColorForDate(date: LocalDate): List<String>

Expand All @@ -52,24 +58,26 @@ interface IPeriodDatabaseHelper {
fun updateSetting(key: String, value: String): Boolean

// This function is used to get a setting from the database
@WorkerThread
fun getSettingByKey(key: String): Setting?
suspend fun getSettingByKey(key: String): Setting?

// This function wraps getSettingByKey to return a valid string
fun getStringSettingByKey(key: String): String
suspend fun getStringSettingByKey(key: String): String

// 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<LocalDate>

//NEW! Testing new function for getting all ovulation dates month-1, month, month+1
suspend fun getOvulationDatesForMonthNew(year: Int, month: Int): Set<LocalDate>

// 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
fun newFindOrCreatePeriodID(date: LocalDate): PeriodId

// Retrieve the previous period's start date from a given date
fun getFirstPreviousPeriodDate(date: LocalDate): LocalDate?
Expand Down Expand Up @@ -111,5 +119,5 @@ interface IPeriodDatabaseHelper {
fun renameSymptom(symptomId: Int, newName: String)

// Retrieve the latest period start date
fun getLatestPeriodStart(): LocalDate
fun getLatestPeriodStart(): LocalDate?
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package com.mensinator.app.business
import java.time.LocalDate

interface IPeriodPrediction {
fun getPredictedPeriodDate(): LocalDate
fun getPredictedPeriodDate(): LocalDate?
}
Loading
Loading