Fabian Kozynski | 9aa23af | 2020-02-05 17:47:47 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2020 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.systemui.controls.management |
| 18 | |
| 19 | import android.text.TextUtils |
| 20 | import android.util.Log |
| 21 | import com.android.systemui.controls.ControlStatus |
| 22 | import java.util.Collections |
| 23 | import java.util.Comparator |
| 24 | |
| 25 | /** |
| 26 | * Model for keeping track of current favorites and their order. |
| 27 | * |
| 28 | * This model is to be used with two [ControlAdapter] one that shows only favorites in the current |
| 29 | * order and another that shows all controls, separated by zone. When the favorite state of any |
| 30 | * control is modified or when the favorites are reordered, the adapters are notified of the change. |
| 31 | * |
| 32 | * @param listControls list of all the [ControlStatus] to display. This includes controls currently |
| 33 | * marked as favorites as well as those that have been removed (not returned |
| 34 | * from load) |
| 35 | * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those |
| 36 | * that have been removed. |
| 37 | * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites |
| 38 | * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls |
| 39 | */ |
| 40 | class FavoriteModel( |
| 41 | private val listControls: List<ControlStatus>, |
| 42 | listFavoritesIds: List<String>, |
| 43 | private val favoritesAdapter: ControlAdapter, |
| 44 | private val allAdapter: ControlAdapter |
| 45 | ) { |
| 46 | |
| 47 | companion object { |
| 48 | private const val TAG = "FavoriteModel" |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * List of favorite controls ([ControlWrapper]) in order. |
| 53 | * |
| 54 | * Initially, this list will give a list of wrappers in the order specified by the constructor |
| 55 | * variable `listFavoriteIds`. |
| 56 | * |
| 57 | * As the favorites are added, removed or moved, this list will keep track of those changes. |
| 58 | */ |
| 59 | val favorites: List<ControlWrapper> = listFavoritesIds.map { id -> |
| 60 | ControlWrapper(listControls.first { it.control.controlId == id }) |
| 61 | }.toMutableList() |
| 62 | |
| 63 | /** |
| 64 | * List of all controls by zones. |
| 65 | * |
| 66 | * Lists all the controls with the zone names interleaved as a flat list. After each zone name, |
| 67 | * the controls in that zone are listed. Zones are listed in alphabetical order |
| 68 | */ |
| 69 | val all: List<ElementWrapper> = listControls.groupBy { it.control.zone } |
| 70 | .mapKeys { it.key ?: "" } // map null to empty |
| 71 | .toSortedMap(CharSequenceComparator()) |
| 72 | .flatMap { |
| 73 | val controls = it.value.map { ControlWrapper(it) } |
| 74 | if (!TextUtils.isEmpty(it.key)) { |
| 75 | listOf(ZoneNameWrapper(it.key)) + controls |
| 76 | } else { |
| 77 | controls |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Change the favorite status of a [Control]. |
| 83 | * |
| 84 | * This can be invoked from any of the [ControlAdapter]. It will change the status of that |
| 85 | * control and either add it to the list of favorites (at the end) or remove it from it. |
| 86 | * |
| 87 | * Removing the favorite status from a Removed control will make it disappear completely if |
| 88 | * changes are saved. |
| 89 | * |
| 90 | * @param controlId the id of the [Control] to change the status |
| 91 | * @param favorite `true` if and only if it's set to be a favorite. |
| 92 | */ |
| 93 | fun changeFavoriteStatus(controlId: String, favorite: Boolean) { |
| 94 | favorites as MutableList |
| 95 | val index = all.indexOfFirst { |
| 96 | it is ControlWrapper && it.controlStatus.control.controlId == controlId |
| 97 | } |
| 98 | val control = (all[index] as ControlWrapper).controlStatus |
| 99 | if (control.favorite == favorite) { |
| 100 | Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ") |
| 101 | return |
| 102 | } else { |
| 103 | control.favorite = favorite |
| 104 | } |
| 105 | allAdapter.notifyItemChanged(index) |
| 106 | if (favorite) { |
| 107 | favorites.add(all[index] as ControlWrapper) |
| 108 | favoritesAdapter.notifyItemInserted(favorites.size - 1) |
| 109 | } else { |
| 110 | val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId } |
| 111 | favorites.removeAt(i) |
| 112 | favoritesAdapter.notifyItemRemoved(i) |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Move items in the model and notify the [favoritesAdapter]. |
| 118 | */ |
| 119 | fun onMoveItem(from: Int, to: Int) { |
| 120 | if (from < to) { |
| 121 | for (i in from until to) { |
| 122 | Collections.swap(favorites, i, i + 1) |
| 123 | } |
| 124 | } else { |
| 125 | for (i in from downTo to + 1) { |
| 126 | Collections.swap(favorites, i, i - 1) |
| 127 | } |
| 128 | } |
| 129 | favoritesAdapter.notifyItemMoved(from, to) |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * Compares [CharSequence] as [String]. |
| 135 | * |
| 136 | * It will have empty strings as the first element |
| 137 | */ |
| 138 | class CharSequenceComparator : Comparator<CharSequence> { |
| 139 | override fun compare(p0: CharSequence?, p1: CharSequence?): Int { |
| 140 | if (p0 == null && p1 == null) return 0 |
| 141 | else if (p0 == null && p1 != null) return -1 |
| 142 | else if (p0 != null && p1 == null) return 1 |
| 143 | return p0.toString().compareTo(p1.toString()) |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * Wrapper classes for the different types of elements shown in the [RecyclerView]s in |
| 149 | * [ControlsFavoritingActivity]. |
| 150 | */ |
| 151 | sealed class ElementWrapper |
| 152 | data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper() |
| 153 | data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper() |