Browse Source
Merge pull request #12642 from t895/adapter-refactor
Merge pull request #12642 from t895/adapter-refactor
android: Refactor list adaptersnce_cpp
committed by
GitHub
22 changed files with 665 additions and 588 deletions
-
33src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt
-
98src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt
-
105src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt
-
36src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt
-
88src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
-
57src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
-
101src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
-
36src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
-
192src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
-
28src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
-
72src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
-
36src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
-
50src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
-
45src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
-
76src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
-
27src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt
-
129src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
-
9src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt
-
3src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
-
3src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
-
18src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt
-
11src/android/app/src/main/res/menu/menu_driver_manager.xml
@ -0,0 +1,33 @@ |
|||||
|
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.adapters |
||||
|
|
||||
|
import android.annotation.SuppressLint |
||||
|
import androidx.recyclerview.widget.AsyncDifferConfig |
||||
|
import androidx.recyclerview.widget.DiffUtil |
||||
|
import androidx.recyclerview.widget.ListAdapter |
||||
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
||||
|
import androidx.recyclerview.widget.RecyclerView |
||||
|
|
||||
|
/** |
||||
|
* Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate |
||||
|
* code used in every [RecyclerView]. |
||||
|
* Type assigned to [Model] must inherit from [Object] in order to be compared properly. |
||||
|
*/ |
||||
|
abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>> : |
||||
|
ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>()).build()) { |
||||
|
override fun onBindViewHolder(holder: Holder, position: Int) = |
||||
|
holder.bind(currentList[position]) |
||||
|
|
||||
|
private class DiffCallback<Model> : DiffUtil.ItemCallback<Model>() { |
||||
|
override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean { |
||||
|
return oldItem === newItem |
||||
|
} |
||||
|
|
||||
|
@SuppressLint("DiffUtilEquals") |
||||
|
override fun areContentsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean { |
||||
|
return oldItem == newItem |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.adapters |
||||
|
|
||||
|
import android.annotation.SuppressLint |
||||
|
import androidx.recyclerview.widget.RecyclerView |
||||
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
||||
|
|
||||
|
/** |
||||
|
* Generic list class meant to take care of basic lists |
||||
|
* @param currentList The list to show initially |
||||
|
*/ |
||||
|
abstract class AbstractListAdapter<Model : Any, Holder : AbstractViewHolder<Model>>( |
||||
|
open var currentList: List<Model> |
||||
|
) : RecyclerView.Adapter<Holder>() { |
||||
|
override fun onBindViewHolder(holder: Holder, position: Int) = |
||||
|
holder.bind(currentList[position]) |
||||
|
|
||||
|
override fun getItemCount(): Int = currentList.size |
||||
|
|
||||
|
/** |
||||
|
* Adds an item to [currentList] and notifies the underlying adapter of the change. If no parameter |
||||
|
* is passed in for position, [item] is added to the end of the list. Invokes [callback] last. |
||||
|
* @param item The item to add to the list |
||||
|
* @param position Index where [item] will be added |
||||
|
* @param callback Lambda that's called at the end of the list changes and has the added list |
||||
|
* position passed in as a parameter |
||||
|
*/ |
||||
|
open fun addItem(item: Model, position: Int = -1, callback: ((position: Int) -> Unit)? = null) { |
||||
|
val newList = currentList.toMutableList() |
||||
|
val positionToUpdate: Int |
||||
|
if (position == -1) { |
||||
|
newList.add(item) |
||||
|
currentList = newList |
||||
|
positionToUpdate = currentList.size - 1 |
||||
|
} else { |
||||
|
newList.add(position, item) |
||||
|
currentList = newList |
||||
|
positionToUpdate = position |
||||
|
} |
||||
|
onItemAdded(positionToUpdate, callback) |
||||
|
} |
||||
|
|
||||
|
protected fun onItemAdded(position: Int, callback: ((Int) -> Unit)? = null) { |
||||
|
notifyItemInserted(position) |
||||
|
callback?.invoke(position) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Replaces the [item] at [position] in the [currentList] and notifies the underlying adapter |
||||
|
* of the change. Invokes [callback] last. |
||||
|
* @param item New list item |
||||
|
* @param position Index where [item] will replace the existing list item |
||||
|
* @param callback Lambda that's called at the end of the list changes and has the changed list |
||||
|
* position passed in as a parameter |
||||
|
*/ |
||||
|
fun changeItem(item: Model, position: Int, callback: ((position: Int) -> Unit)? = null) { |
||||
|
val newList = currentList.toMutableList() |
||||
|
newList[position] = item |
||||
|
currentList = newList |
||||
|
onItemChanged(position, callback) |
||||
|
} |
||||
|
|
||||
|
protected fun onItemChanged(position: Int, callback: ((Int) -> Unit)? = null) { |
||||
|
notifyItemChanged(position) |
||||
|
callback?.invoke(position) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Removes the list item at [position] in [currentList] and notifies the underlying adapter |
||||
|
* of the change. Invokes [callback] last. |
||||
|
* @param position Index where the list item will be removed |
||||
|
* @param callback Lambda that's called at the end of the list changes and has the removed list |
||||
|
* position passed in as a parameter |
||||
|
*/ |
||||
|
fun removeItem(position: Int, callback: ((position: Int) -> Unit)? = null) { |
||||
|
val newList = currentList.toMutableList() |
||||
|
newList.removeAt(position) |
||||
|
currentList = newList |
||||
|
onItemRemoved(position, callback) |
||||
|
} |
||||
|
|
||||
|
protected fun onItemRemoved(position: Int, callback: ((Int) -> Unit)? = null) { |
||||
|
notifyItemRemoved(position) |
||||
|
callback?.invoke(position) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Replaces [currentList] with [newList] and notifies the underlying adapter of the change. |
||||
|
* @param newList The new list to replace [currentList] |
||||
|
*/ |
||||
|
@SuppressLint("NotifyDataSetChanged") |
||||
|
open fun replaceList(newList: List<Model>) { |
||||
|
currentList = newList |
||||
|
notifyDataSetChanged() |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.adapters |
||||
|
|
||||
|
import org.yuzu.yuzu_emu.model.SelectableItem |
||||
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
||||
|
|
||||
|
/** |
||||
|
* Generic list class meant to take care of single selection UI updates |
||||
|
* @param currentList The list to show initially |
||||
|
* @param defaultSelection The default selection to use if no list items are selected by |
||||
|
* [SelectableItem.selected] or if the currently selected item is removed from the list |
||||
|
*/ |
||||
|
abstract class AbstractSingleSelectionList< |
||||
|
Model : SelectableItem, |
||||
|
Holder : AbstractViewHolder<Model> |
||||
|
>( |
||||
|
final override var currentList: List<Model>, |
||||
|
private val defaultSelection: DefaultSelection = DefaultSelection.Start |
||||
|
) : AbstractListAdapter<Model, Holder>(currentList) { |
||||
|
var selectedItem = getDefaultSelection() |
||||
|
|
||||
|
init { |
||||
|
findSelectedItem() |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Changes the selection state of the [SelectableItem] that was selected and the previously selected |
||||
|
* item and notifies the underlying adapter of the change for those items. Invokes [callback] last. |
||||
|
* Does nothing if [position] is the same as the currently selected item. |
||||
|
* @param position Index of the item that was selected |
||||
|
* @param callback Lambda that's called at the end of the list changes and has the selected list |
||||
|
* position passed in as a parameter |
||||
|
*/ |
||||
|
fun selectItem(position: Int, callback: ((position: Int) -> Unit)? = null) { |
||||
|
if (position == selectedItem) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
val previouslySelectedItem = selectedItem |
||||
|
selectedItem = position |
||||
|
if (currentList.indices.contains(selectedItem)) { |
||||
|
currentList[selectedItem].onSelectionStateChanged(true) |
||||
|
} |
||||
|
if (currentList.indices.contains(previouslySelectedItem)) { |
||||
|
currentList[previouslySelectedItem].onSelectionStateChanged(false) |
||||
|
} |
||||
|
onItemChanged(previouslySelectedItem) |
||||
|
onItemChanged(selectedItem) |
||||
|
callback?.invoke(position) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Removes a given item from the list and notifies the underlying adapter of the change. If the |
||||
|
* currently selected item was the item that was removed, the item at the position provided |
||||
|
* by [defaultSelection] will be made the new selection. Invokes [callback] last. |
||||
|
* @param position Index of the item that was removed |
||||
|
* @param callback Lambda that's called at the end of the list changes and has the removed and |
||||
|
* selected list positions passed in as parameters |
||||
|
*/ |
||||
|
fun removeSelectableItem( |
||||
|
position: Int, |
||||
|
callback: ((removedPosition: Int, selectedPosition: Int) -> Unit)? |
||||
|
) { |
||||
|
removeItem(position) |
||||
|
if (position == selectedItem) { |
||||
|
selectedItem = getDefaultSelection() |
||||
|
currentList[selectedItem].onSelectionStateChanged(true) |
||||
|
onItemChanged(selectedItem) |
||||
|
} else if (position < selectedItem) { |
||||
|
selectedItem-- |
||||
|
} |
||||
|
callback?.invoke(position, selectedItem) |
||||
|
} |
||||
|
|
||||
|
override fun addItem(item: Model, position: Int, callback: ((Int) -> Unit)?) { |
||||
|
super.addItem(item, position, callback) |
||||
|
if (position <= selectedItem && position != -1) { |
||||
|
selectedItem++ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
override fun replaceList(newList: List<Model>) { |
||||
|
super.replaceList(newList) |
||||
|
findSelectedItem() |
||||
|
} |
||||
|
|
||||
|
private fun findSelectedItem() { |
||||
|
for (i in currentList.indices) { |
||||
|
if (currentList[i].selected) { |
||||
|
selectedItem = i |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun getDefaultSelection(): Int = |
||||
|
when (defaultSelection) { |
||||
|
DefaultSelection.Start -> currentList.indices.first |
||||
|
DefaultSelection.End -> currentList.indices.last |
||||
|
} |
||||
|
|
||||
|
enum class DefaultSelection { Start, End } |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.model |
||||
|
|
||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata |
||||
|
|
||||
|
data class Driver( |
||||
|
override var selected: Boolean, |
||||
|
val title: String, |
||||
|
val version: String = "", |
||||
|
val description: String = "" |
||||
|
) : SelectableItem { |
||||
|
override fun onSelectionStateChanged(selected: Boolean) { |
||||
|
this.selected = selected |
||||
|
} |
||||
|
|
||||
|
companion object { |
||||
|
fun GpuDriverMetadata.toDriver(selected: Boolean = false): Driver = |
||||
|
Driver( |
||||
|
selected, |
||||
|
this.name ?: "", |
||||
|
this.version ?: "", |
||||
|
this.description ?: "" |
||||
|
) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.model |
||||
|
|
||||
|
interface SelectableItem { |
||||
|
var selected: Boolean |
||||
|
fun onSelectionStateChanged(selected: Boolean) |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.viewholder |
||||
|
|
||||
|
import androidx.recyclerview.widget.RecyclerView |
||||
|
import androidx.viewbinding.ViewBinding |
||||
|
import org.yuzu.yuzu_emu.adapters.AbstractDiffAdapter |
||||
|
import org.yuzu.yuzu_emu.adapters.AbstractListAdapter |
||||
|
|
||||
|
/** |
||||
|
* [RecyclerView.ViewHolder] meant to work together with a [AbstractDiffAdapter] or a |
||||
|
* [AbstractListAdapter] so we can run [bind] on each list item without needing a manual hookup. |
||||
|
*/ |
||||
|
abstract class AbstractViewHolder<Model>(binding: ViewBinding) : |
||||
|
RecyclerView.ViewHolder(binding.root) { |
||||
|
abstract fun bind(model: Model) |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"> |
||||
|
|
||||
|
<item |
||||
|
android:id="@+id/menu_driver_clear" |
||||
|
android:icon="@drawable/ic_clear" |
||||
|
android:title="@string/clear" |
||||
|
app:showAsAction="always" /> |
||||
|
|
||||
|
</menu> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue