diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3062be1..27f84e2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -103,4 +103,8 @@ dependencies { // Firebase Analytics implementation (platform(libs.firebase.bom)) implementation (libs.google.firebase.analytics) + + // In App Review + implementation (libs.review) + implementation (libs.review.ktx) } \ No newline at end of file diff --git a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/MainActivity.kt b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/MainActivity.kt index 643e15c..a8c8821 100644 --- a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/MainActivity.kt +++ b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/MainActivity.kt @@ -1,7 +1,6 @@ package com.minimalisttodolist.pleasebethelastrecyclerview import android.graphics.Color -import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle @@ -10,7 +9,6 @@ import androidx.activity.enableEdgeToEdge import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.annotation.RequiresApi import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -18,13 +16,14 @@ import androidx.compose.runtime.getValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.lifecycleScope import androidx.room.Room +import com.google.android.play.core.review.ReviewManagerFactory import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.analytics -import com.google.firebase.analytics.logEvent import com.minimalisttodolist.pleasebethelastrecyclerview.data.database.MIGRATION_1_2 import com.minimalisttodolist.pleasebethelastrecyclerview.data.preferences.AppPreferences import com.minimalisttodolist.pleasebethelastrecyclerview.data.database.TaskDatabase +import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ReviewStateType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ThemeType import com.minimalisttodolist.pleasebethelastrecyclerview.ui.navigation.NavGraph import com.minimalisttodolist.pleasebethelastrecyclerview.viewmodel.AppViewModel @@ -69,7 +68,6 @@ class MainActivity : ComponentActivity() { TaskViewModelFactory(db.dao, notificationHelper, dataStoreViewModel) } - @RequiresApi(Build.VERSION_CODES.O) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -114,7 +112,6 @@ class MainActivity : ComponentActivity() { ) } - @RequiresApi(Build.VERSION_CODES.O) @Composable private fun SetupTheme() { val theme by dataStoreViewModel.theme.collectAsState() @@ -137,12 +134,23 @@ class MainActivity : ComponentActivity() { NavGraph( taskViewModel = taskViewModel, dataStoreViewModel = dataStoreViewModel, - appViewModel = appViewModel + appViewModel = appViewModel, + maybeShowReview = { maybeShowReview() } ) } } - @RequiresApi(Build.VERSION_CODES.O) + private fun maybeShowReview() { + val manager = ReviewManagerFactory.create(applicationContext) + manager.requestReviewFlow().addOnCompleteListener { + if(it.isSuccessful) { + manager.launchReviewFlow(this, it.result) + dataStoreViewModel.updateReviewState(ReviewStateType.SHOWN) + taskViewModel.logReviewStatus(ReviewStateType.SHOWN) + } + } + } + override fun onResume() { super.onResume() taskViewModel.reloadTasks() diff --git a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/data/model/ReviewStateType.kt b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/data/model/ReviewStateType.kt new file mode 100644 index 0000000..6ed5252 --- /dev/null +++ b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/data/model/ReviewStateType.kt @@ -0,0 +1,7 @@ +package com.minimalisttodolist.pleasebethelastrecyclerview.data.model + +enum class ReviewStateType { + NOT_YET, + SHOWN, + READY +} diff --git a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/data/preferences/AppPreferences.kt b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/data/preferences/AppPreferences.kt index b4def64..36984d9 100644 --- a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/data/preferences/AppPreferences.kt +++ b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/data/preferences/AppPreferences.kt @@ -14,6 +14,7 @@ import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.FirstDayOfT import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.FontFamilyType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.FontWeightType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.RecurrenceType +import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ReviewStateType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.SortType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ThemeType import kotlinx.coroutines.flow.Flow @@ -47,6 +48,7 @@ class AppPreferences private constructor(context: Context) { val TUTORIAL_VISIBILITY = booleanPreferencesKey("tutorial_visibility") val DUE_DATE_FILTER = stringPreferencesKey("due_date_filter") val FIRST_DAY_OF_THE_WEEK = stringPreferencesKey("first_day_of_the_week") + val REVIEW_STATE = stringPreferencesKey("review_state") } val theme: Flow = dataStore.data.map { preferences -> @@ -93,6 +95,10 @@ class AppPreferences private constructor(context: Context) { FirstDayOfTheWeekType.fromDisplayName(preferences[PreferencesKeys.FIRST_DAY_OF_THE_WEEK] ?: FirstDayOfTheWeekType.MONDAY.displayName) } + val reviewState: Flow = dataStore.data.map { preferences -> + ReviewStateType.valueOf(preferences[PreferencesKeys.REVIEW_STATE] ?: ReviewStateType.NOT_YET.name) + } + suspend fun saveTheme(theme: ThemeType) { dataStore.edit { preferences -> preferences[PreferencesKeys.THEME] = theme.displayName } } @@ -143,4 +149,8 @@ class AppPreferences private constructor(context: Context) { suspend fun saveFirstDayOfTheWeekType(firstDayOfTheWeekType: FirstDayOfTheWeekType) { dataStore.edit { preferences -> preferences[PreferencesKeys.FIRST_DAY_OF_THE_WEEK] = firstDayOfTheWeekType.displayName } } + + suspend fun updateReviewState(reviewState: ReviewStateType) { + dataStore.edit { preferences -> preferences[PreferencesKeys.REVIEW_STATE] = reviewState.name } + } } \ No newline at end of file diff --git a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/ui/navigation/NavGraph.kt b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/ui/navigation/NavGraph.kt index 055be14..f116581 100644 --- a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/ui/navigation/NavGraph.kt +++ b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/ui/navigation/NavGraph.kt @@ -1,7 +1,5 @@ package com.minimalisttodolist.pleasebethelastrecyclerview.ui.navigation -import android.os.Build -import androidx.annotation.RequiresApi import androidx.compose.material.ripple.LocalRippleTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -16,13 +14,13 @@ import com.minimalisttodolist.pleasebethelastrecyclerview.viewmodel.AppViewModel import com.minimalisttodolist.pleasebethelastrecyclerview.viewmodel.TaskViewModel import com.minimalisttodolist.pleasebethelastrecyclerview.ui.theme.NoRippleTheme -@RequiresApi(Build.VERSION_CODES.O) @Composable fun NavGraph( startDestination: String = "task_list", taskViewModel: TaskViewModel, dataStoreViewModel: DataStoreViewModel, - appViewModel: AppViewModel + appViewModel: AppViewModel, + maybeShowReview: () -> Unit ) { val navController = rememberNavController() val taskState by taskViewModel.state.collectAsState() @@ -41,6 +39,7 @@ fun NavGraph( onAppEvent = appViewModel::onEvent, taskViewModel = taskViewModel, dataStoreViewModel = dataStoreViewModel, + maybeShowReview = maybeShowReview ) } } diff --git a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/ui/screens/TaskScreen.kt b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/ui/screens/TaskScreen.kt index b25b041..a09b4ce 100644 --- a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/ui/screens/TaskScreen.kt +++ b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/ui/screens/TaskScreen.kt @@ -1,7 +1,5 @@ package com.minimalisttodolist.pleasebethelastrecyclerview.ui.screens -import android.os.Build -import androidx.annotation.RequiresApi import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically @@ -53,6 +51,7 @@ import com.minimalisttodolist.pleasebethelastrecyclerview.util.AnalyticsEvents import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.DueDateFilterType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.PriorityColor import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.RecurrenceType +import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ReviewStateType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.Task import com.minimalisttodolist.pleasebethelastrecyclerview.ui.components.CompleteIcon import com.minimalisttodolist.pleasebethelastrecyclerview.ui.components.emptyStateMessages @@ -76,7 +75,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) -@RequiresApi(Build.VERSION_CODES.O) @Composable fun TaskScreen( taskState: TaskState, @@ -85,6 +83,7 @@ fun TaskScreen( onAppEvent: (AppEvent) -> Unit, taskViewModel: TaskViewModel, dataStoreViewModel: DataStoreViewModel, + maybeShowReview: () -> Unit ) { val darkTheme = LocalDarkTheme.current @@ -187,7 +186,11 @@ fun TaskScreen( }, ) { padding -> - TaskList(onEvent, onClearFilters = { onEvent(TaskEvent.ClearFilters) }, taskState, taskViewModel, padding, dataStoreViewModel) + TaskList(onEvent, onClearFilters = { onEvent(TaskEvent.ClearFilters) }, taskState, taskViewModel, padding, dataStoreViewModel, checkAndShowReview = { + if (dataStoreViewModel.reviewStateType.value == ReviewStateType.READY) { + maybeShowReview() + } + }) if (taskState.isAddTaskDialogVisible) { AddTaskDialog(taskState, onEvent, taskViewModel, dataStoreViewModel, onAppEvent, isEdit = taskState.editingTaskId != null) @@ -290,9 +293,8 @@ fun TaskScreen( } } -@RequiresApi(Build.VERSION_CODES.O) @Composable -fun TaskList(onEvent: (TaskEvent) -> Unit, onClearFilters: () -> Unit, taskState: TaskState, viewModel: TaskViewModel, padding: PaddingValues, dataStoreViewModel: DataStoreViewModel) { +fun TaskList(onEvent: (TaskEvent) -> Unit, onClearFilters: () -> Unit, taskState: TaskState, viewModel: TaskViewModel, padding: PaddingValues, dataStoreViewModel: DataStoreViewModel, checkAndShowReview: () -> Unit) { val dueDateFilterType by dataStoreViewModel.dueDateFilter.collectAsState() val recurrenceType by dataStoreViewModel.recurrenceFilter.collectAsState() val filterText = buildString { @@ -361,6 +363,7 @@ fun TaskList(onEvent: (TaskEvent) -> Unit, onClearFilters: () -> Unit, taskState visible = false } } + checkAndShowReview() }, viewModel = viewModel ) @@ -369,7 +372,6 @@ fun TaskList(onEvent: (TaskEvent) -> Unit, onClearFilters: () -> Unit, taskState } } -@RequiresApi(Build.VERSION_CODES.O) @Composable fun TaskItem(task: Task, onEdit: (Task) -> Unit, onDelete: (Task) -> Unit, viewModel: TaskViewModel) { val darkTheme = LocalDarkTheme.current @@ -426,10 +428,8 @@ fun TaskItem(task: Task, onEdit: (Task) -> Unit, onDelete: (Task) -> Unit, viewM } } -@RequiresApi(Build.VERSION_CODES.O) @Composable fun DueDate_Recurrence_Note( - modifier: Modifier = Modifier, task: Task, viewModel: TaskViewModel ) { diff --git a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/util/AnalyticsEvents.kt b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/util/AnalyticsEvents.kt index 197c468..6f1c54e 100644 --- a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/util/AnalyticsEvents.kt +++ b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/util/AnalyticsEvents.kt @@ -9,4 +9,5 @@ object AnalyticsEvents { const val COMPLETE_TASK_CLICKED = "complete_task_clicked" const val REPEAT_FILTER_CLICKED = "repeat_filter_change" const val DUE_FILTER_CHANGE = "due_filter_change" + const val MAYBE_REVIEW_SHOWN = "maybe_review_shown" } \ No newline at end of file diff --git a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/viewmodel/DataStoreViewModel.kt b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/viewmodel/DataStoreViewModel.kt index c4840b8..446fa84 100644 --- a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/viewmodel/DataStoreViewModel.kt +++ b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/viewmodel/DataStoreViewModel.kt @@ -1,6 +1,5 @@ package com.minimalisttodolist.pleasebethelastrecyclerview.viewmodel -import androidx.compose.ui.text.font.FontWeight import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope @@ -10,6 +9,7 @@ import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.FirstDayOfT import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.FontFamilyType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.FontWeightType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.RecurrenceType +import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ReviewStateType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.SortType import com.minimalisttodolist.pleasebethelastrecyclerview.data.preferences.AppPreferences import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ThemeType @@ -59,6 +59,10 @@ class DataStoreViewModel(private val appPreferences: AppPreferences) : ViewModel .map { it } .stateIn(viewModelScope, SharingStarted.Eagerly, FirstDayOfTheWeekType.MONDAY) + val reviewStateType: StateFlow = appPreferences.reviewState + .map { it } + .stateIn(viewModelScope, SharingStarted.Eagerly, ReviewStateType.NOT_YET) + fun saveTheme(themeType: ThemeType) { viewModelScope.launch { appPreferences.saveTheme(themeType) } } @@ -99,28 +103,8 @@ class DataStoreViewModel(private val appPreferences: AppPreferences) : ViewModel viewModelScope.launch { appPreferences.saveFirstDayOfTheWeekType(firstDayOfTheWeekType) } } - private fun fontWeightFromDisplayName(displayName: String): FontWeight { - return when (displayName) { - "Light" -> FontWeight.Light - "Thin" -> FontWeight.Thin - "Normal" -> FontWeight.Normal - "Medium" -> FontWeight.Medium - "Bold" -> FontWeight.Bold - "Black" -> FontWeight.Black - else -> FontWeight.Normal - } - } - - private fun FontWeight.toDisplayString(): String { - return when (this) { - FontWeight.Light -> "Light" - FontWeight.Thin -> "Thin" - FontWeight.Normal -> "Normal" - FontWeight.Medium -> "Medium" - FontWeight.Bold -> "Bold" - FontWeight.Black -> "Black" - else -> "Normal" - } + fun updateReviewState(reviewState: ReviewStateType) { + viewModelScope.launch { appPreferences.updateReviewState(reviewState) } } } diff --git a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/viewmodel/TaskViewModel.kt b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/viewmodel/TaskViewModel.kt index f1b73f9..c000358 100644 --- a/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/viewmodel/TaskViewModel.kt +++ b/app/src/main/java/com/minimalisttodolist/pleasebethelastrecyclerview/viewmodel/TaskViewModel.kt @@ -1,7 +1,5 @@ package com.minimalisttodolist.pleasebethelastrecyclerview.viewmodel -import android.os.Build -import androidx.annotation.RequiresApi import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope @@ -18,6 +16,7 @@ import com.minimalisttodolist.pleasebethelastrecyclerview.data.database.TaskDao import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ClockType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.DueDateFilterType import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.FirstDayOfTheWeekType +import com.minimalisttodolist.pleasebethelastrecyclerview.data.model.ReviewStateType import com.minimalisttodolist.pleasebethelastrecyclerview.util.calculateNextDueDate import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay @@ -32,7 +31,6 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.TemporalAdjusters -@RequiresApi(Build.VERSION_CODES.O) @OptIn(ExperimentalCoroutinesApi::class) class TaskViewModel( private val dao: TaskDao, @@ -79,7 +77,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) fun onEvent(event: TaskEvent) { when (event) { is TaskEvent.DeleteTask -> handleDeleteTask(event.task) @@ -121,7 +118,6 @@ class TaskViewModel( dataStoreViewModel.saveDueDateFilter(_dueDateFilterType.value) } - @RequiresApi(Build.VERSION_CODES.O) private fun handleDeleteTask(task: Task) { viewModelScope.launch { delay(500) @@ -144,18 +140,17 @@ class TaskViewModel( } private suspend fun deleteTaskNormally(task: Task) { - notificationHelper.cancelNotification(task.id.toInt()) + notificationHelper.cancelNotification(task.id) dao.deleteTask(task) dao.insertDeletedTask(task.toDeletedTask()) } - @RequiresApi(Build.VERSION_CODES.O) private suspend fun updateTaskWithNewDueDate(task: Task) { val newDueDate = calculateNextDueDate(task.dueDate, task.recurrenceType) if (newDueDate != null) { val updatedTask = task.copy(dueDate = newDueDate) dao.upsertTask(updatedTask) - notificationHelper.cancelNotification(task.id.toInt()) + notificationHelper.cancelNotification(task.id) notificationHelper.scheduleNotification(updatedTask) } else { // If we couldn't calculate a new due date, delete the task @@ -163,14 +158,12 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) fun isDueOrPast(dueDate: Long?): Boolean { if (dueDate == null) return false val now = Instant.now().toEpochMilli() return dueDate <= now } - @RequiresApi(Build.VERSION_CODES.O) private fun handleSaveTask() { val currentState = state.value if (currentState.title.isBlank()) { @@ -187,25 +180,45 @@ class TaskViewModel( recurrenceType = currentState.recurrenceType ) - Firebase.analytics.logEvent(AnalyticsEvents.SAVE_TASK_CLICKED){ - param("taskId", task.id.toString()) - param("priority", (task.priority != 0).toString() ) - param("note", (task.note.isNotBlank()).toString() ) - param("dueDateTime", (task.dueDate != null).toString() ) - param("repeat", (task.recurrenceType != RecurrenceType.NONE).toString() ) - - } + logTaskSaveAnalytics(task) viewModelScope.launch { val taskId = dao.upsertTask(task) val savedTask = dao.getTaskById(if (taskId.toInt() == -1) currentState.editingTaskId!! else taskId.toInt()) savedTask?.let { notificationHelper.scheduleNotification(it) } + + checkAndUpdateReviewState(taskId.toInt()) } resetAddTaskDialog() } - @RequiresApi(Build.VERSION_CODES.O) + private fun checkAndUpdateReviewState(taskId: Int) { + val currentReviewState = dataStoreViewModel.reviewStateType.value + + // 07/08/2024 current number of tasks saved by a user everyday = 5.1, reviewDialog shown after a week + if (currentReviewState == ReviewStateType.NOT_YET && taskId >= 35) { + dataStoreViewModel.updateReviewState(ReviewStateType.READY) + logReviewStatus(ReviewStateType.READY) + } + } + + fun logReviewStatus(reviewStateType: ReviewStateType) { + Firebase.analytics.logEvent(AnalyticsEvents.MAYBE_REVIEW_SHOWN) { + param("reviewState", reviewStateType.toString()) + } + } + + private fun logTaskSaveAnalytics(task: Task) { + Firebase.analytics.logEvent(AnalyticsEvents.SAVE_TASK_CLICKED) { + param("taskId", task.id.toString()) + param("priority", (task.priority != 0).toString()) + param("note", (task.note.isNotBlank()).toString()) + param("dueDateTime", (task.dueDate != null).toString()) + param("repeat", (task.recurrenceType != RecurrenceType.NONE).toString()) + } + } + private fun handleEditTask(task: Task) { _state.update { val dueDateOnly = getLocalDateFromEpochMilliWithNull(task.dueDate) @@ -224,7 +237,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) private fun handleSetDueDate(dueDate: LocalDate?) { _state.update { it.copy(dueDateOnly = dueDate).also { updatedState -> @@ -233,7 +245,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) private fun handleSetDueTime(dueTime: LocalTime) { _state.update { it.copy(dueTimeOnly = dueTime).also { updatedState -> @@ -249,7 +260,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) private fun handleUndoDeleteTask(deletedTask: DeletedTask) { viewModelScope.launch { delay(500) @@ -279,7 +289,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) fun reloadTasks() { viewModelScope.launch { combine( @@ -314,7 +323,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) private fun isWithinDueDateFilter( task: Task, filterType: DueDateFilterType, @@ -342,7 +350,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) private fun combineDateAndTime(state: TaskState) { val date = state.dueDateOnly val time = state.dueTimeOnly @@ -381,7 +388,6 @@ class TaskViewModel( ) } - @RequiresApi(Build.VERSION_CODES.O) fun formatDueDateWithDateTime(epochMilli: Long?): String { val formatter = when (dataStoreViewModel.clockType.value) { ClockType.TWELVE_HOUR -> DateTimeFormatter.ofPattern("MMM dd, yyyy hh:mm a") @@ -421,7 +427,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) fun formatDueDateWithDateOnly(epochMilli: Long?): String { val dateFormatter = DateTimeFormatter.ofPattern("MMM dd, yyyy") val dateFormatterCurrentYear = DateTimeFormatter.ofPattern("MMM dd") @@ -443,7 +448,6 @@ class TaskViewModel( } } - @RequiresApi(Build.VERSION_CODES.O) fun formatDueDateWithTimeOnly(epochMilli: Long?): String { val timeFormatter = when (dataStoreViewModel.clockType.value) { ClockType.TWELVE_HOUR -> DateTimeFormatter.ofPattern("hh:mm a") @@ -457,25 +461,21 @@ class TaskViewModel( return dateTime.format(timeFormatter) } - @RequiresApi(Build.VERSION_CODES.O) fun getLocalDateFromEpochMilli(epochMilli: Long?): LocalDate { epochMilli ?: return LocalDate.now() return Instant.ofEpochMilli(epochMilli).atZone(ZoneId.systemDefault()).toLocalDate() } - @RequiresApi(Build.VERSION_CODES.O) fun getLocalTimeFromEpochMilli(epochMilli: Long?): LocalTime { epochMilli ?: return LocalTime.now() return Instant.ofEpochMilli(epochMilli).atZone(ZoneId.systemDefault()).toLocalTime() } - @RequiresApi(Build.VERSION_CODES.O) fun getLocalDateFromEpochMilliWithNull(epochMilli: Long?): LocalDate? { epochMilli ?: return null return Instant.ofEpochMilli(epochMilli).atZone(ZoneId.systemDefault()).toLocalDate() } - @RequiresApi(Build.VERSION_CODES.O) fun getLocalTimeFromEpochMilliWithNull(epochMilli: Long?): LocalTime? { epochMilli ?: return null return Instant.ofEpochMilli(epochMilli).atZone(ZoneId.systemDefault()).toLocalTime() @@ -487,7 +487,6 @@ class TaskViewModelFactory( private val notificationHelper: NotificationHelper, private val dataStoreViewModel: DataStoreViewModel ) : ViewModelProvider.Factory { - @RequiresApi(Build.VERSION_CODES.O) override fun create(modelClass: Class): T { if (modelClass.isAssignableFrom(TaskViewModel::class.java)) { @Suppress("UNCHECKED_CAST") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5c445c..a292069 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,8 @@ activityCompose = "1.9.0" composeBom = "2024.04.01" lottie = "6.3.0" materialIconsExtended = "1.6.8" +review = "2.0.1" +reviewKtx = "2.0.1" roomKtx = "2.6.1" navigationCompose = "2.7.7" uiTextGoogleFonts = "1.6.8" @@ -53,6 +55,8 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" } firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics", version.ref = "firebaseAnalytics" } +review = { module = "com.google.android.play:review", version.ref = "review" } +review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "reviewKtx" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }