Add bottom navigation to all manage child screens

This commit is contained in:
Jonas Lochmann 2023-02-27 01:00:00 +01:00
parent 7f60072b69
commit 446e641f52
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
7 changed files with 306 additions and 27 deletions

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * 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 * 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.customtypes.ImmutableBitmaskAdapter
import io.timelimit.android.data.model.Category import io.timelimit.android.data.model.Category
import io.timelimit.android.livedata.map import io.timelimit.android.livedata.map
import kotlinx.coroutines.flow.Flow
import java.util.* import java.util.*
@Dao @Dao
@ -40,6 +41,9 @@ abstract class CategoryDao {
@Query("SELECT * FROM category WHERE id = :categoryId") @Query("SELECT * FROM category WHERE id = :categoryId")
abstract fun getCategoryByIdSync(categoryId: String): Category? abstract fun getCategoryByIdSync(categoryId: String): Category?
@Query("SELECT * FROM category WHERE id = :categoryId")
abstract fun getCategoryByIdFlow(categoryId: String): Flow<Category?>
@Query("SELECT * FROM category WHERE child_id = :childId") @Query("SELECT * FROM category WHERE child_id = :childId")
abstract fun getCategoriesByChildIdSync(childId: String): List<Category> abstract fun getCategoriesByChildIdSync(childId: String): List<Category>

View file

@ -108,8 +108,7 @@ class MainModel(application: Application): AndroidViewModel(application) {
val screen: Flow<Screen> = state.splitConflated( val screen: Flow<Screen> = state.splitConflated(
Case.simple<_, _, State.LaunchState> { LaunchHandling.processLaunchState(state, logic) }, Case.simple<_, _, State.LaunchState> { LaunchHandling.processLaunchState(state, logic) },
Case.simple<_, _, State.Overview> { OverviewHandling.processState(logic, scope, activityCommandInternal, authenticationModelApi, state) }, 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> { state -> ManageChildHandling.processState(logic, state, updateMethod(::updateState)) },
Case.simple<_, _, State.ManageChild.Apps> { state -> ManageChildHandling.processState(logic, state, updateMethod(::updateState)) },
Case.simple<_, _, State.DiagnoseScreen.DeviceOwner> { DeviceOwnerHandling.processState(logic, scope, authenticationModelApi, state) }, Case.simple<_, _, State.DiagnoseScreen.DeviceOwner> { DeviceOwnerHandling.processState(logic, scope, authenticationModelApi, state) },
Case.simple<_, _, FragmentState> { state -> Case.simple<_, _, FragmentState> { state ->
state.transform { state.transform {

View file

@ -75,6 +75,82 @@ sealed class Screen(
override val title = Title.StringResource(R.string.child_apps_title) override val title = Title.StringResource(R.string.child_apps_title)
} }
class ManageChildAdvancedScreen(
state: State,
toolbarIcons: List<Menu.Icon>,
toolbarOptions: List<Menu.Dropdown>,
fragment: FragmentState,
containerId: Int,
override val backStack: List<BackStackItem>
): 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<Menu.Icon>,
toolbarOptions: List<Menu.Dropdown>,
fragment: FragmentState,
containerId: Int,
override val backStack: List<BackStackItem>
): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle {
override val title = Title.StringResource(R.string.contacts_title_long)
}
class ManageChildUsageHistory(
state: State,
toolbarIcons: List<Menu.Icon>,
toolbarOptions: List<Menu.Dropdown>,
fragment: FragmentState,
containerId: Int,
override val backStack: List<BackStackItem>
): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle {
override val title = Title.StringResource(R.string.usage_history_title)
}
class ManageChildUsageTasks(
state: State,
toolbarIcons: List<Menu.Icon>,
toolbarOptions: List<Menu.Dropdown>,
fragment: FragmentState,
containerId: Int,
override val backStack: List<BackStackItem>
): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle {
override val title = Title.StringResource(R.string.manage_child_tasks)
}
class ManageCategory(
state: State,
toolbarIcons: List<Menu.Icon>,
toolbarOptions: List<Menu.Dropdown>,
fragment: FragmentState,
containerId: Int,
val categoryName: String,
override val backStack: List<BackStackItem>
): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle {
override val title = Title.Plain(categoryName)
}
class ManageCategoryAdvanced(
state: State,
toolbarIcons: List<Menu.Icon>,
toolbarOptions: List<Menu.Dropdown>,
fragment: FragmentState,
containerId: Int,
override val backStack: List<BackStackItem>
): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle {
override val title = Title.StringResource(R.string.category_settings)
}
class ManageBlockedTimes(
state: State,
toolbarIcons: List<Menu.Icon>,
toolbarOptions: List<Menu.Dropdown>,
fragment: FragmentState,
containerId: Int,
override val backStack: List<BackStackItem>
): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle {
override val title = Title.StringResource(R.string.blocked_time_areas)
}
class DeviceOwnerScreen( class DeviceOwnerScreen(
state: State, state: State,
val content: DeviceOwnerHandling.OwnerScreen, val content: DeviceOwnerHandling.OwnerScreen,

View file

@ -117,7 +117,7 @@ sealed class State (val previous: State?): Serializable {
sealed class Sub( sealed class Sub(
previous: State, previous: State,
previousMain: Main, val previousMain: Main,
fragmentClass: Class<out Fragment> fragmentClass: Class<out Fragment>
): ManageChild(previous, fragmentClass, previousMain.childId, previousMain.previousOverview) ): 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() override val arguments: Bundle = ChildTasksFragmentWrapperArgs(previousChild.childId).toBundle()
} }
sealed class ManageCategory(previous: State, val previousChild: ManageChild.Main, fragmentClass: Class<out Fragment>): Sub(previous, previousChild, fragmentClass) { sealed class ManageCategory(
previous: State,
val previousChild: ManageChild.Main,
val categoryId: String,
fragmentClass: Class<out Fragment>
): Sub(previous, previousChild, fragmentClass) {
class Main( class Main(
previousChild: ManageChild.Main, previousChild: ManageChild.Main,
val categoryId: String categoryId: String
): ManageCategory(previousChild, previousChild, ManageCategoryFragment::class.java) { ): ManageCategory(previousChild, previousChild, categoryId, ManageCategoryFragment::class.java) {
@Transient @Transient
override val arguments: Bundle = ManageCategoryFragmentArgs( override val arguments: Bundle = ManageCategoryFragmentArgs(
childId = previousChild.childId, childId = previousChild.childId,
@ -158,8 +163,10 @@ sealed class State (val previous: State?): Serializable {
} }
sealed class Sub( sealed class Sub(
previous: State, previousMain: Main, fragmentClass: Class<out Fragment> previous: State,
): ManageCategory(previous, previousMain.previousChild, fragmentClass) val previousCategory: Main,
fragmentClass: Class<out Fragment>
): ManageCategory(previous, previousCategory.previousChild, previousCategory.categoryId, fragmentClass)
class BlockedTimes( class BlockedTimes(
previousCategory: Main previousCategory: Main

View file

@ -0,0 +1,116 @@
/*
* TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
*/
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<State.ManageChild.ManageCategory>,
parentBackStackLive: Flow<List<BackStackItem>>,
updateState: ((State.ManageChild.ManageCategory) -> State) -> Unit
): Flow<Screen> = 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<State.ManageChild.ManageCategory.Main>,
parentBackStackLive: Flow<List<BackStackItem>>,
categoryLive: Flow<Category>
): Flow<Screen> = 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<State.ManageChild.ManageCategory.Sub>,
parentBackStackLive: Flow<List<BackStackItem>>,
categoryLive: Flow<Category>,
updateState: ((State.ManageChild.ManageCategory.Sub) -> State) -> Unit
): Flow<Screen> {
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<State.ManageChild.ManageCategory.Advanced>,
parentBackStackLive: Flow<List<BackStackItem>>
): Flow<Screen> = 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<State.ManageChild.ManageCategory.BlockedTimes>,
parentBackStackLive: Flow<List<BackStackItem>>
): Flow<Screen> = combine(stateLive, parentBackStackLive) { state, backStack ->
Screen.ManageBlockedTimes(
state,
state.toolbarIcons,
state.toolbarOptions,
state,
R.id.fragment_manage_category_blocked_times,
backStack
)
}
}

View file

@ -17,6 +17,7 @@ package io.timelimit.android.ui.model.managechild
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.data.model.User import io.timelimit.android.data.model.User
import io.timelimit.android.data.model.UserType
import io.timelimit.android.logic.AppLogic import io.timelimit.android.logic.AppLogic
import io.timelimit.android.ui.model.BackStackItem import io.timelimit.android.ui.model.BackStackItem
import io.timelimit.android.ui.model.Screen import io.timelimit.android.ui.model.Screen
@ -38,7 +39,7 @@ object ManageChildHandling {
val state3 = share(state2) val state3 = share(state2)
val userLive = logic.database.user().getUserByIdFlow(childId) 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 foundUserLive = userLive.filterNotNull()
val baseBackStackLive = state3.map { state -> val baseBackStackLive = state3.map { state ->
@ -52,7 +53,7 @@ object ManageChildHandling {
hasUserLive.transformLatest { hasUser -> hasUserLive.transformLatest { hasUser ->
if (hasUser) emitAll(state3.splitConflated( if (hasUser) emitAll(state3.splitConflated(
Case.simple<_, _, State.ManageChild.Main> { processMainState(it, baseBackStackLive, foundUserLive) }, 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 } else updateState { it.previousOverview }
} }
@ -76,19 +77,33 @@ object ManageChildHandling {
) )
} }
private fun processAppsState( private fun processSubState(
stateLive: SharedFlow<State.ManageChild.Apps>, logic: AppLogic,
baseBackStackLive: Flow<List<BackStackItem>>, stateLive: SharedFlow<State.ManageChild.Sub>,
parentBackStackLive: Flow<List<BackStackItem>>,
userLive: Flow<User>, userLive: Flow<User>,
updateState: ((State.ManageChild.Apps) -> State) -> Unit updateState: ((State.ManageChild.Sub) -> State) -> Unit
): Flow<Screen> { ): Flow<Screen> {
val subBackStackLive = combine(stateLive, baseBackStackLive, userLive) { state, baseBackStack, user -> val subBackStackLive = combine(stateLive, parentBackStackLive, userLive) { state, baseBackStack, user ->
baseBackStack + BackStackItem( baseBackStack + BackStackItem(
Title.Plain(user.name) Title.Plain(user.name)
) { updateState { state.previousChild } } ) { updateState { state.previousMain } }
} }
return stateLive.combine(subBackStackLive) { state, 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<State.ManageChild.Apps>,
parentBackStackLive: Flow<List<BackStackItem>>
): Flow<Screen> = stateLive.combine(parentBackStackLive) { state, backStack ->
Screen.ManageChildAppsScreen( Screen.ManageChildAppsScreen(
state, state,
state.toolbarIcons, state.toolbarIcons,
@ -98,5 +113,60 @@ object ManageChildHandling {
backStack backStack
) )
} }
private fun processAdvancedState(
stateLive: Flow<State.ManageChild.Advanced>,
parentBackStackLive: Flow<List<BackStackItem>>
): Flow<Screen> = 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<State.ManageChild.Contacts>,
parentBackStackLive: Flow<List<BackStackItem>>
): Flow<Screen> = 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<State.ManageChild.UsageHistory>,
parentBackStackLive: Flow<List<BackStackItem>>
): Flow<Screen> = 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<State.ManageChild.Tasks>,
parentBackStackLive: Flow<List<BackStackItem>>
): Flow<Screen> = stateLive.combine(parentBackStackLive) { state, backStack ->
Screen.ManageChildUsageTasks(
state,
state.toolbarIcons,
state.toolbarOptions,
state,
R.id.fragment_manage_child_tasks,
backStack
)
} }
} }

View file

@ -32,4 +32,11 @@
<item name="fragment_16" type="id" /> <item name="fragment_16" type="id" />
<item name="fragment_manage_child" type="id" /> <item name="fragment_manage_child" type="id" />
<item name="fragment_manage_child_apps" type="id" /> <item name="fragment_manage_child_apps" type="id" />
<item name="fragment_manage_child_advanced" type="id" />
<item name="fragment_manage_child_contacts" type="id" />
<item name="fragment_manage_child_usage_history" type="id" />
<item name="fragment_manage_child_tasks" type="id" />
<item name="fragment_manage_category" type="id" />
<item name="fragment_manage_category_advanced" type="id" />
<item name="fragment_manage_category_blocked_times" type="id" />
</resources> </resources>