diff --git a/app/src/main/java/io/timelimit/android/data/dao/CategoryDao.kt b/app/src/main/java/io/timelimit/android/data/dao/CategoryDao.kt index 10251ca..e22c3ad 100644 --- a/app/src/main/java/io/timelimit/android/data/dao/CategoryDao.kt +++ b/app/src/main/java/io/timelimit/android/data/dao/CategoryDao.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2020 Jonas Lochmann + * TimeLimit Copyright 2019 - 2023 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ import io.timelimit.android.data.customtypes.ImmutableBitmask import io.timelimit.android.data.customtypes.ImmutableBitmaskAdapter import io.timelimit.android.data.model.Category import io.timelimit.android.livedata.map +import kotlinx.coroutines.flow.Flow import java.util.* @Dao @@ -40,6 +41,9 @@ abstract class CategoryDao { @Query("SELECT * FROM category WHERE id = :categoryId") abstract fun getCategoryByIdSync(categoryId: String): Category? + @Query("SELECT * FROM category WHERE id = :categoryId") + abstract fun getCategoryByIdFlow(categoryId: String): Flow + @Query("SELECT * FROM category WHERE child_id = :childId") abstract fun getCategoriesByChildIdSync(childId: String): List diff --git a/app/src/main/java/io/timelimit/android/ui/model/MainModel.kt b/app/src/main/java/io/timelimit/android/ui/model/MainModel.kt index cbd3980..ddb5015 100644 --- a/app/src/main/java/io/timelimit/android/ui/model/MainModel.kt +++ b/app/src/main/java/io/timelimit/android/ui/model/MainModel.kt @@ -108,8 +108,7 @@ class MainModel(application: Application): AndroidViewModel(application) { val screen: Flow = state.splitConflated( Case.simple<_, _, State.LaunchState> { LaunchHandling.processLaunchState(state, logic) }, Case.simple<_, _, State.Overview> { OverviewHandling.processState(logic, scope, activityCommandInternal, authenticationModelApi, state) }, - Case.simple<_, _, State.ManageChild.Main> { state -> ManageChildHandling.processState(logic, state, updateMethod(::updateState)) }, - Case.simple<_, _, State.ManageChild.Apps> { state -> ManageChildHandling.processState(logic, state, updateMethod(::updateState)) }, + Case.simple<_, _, State.ManageChild> { state -> ManageChildHandling.processState(logic, state, updateMethod(::updateState)) }, Case.simple<_, _, State.DiagnoseScreen.DeviceOwner> { DeviceOwnerHandling.processState(logic, scope, authenticationModelApi, state) }, Case.simple<_, _, FragmentState> { state -> state.transform { diff --git a/app/src/main/java/io/timelimit/android/ui/model/Screen.kt b/app/src/main/java/io/timelimit/android/ui/model/Screen.kt index d0a1f47..8147766 100644 --- a/app/src/main/java/io/timelimit/android/ui/model/Screen.kt +++ b/app/src/main/java/io/timelimit/android/ui/model/Screen.kt @@ -75,6 +75,82 @@ sealed class Screen( override val title = Title.StringResource(R.string.child_apps_title) } + class ManageChildAdvancedScreen( + state: State, + toolbarIcons: List, + toolbarOptions: List, + fragment: FragmentState, + containerId: Int, + override val backStack: List + ): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle { + override val title = Title.StringResource(R.string.manage_child_tab_other) + } + + class ManageChildContactsScreen( + state: State, + toolbarIcons: List, + toolbarOptions: List, + fragment: FragmentState, + containerId: Int, + override val backStack: List + ): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle { + override val title = Title.StringResource(R.string.contacts_title_long) + } + + class ManageChildUsageHistory( + state: State, + toolbarIcons: List, + toolbarOptions: List, + fragment: FragmentState, + containerId: Int, + override val backStack: List + ): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle { + override val title = Title.StringResource(R.string.usage_history_title) + } + + class ManageChildUsageTasks( + state: State, + toolbarIcons: List, + toolbarOptions: List, + fragment: FragmentState, + containerId: Int, + override val backStack: List + ): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle { + override val title = Title.StringResource(R.string.manage_child_tasks) + } + class ManageCategory( + state: State, + toolbarIcons: List, + toolbarOptions: List, + fragment: FragmentState, + containerId: Int, + val categoryName: String, + override val backStack: List + ): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle { + override val title = Title.Plain(categoryName) + } + class ManageCategoryAdvanced( + state: State, + toolbarIcons: List, + toolbarOptions: List, + fragment: FragmentState, + containerId: Int, + override val backStack: List + ): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle { + override val title = Title.StringResource(R.string.category_settings) + } + + class ManageBlockedTimes( + state: State, + toolbarIcons: List, + toolbarOptions: List, + fragment: FragmentState, + containerId: Int, + override val backStack: List + ): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle { + override val title = Title.StringResource(R.string.blocked_time_areas) + } + class DeviceOwnerScreen( state: State, val content: DeviceOwnerHandling.OwnerScreen, diff --git a/app/src/main/java/io/timelimit/android/ui/model/State.kt b/app/src/main/java/io/timelimit/android/ui/model/State.kt index 4f17af3..391e5db 100644 --- a/app/src/main/java/io/timelimit/android/ui/model/State.kt +++ b/app/src/main/java/io/timelimit/android/ui/model/State.kt @@ -117,7 +117,7 @@ sealed class State (val previous: State?): Serializable { sealed class Sub( previous: State, - previousMain: Main, + val previousMain: Main, fragmentClass: Class ): ManageChild(previous, fragmentClass, previousMain.childId, previousMain.previousOverview) @@ -139,11 +139,16 @@ sealed class State (val previous: State?): Serializable { override val arguments: Bundle = ChildTasksFragmentWrapperArgs(previousChild.childId).toBundle() } - sealed class ManageCategory(previous: State, val previousChild: ManageChild.Main, fragmentClass: Class): Sub(previous, previousChild, fragmentClass) { + sealed class ManageCategory( + previous: State, + val previousChild: ManageChild.Main, + val categoryId: String, + fragmentClass: Class + ): Sub(previous, previousChild, fragmentClass) { class Main( previousChild: ManageChild.Main, - val categoryId: String - ): ManageCategory(previousChild, previousChild, ManageCategoryFragment::class.java) { + categoryId: String + ): ManageCategory(previousChild, previousChild, categoryId, ManageCategoryFragment::class.java) { @Transient override val arguments: Bundle = ManageCategoryFragmentArgs( childId = previousChild.childId, @@ -158,8 +163,10 @@ sealed class State (val previous: State?): Serializable { } sealed class Sub( - previous: State, previousMain: Main, fragmentClass: Class - ): ManageCategory(previous, previousMain.previousChild, fragmentClass) + previous: State, + val previousCategory: Main, + fragmentClass: Class + ): ManageCategory(previous, previousCategory.previousChild, previousCategory.categoryId, fragmentClass) class BlockedTimes( previousCategory: Main diff --git a/app/src/main/java/io/timelimit/android/ui/model/managechild/ManageCategoryHandling.kt b/app/src/main/java/io/timelimit/android/ui/model/managechild/ManageCategoryHandling.kt new file mode 100644 index 0000000..3f60f11 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/model/managechild/ManageCategoryHandling.kt @@ -0,0 +1,116 @@ +/* + * TimeLimit Copyright 2019 - 2023 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.ui.model.managechild + +import io.timelimit.android.R +import io.timelimit.android.data.model.Category +import io.timelimit.android.logic.AppLogic +import io.timelimit.android.ui.model.BackStackItem +import io.timelimit.android.ui.model.Screen +import io.timelimit.android.ui.model.State +import io.timelimit.android.ui.model.Title +import io.timelimit.android.ui.model.flow.Case +import io.timelimit.android.ui.model.flow.splitConflated +import kotlinx.coroutines.flow.* + +object ManageCategoryHandling { + fun processState( + logic: AppLogic, + stateLive: Flow, + parentBackStackLive: Flow>, + updateState: ((State.ManageChild.ManageCategory) -> State) -> Unit + ): Flow = stateLive.splitConflated( + Case.withKey<_, _, State.ManageChild.ManageCategory, _>( + withKey = { Pair(it.childId, it.categoryId) }, + producer = { (childId, categoryId), state -> + val categoryLive = logic.database.category().getCategoryByIdFlow(categoryId) + + val hasCategoryLive = categoryLive.map { it?.childId == childId }.distinctUntilChanged() + val foundCategoryLive = categoryLive.filterNotNull() + + hasCategoryLive.transformLatest { hasCategory -> + if (hasCategory) emitAll( + state.splitConflated( + Case.simple<_, _, State.ManageChild.ManageCategory.Main> { processMainState(it, parentBackStackLive, foundCategoryLive) }, + Case.simple<_, _, State.ManageChild.ManageCategory.Sub> { processSubState(share(it), parentBackStackLive, foundCategoryLive, updateMethod(updateState)) }, + ) + ) + else updateState { it.previousChild } + } + } + ) + ) + + private fun processMainState( + stateLive: Flow, + parentBackStackLive: Flow>, + categoryLive: Flow + ): Flow = combine(stateLive, categoryLive, parentBackStackLive) { state, category, backStack -> + Screen.ManageCategory( + state, + state.toolbarIcons, + state.toolbarOptions, + state, + R.id.fragment_manage_category, + category.title, + backStack + ) + } + + private fun processSubState( + stateLive: SharedFlow, + parentBackStackLive: Flow>, + categoryLive: Flow, + updateState: ((State.ManageChild.ManageCategory.Sub) -> State) -> Unit + ): Flow { + val subBackStackLive = combine(stateLive, parentBackStackLive, categoryLive) { state, baseBackStack, category -> + baseBackStack + BackStackItem(Title.Plain(category.title)) { updateState { state.previousCategory } } + } + + return stateLive.splitConflated( + Case.simple<_, _, State.ManageChild.ManageCategory.Advanced> { processAdvancedState(it, subBackStackLive) }, + Case.simple<_, _, State.ManageChild.ManageCategory.BlockedTimes> { processBlockedTimesState(it, subBackStackLive) }, + ) + } + + private fun processAdvancedState( + stateLive: Flow, + parentBackStackLive: Flow> + ): Flow = combine(stateLive, parentBackStackLive) { state, backStack -> + Screen.ManageCategoryAdvanced( + state, + state.toolbarIcons, + state.toolbarOptions, + state, + R.id.fragment_manage_category_advanced, + backStack + ) + } + + private fun processBlockedTimesState( + stateLive: Flow, + parentBackStackLive: Flow> + ): Flow = combine(stateLive, parentBackStackLive) { state, backStack -> + Screen.ManageBlockedTimes( + state, + state.toolbarIcons, + state.toolbarOptions, + state, + R.id.fragment_manage_category_blocked_times, + backStack + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/model/managechild/ManageChildHandling.kt b/app/src/main/java/io/timelimit/android/ui/model/managechild/ManageChildHandling.kt index 79e4360..62ba10b 100644 --- a/app/src/main/java/io/timelimit/android/ui/model/managechild/ManageChildHandling.kt +++ b/app/src/main/java/io/timelimit/android/ui/model/managechild/ManageChildHandling.kt @@ -17,6 +17,7 @@ package io.timelimit.android.ui.model.managechild import io.timelimit.android.R import io.timelimit.android.data.model.User +import io.timelimit.android.data.model.UserType import io.timelimit.android.logic.AppLogic import io.timelimit.android.ui.model.BackStackItem import io.timelimit.android.ui.model.Screen @@ -38,7 +39,7 @@ object ManageChildHandling { val state3 = share(state2) val userLive = logic.database.user().getUserByIdFlow(childId) - val hasUserLive = userLive.map { it != null }.distinctUntilChanged() + val hasUserLive = userLive.map { it?.type == UserType.Child }.distinctUntilChanged() val foundUserLive = userLive.filterNotNull() val baseBackStackLive = state3.map { state -> @@ -52,7 +53,7 @@ object ManageChildHandling { hasUserLive.transformLatest { hasUser -> if (hasUser) emitAll(state3.splitConflated( Case.simple<_, _, State.ManageChild.Main> { processMainState(it, baseBackStackLive, foundUserLive) }, - Case.simple<_, _, State.ManageChild.Apps> { processAppsState(share(it), baseBackStackLive, foundUserLive, updateMethod(updateState)) } + Case.simple<_, _, State.ManageChild.Sub> { processSubState(logic, share(it), baseBackStackLive, foundUserLive, updateMethod(updateState)) }, )) else updateState { it.previousOverview } } @@ -76,27 +77,96 @@ object ManageChildHandling { ) } - private fun processAppsState( - stateLive: SharedFlow, - baseBackStackLive: Flow>, + private fun processSubState( + logic: AppLogic, + stateLive: SharedFlow, + parentBackStackLive: Flow>, userLive: Flow, - updateState: ((State.ManageChild.Apps) -> State) -> Unit + updateState: ((State.ManageChild.Sub) -> State) -> Unit ): Flow { - val subBackStackLive = combine(stateLive, baseBackStackLive, userLive) { state, baseBackStack, user -> + val subBackStackLive = combine(stateLive, parentBackStackLive, userLive) { state, baseBackStack, user -> baseBackStack + BackStackItem( Title.Plain(user.name) - ) { updateState { state.previousChild } } + ) { updateState { state.previousMain } } } - return stateLive.combine(subBackStackLive) { state, backStack -> - Screen.ManageChildAppsScreen( - state, - state.toolbarIcons, - state.toolbarOptions, - state, - R.id.fragment_manage_child_apps, - backStack - ) - } + return stateLive.splitConflated( + Case.simple<_, _, State.ManageChild.Apps> { processAppsState(it, subBackStackLive) }, + Case.simple<_, _, State.ManageChild.Advanced> { processAdvancedState(it, subBackStackLive) }, + Case.simple<_, _, State.ManageChild.Contacts> { processContactsState(it, subBackStackLive) }, + Case.simple<_, _, State.ManageChild.UsageHistory> { processUsageHistoryState(it, subBackStackLive) }, + Case.simple<_, _, State.ManageChild.Tasks> { processTasksState(it, subBackStackLive) }, + Case.simple<_, _, State.ManageChild.ManageCategory> { ManageCategoryHandling.processState(logic, it, subBackStackLive, updateMethod(updateState)) }, + ) + } + + private fun processAppsState( + stateLive: Flow, + parentBackStackLive: Flow> + ): Flow = stateLive.combine(parentBackStackLive) { state, backStack -> + Screen.ManageChildAppsScreen( + state, + state.toolbarIcons, + state.toolbarOptions, + state, + R.id.fragment_manage_child_apps, + backStack + ) + } + + private fun processAdvancedState( + stateLive: Flow, + parentBackStackLive: Flow> + ): Flow = stateLive.combine(parentBackStackLive) { state, backStack -> + Screen.ManageChildAdvancedScreen( + state, + state.toolbarIcons, + state.toolbarOptions, + state, + R.id.fragment_manage_child_advanced, + backStack + ) + } + + private fun processContactsState( + stateLive: Flow, + parentBackStackLive: Flow> + ): Flow = stateLive.combine(parentBackStackLive) { state, backStack -> + Screen.ManageChildContactsScreen( + state, + state.toolbarIcons, + state.toolbarOptions, + state, + R.id.fragment_manage_child_contacts, + backStack + ) + } + + private fun processUsageHistoryState( + stateLive: Flow, + parentBackStackLive: Flow> + ): Flow = stateLive.combine(parentBackStackLive) { state, backStack -> + Screen.ManageChildUsageHistory( + state, + state.toolbarIcons, + state.toolbarOptions, + state, + R.id.fragment_manage_child_usage_history, + backStack + ) + } + + private fun processTasksState( + stateLive: Flow, + parentBackStackLive: Flow> + ): Flow = stateLive.combine(parentBackStackLive) { state, backStack -> + Screen.ManageChildUsageTasks( + state, + state.toolbarIcons, + state.toolbarOptions, + state, + R.id.fragment_manage_child_tasks, + backStack + ) } } \ No newline at end of file diff --git a/app/src/main/res/values/fragment_ids.xml b/app/src/main/res/values/fragment_ids.xml index e700c28..0710719 100644 --- a/app/src/main/res/values/fragment_ids.xml +++ b/app/src/main/res/values/fragment_ids.xml @@ -32,4 +32,11 @@ + + + + + + + \ No newline at end of file