Create AppAndRuleAdapter

This commit is contained in:
Jonas Lochmann 2020-10-26 01:00:00 +01:00
parent 770b5cdf11
commit 8b4e8432f7
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
9 changed files with 236 additions and 22 deletions

View file

@ -23,6 +23,7 @@ import io.timelimit.android.R
import io.timelimit.android.databinding.AddItemViewBinding
import io.timelimit.android.databinding.FragmentCategoryAppsItemBinding
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.manage.category.appsandrules.AppAndRuleItem
import kotlin.properties.Delegates
class AppAdapter: RecyclerView.Adapter<ViewHolder>() {
@ -31,14 +32,14 @@ class AppAdapter: RecyclerView.Adapter<ViewHolder>() {
private const val TYPE_ADD = 2
}
var data: List<AppEntry>? by Delegates.observable(null as List<AppEntry>?) { _, _, _ -> notifyDataSetChanged() }
var handlers: Handlers? by Delegates.observable(null as Handlers?) { _, _, _ -> notifyDataSetChanged() }
var data: List<AppAndRuleItem.AppEntry>? by Delegates.observable(null as List<AppAndRuleItem.AppEntry>?) { _, _, _ -> notifyDataSetChanged() }
var handlers: AppAdapterHandlers? by Delegates.observable(null as AppAdapterHandlers?) { _, _, _ -> notifyDataSetChanged() }
init {
setHasStableIds(true)
}
private fun getItem(position: Int): AppEntry {
private fun getItem(position: Int): AppAndRuleItem.AppEntry {
return data!![position]
}
@ -112,9 +113,7 @@ class AppAdapter: RecyclerView.Adapter<ViewHolder>() {
open class ViewHolder(view: View): RecyclerView.ViewHolder(view)
class AppViewHolder(val binding: FragmentCategoryAppsItemBinding): ViewHolder(binding.root)
data class AppEntry(val title: String, val packageName: String, val packageNameWithoutActivityName: String)
interface Handlers {
fun onAppClicked(app: AppEntry)
interface AppAdapterHandlers {
fun onAppClicked(app: AppAndRuleItem.AppEntry)
fun onAddAppsClicked()
}

View file

@ -33,6 +33,7 @@ import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.getActivityViewModel
import io.timelimit.android.ui.manage.category.ManageCategoryFragmentArgs
import io.timelimit.android.ui.manage.category.apps.add.AddCategoryAppsFragment
import io.timelimit.android.ui.manage.category.appsandrules.AppAndRuleItem
import kotlinx.android.synthetic.main.fragment_category_apps.*
class CategoryAppsFragment : Fragment() {
@ -42,9 +43,9 @@ class CategoryAppsFragment : Fragment() {
}
}
private val params: ManageCategoryFragmentArgs by lazy { ManageCategoryFragmentArgs.fromBundle(arguments!!) }
private val database: Database by lazy { DefaultAppLogic.with(context!!).database }
private val auth: ActivityViewModel by lazy { getActivityViewModel(activity!!) }
private val params: ManageCategoryFragmentArgs by lazy { ManageCategoryFragmentArgs.fromBundle(requireArguments()) }
private val database: Database by lazy { DefaultAppLogic.with(requireContext()).database }
private val auth: ActivityViewModel by lazy { getActivityViewModel(requireActivity()) }
private val model: CategoryAppsModel by lazy {
ViewModelProviders.of(this).get(CategoryAppsModel::class.java)
}
@ -58,8 +59,8 @@ class CategoryAppsFragment : Fragment() {
val adapter = AppAdapter()
adapter.handlers = object: Handlers {
override fun onAppClicked(app: AppEntry) {
adapter.handlers = object: AppAdapterHandlers {
override fun onAppClicked(app: AppAndRuleItem.AppEntry) {
if (auth.tryDispatchParentAction(
RemoveCategoryAppsAction(
categoryId = params.categoryId,
@ -90,7 +91,7 @@ class CategoryAppsFragment : Fragment() {
}
}
recycler.layoutManager = LinearLayoutManager(context!!)
recycler.layoutManager = LinearLayoutManager(requireContext())
recycler.adapter = adapter
model.init(params)

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* 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
@ -22,6 +22,7 @@ import io.timelimit.android.livedata.map
import io.timelimit.android.livedata.switchMap
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.manage.category.ManageCategoryFragmentArgs
import io.timelimit.android.ui.manage.category.appsandrules.AppAndRuleItem
import java.util.*
class CategoryAppsModel(application: Application): AndroidViewModel(application) {
@ -50,9 +51,9 @@ class CategoryAppsModel(application: Application): AndroidViewModel(application)
val appEntries = appsOfCategoryWithNames.map { apps ->
apps.map { (app, appEntry) ->
if (appEntry != null) {
AppEntry(appEntry.title, app.packageName, app.packageNameWithoutActivityName)
AppAndRuleItem.AppEntry(appEntry.title, app.packageName, app.packageNameWithoutActivityName)
} else {
AppEntry("app not found", app.packageName, app.packageNameWithoutActivityName)
AppAndRuleItem.AppEntry("app not found", app.packageName, app.packageNameWithoutActivityName)
}
}.sortedBy { it.title.toLowerCase(Locale.US) }
}

View file

@ -0,0 +1,185 @@
/*
* 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.appsandrules
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.timelimit.android.R
import io.timelimit.android.data.model.UsedTimeItem
import io.timelimit.android.databinding.*
import io.timelimit.android.extensions.MinuteOfDay
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.manage.category.apps.AppAdapterHandlers
import io.timelimit.android.ui.manage.category.timelimit_rules.TimeLimitRulesHandlers
import io.timelimit.android.util.JoinUtil
import io.timelimit.android.util.TimeTextUtil
import kotlin.properties.Delegates
class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
companion object {
private const val APP_ENTRY = 1
private const val ADD_APP_ITEM = 2
private const val EXPAND_APPS_ITEM = 3
private const val RULE_ENTRY = 4
private const val EXPAND_RULES_ITEM = 5
private const val RULES_INTRO = 6
}
var items: List<AppAndRuleItem> by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() }
var usedTimes: List<UsedTimeItem> by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() }
var epochDayOfStartOfWeek: Int by Delegates.observable(0) { _, _, _ -> notifyDataSetChanged() }
var handlers: Handlers? = null
init {
setHasStableIds(true)
}
override fun getItemCount(): Int = items.size
override fun getItemId(position: Int): Long = items[position].let { item ->
when (item) {
is AppAndRuleItem.AppEntry -> item.packageName.hashCode()
is AppAndRuleItem.RuleEntry -> item.rule.id.hashCode()
else -> item.hashCode()
}
}.toLong()
override fun getItemViewType(position: Int): Int = when (items[position]) {
is AppAndRuleItem.AppEntry -> APP_ENTRY
AppAndRuleItem.AddAppItem -> ADD_APP_ITEM
AppAndRuleItem.ExpandAppsItem -> EXPAND_APPS_ITEM
is AppAndRuleItem.RuleEntry -> RULE_ENTRY
AppAndRuleItem.ExpandRulesItem -> EXPAND_RULES_ITEM
AppAndRuleItem.RulesIntro -> RULES_INTRO
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder = Holder(
when (viewType) {
APP_ENTRY -> FragmentCategoryAppsItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).also { it.root.tag = it }.root
ADD_APP_ITEM -> AddItemViewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).apply {
label = parent.context.getString(R.string.category_apps_add_dialog_btn_positive)
wide = true
root.setOnClickListener { handlers?.onAddAppsClicked() }
}.root
EXPAND_APPS_ITEM -> ShowMoreListItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).root.also { it.setOnClickListener { handlers?.onShowAllApps() } }
RULE_ENTRY -> FragmentCategoryTimeLimitRuleItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).also { it.root.tag = it }.root
EXPAND_RULES_ITEM -> ShowMoreListItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).root.also { it.setOnClickListener { handlers?.onShowAllRules() } }
RULES_INTRO -> TimeLimitRuleIntroductionBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).root
else -> throw IllegalArgumentException()
}
)
override fun onBindViewHolder(holder: Holder, position: Int) {
val item = items[position]
when (item) {
is AppAndRuleItem.AppEntry -> {
val binding = holder.itemView.tag as FragmentCategoryAppsItemBinding
binding.item = item
binding.handlers = handlers
binding.executePendingBindings()
binding.icon.setImageDrawable(
DefaultAppLogic.with(binding.root.context)
.platformIntegration.getAppIcon(item.packageNameWithoutActivityName)
)
}
AppAndRuleItem.AddAppItem -> {/* nothing to do */}
AppAndRuleItem.ExpandAppsItem -> {/* nothing to do */}
is AppAndRuleItem.RuleEntry -> {
val rule = item.rule
val binding = holder.itemView.tag as FragmentCategoryTimeLimitRuleItemBinding
val context = binding.root.context
val dayNames = binding.root.resources.getStringArray(R.array.days_of_week_array)
val usedTime = usedTimes.filter { usedTime ->
val dayOfWeek = usedTime.dayOfEpoch - epochDayOfStartOfWeek
usedTime.startTimeOfDay == rule.startMinuteOfDay && usedTime.endTimeOfDay == rule.endMinuteOfDay &&
(rule.dayMask.toInt() and (1 shl dayOfWeek) != 0)
}.map { it.usedMillis }.sum().toInt()
binding.maxTimeString = TimeTextUtil.time(rule.maximumTimeInMillis, context)
binding.usageAsText = TimeTextUtil.used(usedTime, context)
binding.usageProgressInPercent = if (rule.maximumTimeInMillis > 0)
(usedTime * 100 / rule.maximumTimeInMillis)
else
100
binding.daysString = JoinUtil.join(
dayNames.filterIndexed { index, _ -> (rule.dayMask.toInt() and (1 shl index)) != 0 },
context
)
binding.timeAreaString = if (rule.appliesToWholeDay)
null
else
context.getString(
R.string.category_time_limit_rules_time_area,
MinuteOfDay.format(rule.startMinuteOfDay),
MinuteOfDay.format(rule.endMinuteOfDay)
)
binding.appliesToExtraTime = rule.applyToExtraTimeUsage
binding.sessionLimitString = if (rule.sessionDurationLimitEnabled)
context.getString(
R.string.category_time_limit_rules_session_limit,
TimeTextUtil.time(rule.sessionPauseMilliseconds, context),
TimeTextUtil.time(rule.sessionDurationMilliseconds, context)
)
else
null
binding.card.setOnClickListener { handlers?.onTimeLimitRuleClicked(rule) }
binding.executePendingBindings()
}
AppAndRuleItem.ExpandRulesItem -> {/* nothing to do */}
AppAndRuleItem.RulesIntro -> {/* nothing to do */}
}.let { }
}
class Holder(view: View): RecyclerView.ViewHolder(view)
}
interface Handlers: AppAdapterHandlers, TimeLimitRulesHandlers {
fun onShowAllApps()
fun onShowAllRules()
}

View file

@ -0,0 +1,28 @@
/*
* 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.appsandrules
import io.timelimit.android.data.model.TimeLimitRule
sealed class AppAndRuleItem {
data class AppEntry(val title: String, val packageName: String, val packageNameWithoutActivityName: String): AppAndRuleItem()
object AddAppItem: AppAndRuleItem()
object ExpandAppsItem: AppAndRuleItem()
data class RuleEntry(val rule: TimeLimitRule): AppAndRuleItem()
object ExpandRulesItem: AppAndRuleItem()
object RulesIntro: AppAndRuleItem()
}

View file

@ -40,7 +40,7 @@ class Adapter: RecyclerView.Adapter<ViewHolder>() {
var data: List<TimeLimitRuleItem> by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() }
var usedTimes: List<UsedTimeItem> by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() }
var epochDayOfStartOfWeek: Int by Delegates.observable(0) { _, _, _ -> notifyDataSetChanged() }
var handlers: Handlers? = null
var handlers: TimeLimitRulesHandlers? = null
init {
setHasStableIds(true)
@ -160,7 +160,7 @@ class Adapter: RecyclerView.Adapter<ViewHolder>() {
open class ViewHolder(view: View): RecyclerView.ViewHolder(view)
class ItemViewHolder(val view: FragmentCategoryTimeLimitRuleItemBinding): ViewHolder(view.root)
interface Handlers {
interface TimeLimitRulesHandlers {
fun onTimeLimitRuleClicked(rule: TimeLimitRule)
fun onAddTimeLimitRuleClicked()
}

View file

@ -106,7 +106,7 @@ class CategoryTimeLimitRulesFragment : Fragment(), EditTimeLimitRuleDialogFragme
adapter.data = it
})
adapter.handlers = object: Handlers {
adapter.handlers = object: TimeLimitRulesHandlers {
override fun onTimeLimitRuleClicked(rule: TimeLimitRule) {
if (auth.requestAuthenticationOrReturnTrue()) {
EditTimeLimitRuleDialogFragment.newInstance(rule, this@CategoryTimeLimitRulesFragment).show(fragmentManager!!)

View file

@ -19,11 +19,11 @@
<data>
<variable
name="item"
type="io.timelimit.android.ui.manage.category.apps.AppEntry" />
type="io.timelimit.android.ui.manage.category.appsandrules.AppAndRuleItem.AppEntry" />
<variable
name="handlers"
type="io.timelimit.android.ui.manage.category.apps.Handlers" />
type="io.timelimit.android.ui.manage.category.apps.AppAdapterHandlers" />
<import type="android.view.View" />
</data>

View file

@ -48,7 +48,7 @@
<variable
name="handlers"
type="io.timelimit.android.ui.manage.category.timelimit_rules.Handlers" />
type="io.timelimit.android.ui.manage.category.timelimit_rules.TimeLimitRulesHandlers" />
<import type="android.view.View" />
<import type="android.text.TextUtils" />