From c4d06d7ffb2e94ce72debfa9396bf04737f8f43f Mon Sep 17 00:00:00 2001 From: Brayan Bedritchuk Date: Thu, 11 Dec 2025 14:13:34 -0300 Subject: [PATCH 1/2] Force update screens after midnight --- .../com/sailboat/todozy/home/HomeActivity.kt | 115 +++++++++--------- .../todozy/home/model/BottomBarItem.kt | 9 ++ .../todozy/navigation/AppNavigationModule.kt | 45 ++++--- .../todozy.android.application.gradle.kts | 35 +++--- .../kotlin/todozy.android.library.gradle.kts | 12 +- .../main/kotlin/todozy.jvm.library.gradle.kts | 11 +- .../todozy/domain/model/RepeatType.kt | 5 +- .../todozy/domain/model/TaskProgressRange.kt | 3 +- .../todozy/domain/model/TaskStatus.kt | 7 +- .../domain/repository/TaskRepository.kt | 9 -- .../about/impl/presentation/AboutAdapter.kt | 11 +- .../about/impl/presentation/GetAboutView.kt | 25 +++- .../impl/presentation/SettingsFragment.kt | 2 - .../impl/presentation/TaskFormFragment.kt | 3 +- .../extension/CalendarExtensions.kt | 19 +++ .../extension/CalendarExtensionsTest.kt | 45 +++++++ .../impl/presentation/TaskHistoryFragment.kt | 2 - .../viewmodel/TaskListViewModel.kt | 1 + .../viewmodel/TaskListViewModelTest.kt | 50 ++++++++ 19 files changed, 271 insertions(+), 138 deletions(-) create mode 100644 app/src/main/java/br/com/sailboat/todozy/home/model/BottomBarItem.kt create mode 100644 feature/task-form/impl/src/main/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/extension/CalendarExtensions.kt create mode 100644 feature/task-form/impl/src/test/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/extension/CalendarExtensionsTest.kt diff --git a/app/src/main/java/br/com/sailboat/todozy/home/HomeActivity.kt b/app/src/main/java/br/com/sailboat/todozy/home/HomeActivity.kt index b27837a6..518e0622 100644 --- a/app/src/main/java/br/com/sailboat/todozy/home/HomeActivity.kt +++ b/app/src/main/java/br/com/sailboat/todozy/home/HomeActivity.kt @@ -2,6 +2,7 @@ package br.com.sailboat.todozy.home import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.compose.material.BottomNavigation @@ -15,7 +16,7 @@ import androidx.compose.material.icons.automirrored.filled.ListAlt import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.Settings import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.stringResource @@ -26,44 +27,44 @@ import br.com.sailboat.todozy.databinding.ActivityHomeBinding import br.com.sailboat.todozy.feature.navigation.android.HomeDestination import br.com.sailboat.todozy.feature.navigation.android.HomeNavigationExtras.EXTRA_HOME_DESTINATION import br.com.sailboat.todozy.feature.navigation.android.HomeTabNavigator +import br.com.sailboat.todozy.home.model.BottomBarItem import br.com.sailboat.uicomponent.impl.theme.TodozyTheme import br.com.sailboat.todozy.R as AppR import br.com.sailboat.uicomponent.impl.R as UiR class HomeActivity : AppCompatActivity(), HomeTabNavigator { private lateinit var binding: ActivityHomeBinding - private var selectedTabId by mutableStateOf(AppR.id.nav_tasks) - - private val navHostIds = - mapOf( - AppR.id.nav_tasks to AppR.id.tasks_nav_host, - AppR.id.nav_history to AppR.id.history_nav_host, - AppR.id.nav_settings to AppR.id.settings_nav_host, - ) - private val navGraphIds = - mapOf( - AppR.id.nav_tasks to AppR.navigation.nav_tasks, - AppR.id.nav_history to AppR.navigation.nav_history, - AppR.id.nav_settings to AppR.navigation.nav_settings, - ) - private val bottomBarItems = - listOf( - BottomBarItem( - id = AppR.id.nav_tasks, - icon = Icons.AutoMirrored.Filled.ListAlt, - title = UiR.string.label_tasks, - ), - BottomBarItem( - id = AppR.id.nav_history, - icon = Icons.Filled.History, - title = UiR.string.history, - ), - BottomBarItem( - id = AppR.id.nav_settings, - icon = Icons.Filled.Settings, - title = UiR.string.settings, - ), - ) + private var selectedTabId by mutableIntStateOf(AppR.id.nav_tasks) + + private val navHostIds = mapOf( + AppR.id.nav_tasks to AppR.id.tasks_nav_host, + AppR.id.nav_history to AppR.id.history_nav_host, + AppR.id.nav_settings to AppR.id.settings_nav_host, + ) + + private val navGraphIds = mapOf( + AppR.id.nav_tasks to AppR.navigation.nav_tasks, + AppR.id.nav_history to AppR.navigation.nav_history, + AppR.id.nav_settings to AppR.navigation.nav_settings, + ) + + private val bottomBarItems = listOf( + BottomBarItem( + id = AppR.id.nav_tasks, + icon = Icons.AutoMirrored.Filled.ListAlt, + title = UiR.string.label_tasks, + ), + BottomBarItem( + id = AppR.id.nav_history, + icon = Icons.Filled.History, + title = UiR.string.history, + ), + BottomBarItem( + id = AppR.id.nav_settings, + icon = Icons.Filled.Settings, + title = UiR.string.settings, + ), + ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -89,7 +90,10 @@ class HomeActivity : AppCompatActivity(), HomeTabNavigator { selected = selected, onClick = { onBottomTabSelected(item.id) }, icon = { - Icon(imageVector = item.icon, contentDescription = stringResource(id = item.title)) + Icon( + imageVector = item.icon, + contentDescription = stringResource(id = item.title) + ) }, label = { Text(text = stringResource(id = item.title)) }, selectedContentColor = MaterialTheme.colors.primary, @@ -103,12 +107,11 @@ class HomeActivity : AppCompatActivity(), HomeTabNavigator { } private fun applyStartDestination() { - val targetDestination = - when (intent.getSerializableExtra(EXTRA_HOME_DESTINATION) as? HomeDestination) { - HomeDestination.HISTORY -> AppR.id.nav_history - HomeDestination.SETTINGS -> AppR.id.nav_settings - else -> AppR.id.nav_tasks - } + val targetDestination = when (intent.homeDestinationExtra()) { + HomeDestination.HISTORY -> AppR.id.nav_history + HomeDestination.SETTINGS -> AppR.id.nav_settings + else -> AppR.id.nav_tasks + } selectTab(targetDestination, allowReselectPop = false) } @@ -116,10 +119,7 @@ class HomeActivity : AppCompatActivity(), HomeTabNavigator { selectTab(itemId, allowReselectPop = true) } - private fun selectTab( - itemId: Int, - allowReselectPop: Boolean, - ) { + private fun selectTab(itemId: Int, allowReselectPop: Boolean) { if (itemId == AppR.id.nav_history) { notifyHistoryTabSelected() } @@ -138,7 +138,8 @@ class HomeActivity : AppCompatActivity(), HomeTabNavigator { navHostIds.forEach { (destinationId, containerId) -> val fragment = supportFragmentManager.findFragmentById(containerId) ?: return@forEach - val containerView = binding.root.findViewById(containerId) + val containerView = + binding.root.findViewById(containerId) if (destinationId == itemId) { containerView?.isVisible = true transaction.show(fragment) @@ -176,12 +177,11 @@ class HomeActivity : AppCompatActivity(), HomeTabNavigator { } override fun switchTo(destination: HomeDestination) { - val targetId = - when (destination) { - HomeDestination.TASKS -> AppR.id.nav_tasks - HomeDestination.HISTORY -> AppR.id.nav_history - HomeDestination.SETTINGS -> AppR.id.nav_settings - } + val targetId = when (destination) { + HomeDestination.TASKS -> AppR.id.nav_tasks + HomeDestination.HISTORY -> AppR.id.nav_history + HomeDestination.SETTINGS -> AppR.id.nav_settings + } selectTab(targetId, allowReselectPop = false) } @@ -197,11 +197,14 @@ class HomeActivity : AppCompatActivity(), HomeTabNavigator { private const val HISTORY_REFRESH_KEY = "history-refresh-request" -private data class BottomBarItem( - val id: Int, - val icon: androidx.compose.ui.graphics.vector.ImageVector, - val title: Int, -) +private fun Intent.homeDestinationExtra(): HomeDestination? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getSerializableExtra(EXTRA_HOME_DESTINATION, HomeDestination::class.java) + } else { + @Suppress("DEPRECATION") + getSerializableExtra(EXTRA_HOME_DESTINATION) as? HomeDestination + } +} private fun HomeActivity.notifyHistoryTabSelected() { val navController = this.navHostFragmentFor(AppR.id.nav_history).navController diff --git a/app/src/main/java/br/com/sailboat/todozy/home/model/BottomBarItem.kt b/app/src/main/java/br/com/sailboat/todozy/home/model/BottomBarItem.kt new file mode 100644 index 00000000..db91c3b2 --- /dev/null +++ b/app/src/main/java/br/com/sailboat/todozy/home/model/BottomBarItem.kt @@ -0,0 +1,9 @@ +package br.com.sailboat.todozy.home.model + +import androidx.compose.ui.graphics.vector.ImageVector + +data class BottomBarItem( + val id: Int, + val icon: ImageVector, + val title: Int, +) diff --git a/app/src/main/java/br/com/sailboat/todozy/navigation/AppNavigationModule.kt b/app/src/main/java/br/com/sailboat/todozy/navigation/AppNavigationModule.kt index 5412e315..cb694f8e 100644 --- a/app/src/main/java/br/com/sailboat/todozy/navigation/AppNavigationModule.kt +++ b/app/src/main/java/br/com/sailboat/todozy/navigation/AppNavigationModule.kt @@ -24,34 +24,33 @@ private fun ActivityResultLauncher.launchHome( launch(intent) } -internal val appNavigationModule: List = - listOf( - module(override = true) { - factory { - object : TaskListNavigator { - override fun navigateToTaskList(context: Context) { - context.startHome(HomeDestination.TASKS) - } +internal val appNavigationModule: List = listOf( + module { + factory { + object : TaskListNavigator { + override fun navigateToTaskList(context: Context) { + context.startHome(HomeDestination.TASKS) } } + } - factory { - object : TaskHistoryNavigator { - override fun navigateToTaskHistory(context: Context) { - context.startHome(HomeDestination.HISTORY) - } + factory { + object : TaskHistoryNavigator { + override fun navigateToTaskHistory(context: Context) { + context.startHome(HomeDestination.HISTORY) } } + } - factory { - object : SettingsNavigator { - override fun navigateToSettings( - context: Context, - launcher: ActivityResultLauncher, - ) { - launcher.launchHome(context, HomeDestination.SETTINGS) - } + factory { + object : SettingsNavigator { + override fun navigateToSettings( + context: Context, + launcher: ActivityResultLauncher, + ) { + launcher.launchHome(context, HomeDestination.SETTINGS) } } - }, - ) + } + }, +) diff --git a/build-logic/src/main/kotlin/todozy.android.application.gradle.kts b/build-logic/src/main/kotlin/todozy.android.application.gradle.kts index 6fc19788..3e36baca 100644 --- a/build-logic/src/main/kotlin/todozy.android.application.gradle.kts +++ b/build-logic/src/main/kotlin/todozy.android.application.gradle.kts @@ -1,11 +1,10 @@ import com.android.build.api.dsl.ApplicationExtension -import java.util.Properties -import org.gradle.api.JavaVersion import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension +import java.util.Properties plugins { id("com.android.application") @@ -56,12 +55,11 @@ extensions.configure { buildTypes { getByName("release") { - signingConfig = - if (hasSigningProps(localProps)) { - signingConfigs.getByName("config") - } else { - signingConfigs.getByName("debug") - } + signingConfig = if (hasSigningProps(localProps)) { + signingConfigs.getByName("config") + } else { + signingConfigs.getByName("debug") + } isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), @@ -70,12 +68,11 @@ extensions.configure { isDebuggable = false } getByName("debug") { - signingConfig = - if (hasSigningProps(localProps)) { - signingConfigs.getByName("config") - } else { - signingConfigs.getByName("debug") - } + signingConfig = if (hasSigningProps(localProps)) { + signingConfigs.getByName("config") + } else { + signingConfigs.getByName("debug") + } isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), @@ -113,8 +110,8 @@ extensions.configure { } } -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "17" +extensions.configure { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) } } diff --git a/build-logic/src/main/kotlin/todozy.android.library.gradle.kts b/build-logic/src/main/kotlin/todozy.android.library.gradle.kts index c484ad54..d74dd532 100644 --- a/build-logic/src/main/kotlin/todozy.android.library.gradle.kts +++ b/build-logic/src/main/kotlin/todozy.android.library.gradle.kts @@ -1,10 +1,10 @@ import com.android.build.api.dsl.LibraryExtension import org.gradle.api.JavaVersion +import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.withType -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension plugins { id("com.android.library") @@ -37,8 +37,8 @@ extensions.configure { } } -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "17" +extensions.configure { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) } } diff --git a/build-logic/src/main/kotlin/todozy.jvm.library.gradle.kts b/build-logic/src/main/kotlin/todozy.jvm.library.gradle.kts index 40fc1d99..874e8ae5 100644 --- a/build-logic/src/main/kotlin/todozy.jvm.library.gradle.kts +++ b/build-logic/src/main/kotlin/todozy.jvm.library.gradle.kts @@ -1,6 +1,7 @@ import org.gradle.api.JavaVersion -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension plugins { `java-library` @@ -12,6 +13,8 @@ java { targetCompatibility = JavaVersion.VERSION_17 } -tasks.withType().configureEach { - kotlinOptions.jvmTarget = "17" +extensions.configure { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } } diff --git a/domain/src/main/java/br/com/sailboat/todozy/domain/model/RepeatType.kt b/domain/src/main/java/br/com/sailboat/todozy/domain/model/RepeatType.kt index 71033d24..4166cf06 100644 --- a/domain/src/main/java/br/com/sailboat/todozy/domain/model/RepeatType.kt +++ b/domain/src/main/java/br/com/sailboat/todozy/domain/model/RepeatType.kt @@ -9,8 +9,7 @@ enum class RepeatType { SECOND, MINUTE, HOUR, - CUSTOM, - ; + CUSTOM; companion object { fun isAlarmRepeating(alarm: Alarm): Boolean { @@ -18,7 +17,7 @@ enum class RepeatType { } fun indexOf(index: Int): RepeatType { - for (repeatType in values()) { + for (repeatType in RepeatType.entries) { if (repeatType.ordinal == index) { return repeatType } diff --git a/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskProgressRange.kt b/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskProgressRange.kt index 2a401436..88df7342 100644 --- a/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskProgressRange.kt +++ b/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskProgressRange.kt @@ -6,8 +6,7 @@ enum class TaskProgressRange { ALL, LAST_YEAR, LAST_30_DAYS, - LAST_7_DAYS, - ; + LAST_7_DAYS; fun startDate(today: LocalDate): LocalDate = when (this) { ALL -> today diff --git a/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskStatus.kt b/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskStatus.kt index 87917ba8..9e4c8fed 100644 --- a/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskStatus.kt +++ b/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskStatus.kt @@ -1,12 +1,11 @@ package br.com.sailboat.todozy.domain.model enum class TaskStatus(val id: Int) { - // TODO: Add TaskStatusData object NOT_DONE(0), - DONE(1), - ; + DONE(1); companion object { - fun getById(id: Int): TaskStatus = values().firstOrNull { it.id == id } ?: NOT_DONE + fun getById(id: Int): TaskStatus = + TaskStatus.entries.firstOrNull { it.id == id } ?: NOT_DONE } } diff --git a/domain/src/main/java/br/com/sailboat/todozy/domain/repository/TaskRepository.kt b/domain/src/main/java/br/com/sailboat/todozy/domain/repository/TaskRepository.kt index a38a86b2..9ba58a30 100644 --- a/domain/src/main/java/br/com/sailboat/todozy/domain/repository/TaskRepository.kt +++ b/domain/src/main/java/br/com/sailboat/todozy/domain/repository/TaskRepository.kt @@ -5,22 +5,13 @@ import br.com.sailboat.todozy.domain.model.TaskFilter interface TaskRepository { suspend fun getTask(taskId: Long): Result - suspend fun getBeforeTodayTasks(filter: TaskFilter): Result> - suspend fun getTodayTasks(filter: TaskFilter): Result> - suspend fun getTomorrowTasks(filter: TaskFilter): Result> - suspend fun getNextDaysTasks(filter: TaskFilter): Result> - suspend fun getBeforeNowTasks(): Result> - suspend fun getTasksWithAlarms(): Result> - suspend fun insert(task: Task): Result - suspend fun update(task: Task): Result - suspend fun disableTask(task: Task): Result } diff --git a/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/AboutAdapter.kt b/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/AboutAdapter.kt index 36b2e4f6..eabf7835 100644 --- a/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/AboutAdapter.kt +++ b/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/AboutAdapter.kt @@ -12,12 +12,10 @@ import br.com.sailboat.uicomponent.model.LabelValueUiModel import br.com.sailboat.uicomponent.model.UiModel import br.com.sailboat.uicomponent.model.UiModelType -internal class AboutAdapter : - ListAdapter(UiModelDiffUtilCallback()) { - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int, - ) = when (viewType) { +internal class AboutAdapter : ListAdapter( + UiModelDiffUtilCallback() +) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) { UiModelType.IMAGE_TITLE.ordinal -> ImageTitleDividerViewHolder(parent) UiModelType.LABEL_VALUE.ordinal -> LabelValueViewHolder(parent) else -> EmptyViewHolder(parent) @@ -34,4 +32,5 @@ internal class AboutAdapter : } override fun getItemViewType(position: Int) = getItem(position).uiModelId + } diff --git a/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/GetAboutView.kt b/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/GetAboutView.kt index ce89459b..d804edc1 100644 --- a/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/GetAboutView.kt +++ b/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/GetAboutView.kt @@ -1,6 +1,8 @@ package br.com.sailboat.todozy.feature.about.impl.presentation import android.content.Context +import android.content.pm.PackageManager +import android.os.Build import br.com.sailboat.uicomponent.model.ImageTitleDividerUiModel import br.com.sailboat.uicomponent.model.LabelValueUiModel import br.com.sailboat.uicomponent.model.UiModel @@ -34,7 +36,7 @@ internal class GetAboutView(private val context: Context) : GetAboutViewUseCase private fun getVersion(): LabelValueUiModel { return LabelValueUiModel( label = context.getString(UiR.string.version), - value = "1.5.0", + value = context.appVersionName(), ) } @@ -45,3 +47,24 @@ internal class GetAboutView(private val context: Context) : GetAboutViewUseCase ) } } + +private fun Context.appVersionName(): String { + val packageManager = packageManager + val packageName = packageName + val packageInfo = + runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) + } else { + @Suppress("DEPRECATION") + packageManager.getPackageInfo(packageName, 0) + } + }.getOrNull() + val longVersionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + packageInfo?.longVersionCode + } else { + @Suppress("DEPRECATION") + packageInfo?.versionCode?.toLong() + } + return packageInfo?.versionName ?: longVersionCode?.toString().orEmpty() +} diff --git a/feature/settings/impl/src/main/java/br/com/sailboat/todozy/feature/settings/impl/presentation/SettingsFragment.kt b/feature/settings/impl/src/main/java/br/com/sailboat/todozy/feature/settings/impl/presentation/SettingsFragment.kt index 74b89136..937f4675 100644 --- a/feature/settings/impl/src/main/java/br/com/sailboat/todozy/feature/settings/impl/presentation/SettingsFragment.kt +++ b/feature/settings/impl/src/main/java/br/com/sailboat/todozy/feature/settings/impl/presentation/SettingsFragment.kt @@ -89,8 +89,6 @@ internal class SettingsFragment : Fragment() { private fun initToolbar() = with(binding) { appbar.toolbar.setTitle(UiR.string.settings) - appbar.toolbar.setNavigationIcon(UiR.drawable.ic_arrow_back_white_24dp) - appbar.toolbar.setNavigationOnClickListener { requireActivity().onBackPressedDispatcher.onBackPressed() } } private fun initToneViews() = activity?.run { diff --git a/feature/task-form/impl/src/main/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/TaskFormFragment.kt b/feature/task-form/impl/src/main/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/TaskFormFragment.kt index d31e828f..8efaa47e 100644 --- a/feature/task-form/impl/src/main/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/TaskFormFragment.kt +++ b/feature/task-form/impl/src/main/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/TaskFormFragment.kt @@ -17,6 +17,7 @@ import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import br.com.sailboat.todozy.domain.model.RepeatType import br.com.sailboat.todozy.feature.task.form.impl.databinding.FragmentTaskFormBinding +import br.com.sailboat.todozy.feature.task.form.impl.presentation.extension.toUtcStartOfDayInMillis import br.com.sailboat.todozy.feature.task.form.impl.presentation.viewmodel.TaskFormViewAction import br.com.sailboat.todozy.feature.task.form.impl.presentation.viewmodel.TaskFormViewIntent.OnClickAddAlarm import br.com.sailboat.todozy.feature.task.form.impl.presentation.viewmodel.TaskFormViewIntent.OnClickAlarmDate @@ -206,7 +207,7 @@ internal class TaskFormFragment : Fragment() { val picker = MaterialDatePicker.Builder.datePicker() .setTitleText(getString(UiR.string.label_date)) - .setSelection(action.currentDate.timeInMillis) + .setSelection(action.currentDate.toUtcStartOfDayInMillis()) .build() picker.addOnPositiveButtonClickListener { selection -> diff --git a/feature/task-form/impl/src/main/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/extension/CalendarExtensions.kt b/feature/task-form/impl/src/main/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/extension/CalendarExtensions.kt new file mode 100644 index 00000000..383525df --- /dev/null +++ b/feature/task-form/impl/src/main/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/extension/CalendarExtensions.kt @@ -0,0 +1,19 @@ +package br.com.sailboat.todozy.feature.task.form.impl.presentation.extension + +import java.util.Calendar +import java.util.TimeZone + +internal fun Calendar.toUtcStartOfDayInMillis(): Long { + val utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + utcCalendar.clear() + utcCalendar.set( + get(Calendar.YEAR), + get(Calendar.MONTH), + get(Calendar.DAY_OF_MONTH), + 0, + 0, + 0, + ) + utcCalendar.set(Calendar.MILLISECOND, 0) + return utcCalendar.timeInMillis +} diff --git a/feature/task-form/impl/src/test/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/extension/CalendarExtensionsTest.kt b/feature/task-form/impl/src/test/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/extension/CalendarExtensionsTest.kt new file mode 100644 index 00000000..7d621486 --- /dev/null +++ b/feature/task-form/impl/src/test/java/br/com/sailboat/todozy/feature/task/form/impl/presentation/extension/CalendarExtensionsTest.kt @@ -0,0 +1,45 @@ +package br.com.sailboat.todozy.feature.task.form.impl.presentation.extension + +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.util.Calendar +import java.util.TimeZone + +internal class CalendarExtensionsTest { + private var defaultTimeZone: TimeZone? = null + + @Before + fun setUp() { + defaultTimeZone = TimeZone.getDefault() + } + + @After + fun tearDown() { + defaultTimeZone?.let { TimeZone.setDefault(it) } + } + + @Test + fun `toUtcStartOfDayInMillis keeps local date when timezone pushes time past utc midnight`() { + TimeZone.setDefault(TimeZone.getTimeZone("GMT+10:00")) + val localCalendar = + Calendar.getInstance().apply { + set(2024, Calendar.APRIL, 10, 23, 45, 0) + set(Calendar.MILLISECOND, 0) + } + + val utcStartOfDayMillis = localCalendar.toUtcStartOfDayInMillis() + + val utcCalendar = + Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { + timeInMillis = utcStartOfDayMillis + } + + assertEquals(2024, utcCalendar.get(Calendar.YEAR)) + assertEquals(Calendar.APRIL, utcCalendar.get(Calendar.MONTH)) + assertEquals(10, utcCalendar.get(Calendar.DAY_OF_MONTH)) + assertEquals(0, utcCalendar.get(Calendar.HOUR_OF_DAY)) + assertEquals(0, utcCalendar.get(Calendar.MINUTE)) + } +} diff --git a/feature/task-history/impl/src/main/java/br/com/sailboat/todozy/feature/task/history/impl/presentation/TaskHistoryFragment.kt b/feature/task-history/impl/src/main/java/br/com/sailboat/todozy/feature/task/history/impl/presentation/TaskHistoryFragment.kt index 0bd95944..c966bd09 100644 --- a/feature/task-history/impl/src/main/java/br/com/sailboat/todozy/feature/task/history/impl/presentation/TaskHistoryFragment.kt +++ b/feature/task-history/impl/src/main/java/br/com/sailboat/todozy/feature/task/history/impl/presentation/TaskHistoryFragment.kt @@ -377,8 +377,6 @@ internal class TaskHistoryFragment : Fragment(), SearchMenu by SearchMenuImpl() private fun initToolbar() { val toolbar = binding.appbarTaskHistory.toolbarScroll.toolbar (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) - toolbar.setNavigationIcon(UiR.drawable.ic_arrow_back_white_24dp) - toolbar.setNavigationOnClickListener { requireActivity().onBackPressedDispatcher.onBackPressed() } toolbar.setTitle(UiR.string.history) } diff --git a/feature/task-list/impl/src/main/java/br/com/sailboat/todozy/feature/task/list/impl/presentation/viewmodel/TaskListViewModel.kt b/feature/task-list/impl/src/main/java/br/com/sailboat/todozy/feature/task/list/impl/presentation/viewmodel/TaskListViewModel.kt index 5de79e36..a8f0705c 100644 --- a/feature/task-list/impl/src/main/java/br/com/sailboat/todozy/feature/task/list/impl/presentation/viewmodel/TaskListViewModel.kt +++ b/feature/task-list/impl/src/main/java/br/com/sailboat/todozy/feature/task/list/impl/presentation/viewmodel/TaskListViewModel.kt @@ -119,6 +119,7 @@ internal class TaskListViewModel( try { viewState.tasksLoading.postValue(true) viewState.taskProgressLoading.postValue(true) + clearProgressCache() if (closeNotifications) { viewState.viewAction.postValue(TaskListViewAction.CloseNotifications) } diff --git a/feature/task-list/impl/src/test/java/br/com/sailboat/todozy/feature/task/list/impl/presentation/viewmodel/TaskListViewModelTest.kt b/feature/task-list/impl/src/test/java/br/com/sailboat/todozy/feature/task/list/impl/presentation/viewmodel/TaskListViewModelTest.kt index b75e1d8e..b71f59f8 100644 --- a/feature/task-list/impl/src/test/java/br/com/sailboat/todozy/feature/task/list/impl/presentation/viewmodel/TaskListViewModelTest.kt +++ b/feature/task-list/impl/src/test/java/br/com/sailboat/todozy/feature/task/list/impl/presentation/viewmodel/TaskListViewModelTest.kt @@ -5,6 +5,7 @@ import br.com.sailboat.todozy.domain.model.Task import br.com.sailboat.todozy.domain.model.TaskCategory import br.com.sailboat.todozy.domain.model.TaskFilter import br.com.sailboat.todozy.domain.model.TaskMetrics +import br.com.sailboat.todozy.domain.model.TaskProgressDay import br.com.sailboat.todozy.domain.model.TaskProgressRange import br.com.sailboat.todozy.domain.model.TaskStatus import br.com.sailboat.todozy.feature.alarm.domain.usecase.ScheduleAllAlarmsUseCase @@ -505,6 +506,55 @@ internal class TaskListViewModelTest { ) } + @Test + fun `should reload progress after midnight instead of using cache`() = runTest(coroutinesTestRule.dispatcher) { + val initialProgress = + listOf( + TaskProgressDay( + date = LocalDate.now().minusDays(1), + doneCount = 1, + notDoneCount = 0, + totalCount = 1, + ), + ) + val refreshedProgress = + listOf( + TaskProgressDay( + date = LocalDate.now(), + doneCount = 2, + notDoneCount = 0, + totalCount = 2, + ), + ) + + val todayTask = + Task( + id = 42L, + name = "Task", + notes = null, + ) + coEvery { getTasksUseCase(match { it.category == TaskCategory.TODAY }) } returns + Result.success(listOf(todayTask)) + coEvery { getTasksUseCase(match { it.category != TaskCategory.TODAY }) } returns Result.success(emptyList()) + coEvery { taskListUiModelFactory.create(any(), any()) } returns + listOf(TaskUiModel(taskId = todayTask.id, taskName = todayTask.name)) + coEvery { getTaskMetricsUseCase(any()) } returns Result.success(TaskMetrics(0, 0, 0)) + coEvery { getTaskProgressUseCase(any()) } returnsMany + listOf(Result.success(initialProgress), Result.success(refreshedProgress)) + + viewModel.dispatchViewIntent(TaskListViewIntent.OnStart) + advanceUntilIdle() + + assertEquals(initialProgress, viewModel.viewState.taskProgressDays.value) + + setLastFullLoadDate(LocalDate.now().minusDays(1)) + viewModel.dispatchViewIntent(TaskListViewIntent.OnResume(forceReload = false)) + advanceUntilIdle() + + assertEquals(refreshedProgress, viewModel.viewState.taskProgressDays.value) + coVerify(exactly = 2) { getTaskProgressUseCase(any()) } + } + private fun setLastFullLoadDate(date: LocalDate) { val field = TaskListViewModel::class.java.getDeclaredField("lastFullLoadDate") field.isAccessible = true From c9e7dc417effd337cd66cd4d995f37bd9c075ebb Mon Sep 17 00:00:00 2001 From: Brayan Bedritchuk Date: Thu, 11 Dec 2025 14:17:37 -0300 Subject: [PATCH 2/2] Fix ktlint --- .../main/java/br/com/sailboat/todozy/home/HomeActivity.kt | 7 +++++-- .../br/com/sailboat/todozy/domain/model/RepeatType.kt | 3 ++- .../com/sailboat/todozy/domain/model/TaskProgressRange.kt | 3 ++- .../br/com/sailboat/todozy/domain/model/TaskStatus.kt | 6 +++--- .../feature/about/impl/presentation/AboutAdapter.kt | 8 +++++--- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/br/com/sailboat/todozy/home/HomeActivity.kt b/app/src/main/java/br/com/sailboat/todozy/home/HomeActivity.kt index 518e0622..fccd84cd 100644 --- a/app/src/main/java/br/com/sailboat/todozy/home/HomeActivity.kt +++ b/app/src/main/java/br/com/sailboat/todozy/home/HomeActivity.kt @@ -92,7 +92,7 @@ class HomeActivity : AppCompatActivity(), HomeTabNavigator { icon = { Icon( imageVector = item.icon, - contentDescription = stringResource(id = item.title) + contentDescription = stringResource(id = item.title), ) }, label = { Text(text = stringResource(id = item.title)) }, @@ -119,7 +119,10 @@ class HomeActivity : AppCompatActivity(), HomeTabNavigator { selectTab(itemId, allowReselectPop = true) } - private fun selectTab(itemId: Int, allowReselectPop: Boolean) { + private fun selectTab( + itemId: Int, + allowReselectPop: Boolean, + ) { if (itemId == AppR.id.nav_history) { notifyHistoryTabSelected() } diff --git a/domain/src/main/java/br/com/sailboat/todozy/domain/model/RepeatType.kt b/domain/src/main/java/br/com/sailboat/todozy/domain/model/RepeatType.kt index 4166cf06..9b786781 100644 --- a/domain/src/main/java/br/com/sailboat/todozy/domain/model/RepeatType.kt +++ b/domain/src/main/java/br/com/sailboat/todozy/domain/model/RepeatType.kt @@ -9,7 +9,8 @@ enum class RepeatType { SECOND, MINUTE, HOUR, - CUSTOM; + CUSTOM, + ; companion object { fun isAlarmRepeating(alarm: Alarm): Boolean { diff --git a/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskProgressRange.kt b/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskProgressRange.kt index 88df7342..2a401436 100644 --- a/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskProgressRange.kt +++ b/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskProgressRange.kt @@ -6,7 +6,8 @@ enum class TaskProgressRange { ALL, LAST_YEAR, LAST_30_DAYS, - LAST_7_DAYS; + LAST_7_DAYS, + ; fun startDate(today: LocalDate): LocalDate = when (this) { ALL -> today diff --git a/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskStatus.kt b/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskStatus.kt index 9e4c8fed..586021fa 100644 --- a/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskStatus.kt +++ b/domain/src/main/java/br/com/sailboat/todozy/domain/model/TaskStatus.kt @@ -2,10 +2,10 @@ package br.com.sailboat.todozy.domain.model enum class TaskStatus(val id: Int) { NOT_DONE(0), - DONE(1); + DONE(1), + ; companion object { - fun getById(id: Int): TaskStatus = - TaskStatus.entries.firstOrNull { it.id == id } ?: NOT_DONE + fun getById(id: Int): TaskStatus = TaskStatus.entries.firstOrNull { it.id == id } ?: NOT_DONE } } diff --git a/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/AboutAdapter.kt b/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/AboutAdapter.kt index eabf7835..a91ac9ba 100644 --- a/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/AboutAdapter.kt +++ b/feature/about/impl/src/main/java/br/com/sailboat/todozy/feature/about/impl/presentation/AboutAdapter.kt @@ -13,9 +13,12 @@ import br.com.sailboat.uicomponent.model.UiModel import br.com.sailboat.uicomponent.model.UiModelType internal class AboutAdapter : ListAdapter( - UiModelDiffUtilCallback() + UiModelDiffUtilCallback(), ) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ) = when (viewType) { UiModelType.IMAGE_TITLE.ordinal -> ImageTitleDividerViewHolder(parent) UiModelType.LABEL_VALUE.ordinal -> LabelValueViewHolder(parent) else -> EmptyViewHolder(parent) @@ -32,5 +35,4 @@ internal class AboutAdapter : ListAdapter( } override fun getItemViewType(position: Int) = getItem(position).uiModelId - }