Merge category rules and apps

This commit is contained in:
Jonas Lochmann 2020-10-26 01:00:00 +01:00
parent 75dcb69fc4
commit cdb08b89b4
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
8 changed files with 46 additions and 219 deletions

View file

@ -18,38 +18,21 @@ package io.timelimit.android.ui.manage.category
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.LiveData
import androidx.navigation.NavController
import androidx.navigation.Navigation
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.data.model.Category
import io.timelimit.android.data.model.User
import io.timelimit.android.extensions.safeNavigate import io.timelimit.android.extensions.safeNavigate
import io.timelimit.android.livedata.liveDataFromValue import io.timelimit.android.ui.fragment.CategoryFragmentWrapper
import io.timelimit.android.livedata.map
import io.timelimit.android.livedata.switchMap
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.main.ActivityViewModelHolder
import io.timelimit.android.ui.main.AuthenticationFab
import io.timelimit.android.ui.main.FragmentWithCustomTitle import io.timelimit.android.ui.main.FragmentWithCustomTitle
import io.timelimit.android.ui.manage.category.apps.CategoryAppsFragment import io.timelimit.android.ui.manage.category.appsandrules.CombinedAppsAndRulesFragment
import io.timelimit.android.ui.manage.category.timelimit_rules.CategoryTimeLimitRulesFragment
import kotlinx.android.synthetic.main.fragment_manage_category.*
class ManageCategoryFragment : Fragment(), FragmentWithCustomTitle { class ManageCategoryFragment : CategoryFragmentWrapper(), FragmentWithCustomTitle {
private val params: ManageCategoryFragmentArgs by lazy { ManageCategoryFragmentArgs.fromBundle(requireArguments()) } private val params: ManageCategoryFragmentArgs by lazy { ManageCategoryFragmentArgs.fromBundle(requireArguments()) }
private val logic: AppLogic by lazy { DefaultAppLogic.with(requireContext()) } override val childId: String get() = params.childId
private val category: LiveData<Category?> by lazy { override val categoryId: String get() = params.categoryId
logic.database.category()
.getCategoryByChildIdAndId(params.childId, params.categoryId) override fun createChildFragment(): Fragment = CombinedAppsAndRulesFragment.newInstance(
} childId = childId,
private val user: LiveData<User?> by lazy { categoryId = categoryId
logic.database.user().getUserByIdLive(params.childId) )
}
private val activity: ActivityViewModelHolder by lazy { getActivity() as ActivityViewModelHolder }
lateinit var navigation: NavController
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -57,62 +40,6 @@ class ManageCategoryFragment : Fragment(), FragmentWithCustomTitle {
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_manage_category, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
AuthenticationFab.manageAuthenticationFab(
fab = fab,
fragment = this,
doesSupportAuth = liveDataFromValue(true),
authenticatedUser = activity.getActivityViewModel().authenticatedUser,
shouldHighlight = activity.getActivityViewModel().shouldHighlightAuthenticationButton
)
fab.setOnClickListener { activity.showAuthenticationScreen() }
navigation = Navigation.findNavController(view)
bottom_navigation_view.setOnNavigationItemReselectedListener { /* ignore */ }
bottom_navigation_view.setOnNavigationItemSelectedListener { menuItem ->
if (childFragmentManager.isStateSaved) {
false
} else {
childFragmentManager.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.container, when (menuItem.itemId) {
R.id.manage_category_tab_apps -> CategoryAppsFragment.newInstance(params)
R.id.manage_category_tab_time_limit_rules -> CategoryTimeLimitRulesFragment.newInstance(params)
else -> throw IllegalStateException()
})
.commit()
true
}
}
if (childFragmentManager.findFragmentById(R.id.container) == null) {
childFragmentManager.beginTransaction()
.replace(R.id.container, CategoryAppsFragment.newInstance(params))
.commit()
}
category.observe(viewLifecycleOwner) {
if (it == null) {
navigation.popBackStack()
}
}
}
override fun getCustomTitle(): LiveData<String?> = user.switchMap { user ->
category.map { category ->
"${category?.title} < ${user?.name} < ${getString(R.string.main_tab_overview)}" as String?
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)

View file

@ -40,6 +40,7 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
private const val EXPAND_RULES_ITEM = 5 private const val EXPAND_RULES_ITEM = 5
private const val RULES_INTRO = 6 private const val RULES_INTRO = 6
private const val ADD_RULE_ITEM = 7 private const val ADD_RULE_ITEM = 7
private const val HEADLINE = 8
} }
var items: List<AppAndRuleItem> by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() } var items: List<AppAndRuleItem> by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() }
@ -69,6 +70,7 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
AppAndRuleItem.ExpandRulesItem -> EXPAND_RULES_ITEM AppAndRuleItem.ExpandRulesItem -> EXPAND_RULES_ITEM
AppAndRuleItem.RulesIntro -> RULES_INTRO AppAndRuleItem.RulesIntro -> RULES_INTRO
AppAndRuleItem.AddRuleItem -> ADD_RULE_ITEM AppAndRuleItem.AddRuleItem -> ADD_RULE_ITEM
is AppAndRuleItem.Headline -> HEADLINE
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder = Holder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder = Holder(
@ -84,7 +86,6 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
false false
).apply { ).apply {
label = parent.context.getString(R.string.category_apps_add_dialog_btn_positive) label = parent.context.getString(R.string.category_apps_add_dialog_btn_positive)
wide = true
root.setOnClickListener { handlers?.onAddAppsClicked() } root.setOnClickListener { handlers?.onAddAppsClicked() }
}.root }.root
@ -114,10 +115,14 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
false false
).apply { ).apply {
label = parent.context.getString(R.string.category_time_limit_rule_dialog_new) label = parent.context.getString(R.string.category_time_limit_rule_dialog_new)
wide = true
root.setOnClickListener { handlers?.onAddTimeLimitRuleClicked() } root.setOnClickListener { handlers?.onAddTimeLimitRuleClicked() }
}.root }.root
HEADLINE -> GenericListHeaderBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).also { it.root.tag = it }.root
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
) )
@ -186,6 +191,11 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
AppAndRuleItem.ExpandRulesItem -> {/* nothing to do */} AppAndRuleItem.ExpandRulesItem -> {/* nothing to do */}
AppAndRuleItem.RulesIntro -> {/* nothing to do */} AppAndRuleItem.RulesIntro -> {/* nothing to do */}
AppAndRuleItem.AddRuleItem -> {/* nothing to do */} AppAndRuleItem.AddRuleItem -> {/* nothing to do */}
is AppAndRuleItem.Headline -> {
val binding = holder.itemView.tag as GenericListHeaderBinding
binding.text = holder.itemView.context.getString(item.stringRessource)
}
}.let { } }.let { }
} }

View file

@ -26,4 +26,5 @@ sealed class AppAndRuleItem {
object ExpandRulesItem: AppAndRuleItem() object ExpandRulesItem: AppAndRuleItem()
object RulesIntro: AppAndRuleItem() object RulesIntro: AppAndRuleItem()
object AddRuleItem: AppAndRuleItem() object AddRuleItem: AppAndRuleItem()
data class Headline(val stringRessource: Int): AppAndRuleItem()
} }

View file

@ -19,6 +19,7 @@ package io.timelimit.android.ui.manage.category.appsandrules
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import io.timelimit.android.R
import io.timelimit.android.data.extensions.getDateLive import io.timelimit.android.data.extensions.getDateLive
import io.timelimit.android.data.model.HintsToShow import io.timelimit.android.data.model.HintsToShow
import io.timelimit.android.extensions.takeDistributedElements import io.timelimit.android.extensions.takeDistributedElements
@ -83,7 +84,7 @@ class AppsAndRulesModel(application: Application): AndroidViewModel(application)
private val hasHiddenRuleIntro = database.config().wereHintsShown(HintsToShow.TIME_LIMIT_RULE_INTRODUCTION) private val hasHiddenRuleIntro = database.config().wereHintsShown(HintsToShow.TIME_LIMIT_RULE_INTRODUCTION)
val fullRuleScreenContent = visibleRuleItems.switchMap { visibleRuleItems -> private val fullRuleScreenContent = visibleRuleItems.switchMap { visibleRuleItems ->
hasHiddenRuleIntro.map { hasHiddenRuleIntro -> hasHiddenRuleIntro.map { hasHiddenRuleIntro ->
if (hasHiddenRuleIntro) { if (hasHiddenRuleIntro) {
visibleRuleItems visibleRuleItems
@ -115,7 +116,7 @@ class AppsAndRulesModel(application: Application): AndroidViewModel(application)
}.sortedBy { it.title.toLowerCase(Locale.US) } }.sortedBy { it.title.toLowerCase(Locale.US) }
} }
val fullAppScreenContent = showAllAppsLive.switchMap { showAllApps -> private val fullAppScreenContent = showAllAppsLive.switchMap { showAllApps ->
if (showAllApps) if (showAllApps)
appEntries.map { it + listOf(AppAndRuleItem.AddAppItem) } appEntries.map { it + listOf(AppAndRuleItem.AddAppItem) }
else else
@ -129,6 +130,13 @@ class AppsAndRulesModel(application: Application): AndroidViewModel(application)
} }
} }
val combinedList = fullAppScreenContent.switchMap { apps ->
fullRuleScreenContent.map { rules ->
listOf(AppAndRuleItem.Headline(R.string.category_apps_title)) + apps +
listOf(AppAndRuleItem.Headline(R.string.category_time_limit_rules)) + rules
}
}
fun init(userId: String, categoryId: String) { fun init(userId: String, categoryId: String) {
if (didInit) return; didInit = true if (didInit) return; didInit = true

View file

@ -13,27 +13,30 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package io.timelimit.android.ui.manage.category.apps package io.timelimit.android.ui.manage.category.appsandrules
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import io.timelimit.android.ui.manage.category.ManageCategoryFragmentArgs
import io.timelimit.android.ui.manage.category.appsandrules.CategoryAppsAndRulesFragment
class CategoryAppsFragment : CategoryAppsAndRulesFragment() { class CombinedAppsAndRulesFragment : CategoryAppsAndRulesFragment() {
companion object { companion object {
fun newInstance(params: ManageCategoryFragmentArgs): CategoryAppsFragment = CategoryAppsFragment().apply { private const val CHILD_ID = "childId"
arguments = params.toBundle() private const val CATEGORY_ID = "categoryId"
fun newInstance(childId: String, categoryId: String): CombinedAppsAndRulesFragment = CombinedAppsAndRulesFragment().apply {
arguments = Bundle().apply {
putString(CHILD_ID, childId)
putString(CATEGORY_ID, categoryId)
}
} }
} }
private val params: ManageCategoryFragmentArgs by lazy { ManageCategoryFragmentArgs.fromBundle(requireArguments()) } override val categoryId: String get() = requireArguments().getString(CATEGORY_ID)!!
override val categoryId: String get() = params.categoryId override val childId: String get() = requireArguments().getString(CHILD_ID)!!
override val childId: String get() = params.childId
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
model.fullAppScreenContent.observe(viewLifecycleOwner) { setListContent(it) } model.combinedList.observe(viewLifecycleOwner) { setListContent(it) }
} }
} }

View file

@ -1,41 +0,0 @@
/*
* TimeLimit Copyright <C> 2019 - 2020 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.manage.category.timelimit_rules
import android.os.Bundle
import android.view.View
import io.timelimit.android.ui.manage.category.ManageCategoryFragmentArgs
import io.timelimit.android.ui.manage.category.appsandrules.CategoryAppsAndRulesFragment
class CategoryTimeLimitRulesFragment: CategoryAppsAndRulesFragment() {
companion object {
fun newInstance(params: ManageCategoryFragmentArgs): CategoryTimeLimitRulesFragment {
val result = CategoryTimeLimitRulesFragment()
result.arguments = params.toBundle()
return result
}
}
private val params: ManageCategoryFragmentArgs by lazy { ManageCategoryFragmentArgs.fromBundle(requireArguments()) }
override val childId: String get() = params.childId
override val categoryId: String get() = params.categoryId
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.fullRuleScreenContent.observe(viewLifecycleOwner) { setListContent(it) }
}
}

View file

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 2019 - 2020 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/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".ui.manage.category.ManageCategoryFragment">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
app:fabSize="normal"
android:src="@drawable/ic_lock_open_white_24dp"
android:layout_margin="16dp"
android:layout_gravity="end|bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
app:menu="@menu/fragment_manage_category_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 2019 - 2020 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/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:icon="@drawable/ic_apps_white_24dp"
android:id="@+id/manage_category_tab_apps"
android:title="@string/category_apps_title"
app:showAsAction="always"
/>
<item
android:icon="@drawable/ic_alarm_white_24dp"
android:id="@+id/manage_category_tab_time_limit_rules"
android:title="@string/category_time_limit_rules"
app:showAsAction="always"
/>
</menu>