diff options
| author | 2024-01-12 10:01:54 -0500 | |
|---|---|---|
| committer | 2024-01-12 10:01:54 -0500 | |
| commit | fcb0dff67ce18bde935fa1d79f653ae418dced8d (patch) | |
| tree | 338799be4b3eb1ea714fee96fa7c6333a077819f | |
| parent | Merge pull request #12653 from liamwhite/once-more (diff) | |
| parent | android: Fix added driver path (diff) | |
| download | yuzu-fcb0dff67ce18bde935fa1d79f653ae418dced8d.tar.gz yuzu-fcb0dff67ce18bde935fa1d79f653ae418dced8d.tar.xz yuzu-fcb0dff67ce18bde935fa1d79f653ae418dced8d.zip | |
Merge pull request #12642 from t895/adapter-refactor
android: Refactor list adapters
22 files changed, 665 insertions, 588 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt new file mode 100644 index 000000000..f006f9e3d --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.adapters | ||
| 5 | |||
| 6 | import android.annotation.SuppressLint | ||
| 7 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 8 | import androidx.recyclerview.widget.DiffUtil | ||
| 9 | import androidx.recyclerview.widget.ListAdapter | ||
| 10 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 11 | import androidx.recyclerview.widget.RecyclerView | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate | ||
| 15 | * code used in every [RecyclerView]. | ||
| 16 | * Type assigned to [Model] must inherit from [Object] in order to be compared properly. | ||
| 17 | */ | ||
| 18 | abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>> : | ||
| 19 | ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>()).build()) { | ||
| 20 | override fun onBindViewHolder(holder: Holder, position: Int) = | ||
| 21 | holder.bind(currentList[position]) | ||
| 22 | |||
| 23 | private class DiffCallback<Model> : DiffUtil.ItemCallback<Model>() { | ||
| 24 | override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean { | ||
| 25 | return oldItem === newItem | ||
| 26 | } | ||
| 27 | |||
| 28 | @SuppressLint("DiffUtilEquals") | ||
| 29 | override fun areContentsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean { | ||
| 30 | return oldItem == newItem | ||
| 31 | } | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt new file mode 100644 index 000000000..3dfee3d0c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.adapters | ||
| 5 | |||
| 6 | import android.annotation.SuppressLint | ||
| 7 | import androidx.recyclerview.widget.RecyclerView | ||
| 8 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 9 | |||
| 10 | /** | ||
| 11 | * Generic list class meant to take care of basic lists | ||
| 12 | * @param currentList The list to show initially | ||
| 13 | */ | ||
| 14 | abstract class AbstractListAdapter<Model : Any, Holder : AbstractViewHolder<Model>>( | ||
| 15 | open var currentList: List<Model> | ||
| 16 | ) : RecyclerView.Adapter<Holder>() { | ||
| 17 | override fun onBindViewHolder(holder: Holder, position: Int) = | ||
| 18 | holder.bind(currentList[position]) | ||
| 19 | |||
| 20 | override fun getItemCount(): Int = currentList.size | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Adds an item to [currentList] and notifies the underlying adapter of the change. If no parameter | ||
| 24 | * is passed in for position, [item] is added to the end of the list. Invokes [callback] last. | ||
| 25 | * @param item The item to add to the list | ||
| 26 | * @param position Index where [item] will be added | ||
| 27 | * @param callback Lambda that's called at the end of the list changes and has the added list | ||
| 28 | * position passed in as a parameter | ||
| 29 | */ | ||
| 30 | open fun addItem(item: Model, position: Int = -1, callback: ((position: Int) -> Unit)? = null) { | ||
| 31 | val newList = currentList.toMutableList() | ||
| 32 | val positionToUpdate: Int | ||
| 33 | if (position == -1) { | ||
| 34 | newList.add(item) | ||
| 35 | currentList = newList | ||
| 36 | positionToUpdate = currentList.size - 1 | ||
| 37 | } else { | ||
| 38 | newList.add(position, item) | ||
| 39 | currentList = newList | ||
| 40 | positionToUpdate = position | ||
| 41 | } | ||
| 42 | onItemAdded(positionToUpdate, callback) | ||
| 43 | } | ||
| 44 | |||
| 45 | protected fun onItemAdded(position: Int, callback: ((Int) -> Unit)? = null) { | ||
| 46 | notifyItemInserted(position) | ||
| 47 | callback?.invoke(position) | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Replaces the [item] at [position] in the [currentList] and notifies the underlying adapter | ||
| 52 | * of the change. Invokes [callback] last. | ||
| 53 | * @param item New list item | ||
| 54 | * @param position Index where [item] will replace the existing list item | ||
| 55 | * @param callback Lambda that's called at the end of the list changes and has the changed list | ||
| 56 | * position passed in as a parameter | ||
| 57 | */ | ||
| 58 | fun changeItem(item: Model, position: Int, callback: ((position: Int) -> Unit)? = null) { | ||
| 59 | val newList = currentList.toMutableList() | ||
| 60 | newList[position] = item | ||
| 61 | currentList = newList | ||
| 62 | onItemChanged(position, callback) | ||
| 63 | } | ||
| 64 | |||
| 65 | protected fun onItemChanged(position: Int, callback: ((Int) -> Unit)? = null) { | ||
| 66 | notifyItemChanged(position) | ||
| 67 | callback?.invoke(position) | ||
| 68 | } | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Removes the list item at [position] in [currentList] and notifies the underlying adapter | ||
| 72 | * of the change. Invokes [callback] last. | ||
| 73 | * @param position Index where the list item will be removed | ||
| 74 | * @param callback Lambda that's called at the end of the list changes and has the removed list | ||
| 75 | * position passed in as a parameter | ||
| 76 | */ | ||
| 77 | fun removeItem(position: Int, callback: ((position: Int) -> Unit)? = null) { | ||
| 78 | val newList = currentList.toMutableList() | ||
| 79 | newList.removeAt(position) | ||
| 80 | currentList = newList | ||
| 81 | onItemRemoved(position, callback) | ||
| 82 | } | ||
| 83 | |||
| 84 | protected fun onItemRemoved(position: Int, callback: ((Int) -> Unit)? = null) { | ||
| 85 | notifyItemRemoved(position) | ||
| 86 | callback?.invoke(position) | ||
| 87 | } | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Replaces [currentList] with [newList] and notifies the underlying adapter of the change. | ||
| 91 | * @param newList The new list to replace [currentList] | ||
| 92 | */ | ||
| 93 | @SuppressLint("NotifyDataSetChanged") | ||
| 94 | open fun replaceList(newList: List<Model>) { | ||
| 95 | currentList = newList | ||
| 96 | notifyDataSetChanged() | ||
| 97 | } | ||
| 98 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt new file mode 100644 index 000000000..52163f9d7 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.adapters | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.model.SelectableItem | ||
| 7 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Generic list class meant to take care of single selection UI updates | ||
| 11 | * @param currentList The list to show initially | ||
| 12 | * @param defaultSelection The default selection to use if no list items are selected by | ||
| 13 | * [SelectableItem.selected] or if the currently selected item is removed from the list | ||
| 14 | */ | ||
| 15 | abstract class AbstractSingleSelectionList< | ||
| 16 | Model : SelectableItem, | ||
| 17 | Holder : AbstractViewHolder<Model> | ||
| 18 | >( | ||
| 19 | final override var currentList: List<Model>, | ||
| 20 | private val defaultSelection: DefaultSelection = DefaultSelection.Start | ||
| 21 | ) : AbstractListAdapter<Model, Holder>(currentList) { | ||
| 22 | var selectedItem = getDefaultSelection() | ||
| 23 | |||
| 24 | init { | ||
| 25 | findSelectedItem() | ||
| 26 | } | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Changes the selection state of the [SelectableItem] that was selected and the previously selected | ||
| 30 | * item and notifies the underlying adapter of the change for those items. Invokes [callback] last. | ||
| 31 | * Does nothing if [position] is the same as the currently selected item. | ||
| 32 | * @param position Index of the item that was selected | ||
| 33 | * @param callback Lambda that's called at the end of the list changes and has the selected list | ||
| 34 | * position passed in as a parameter | ||
| 35 | */ | ||
| 36 | fun selectItem(position: Int, callback: ((position: Int) -> Unit)? = null) { | ||
| 37 | if (position == selectedItem) { | ||
| 38 | return | ||
| 39 | } | ||
| 40 | |||
| 41 | val previouslySelectedItem = selectedItem | ||
| 42 | selectedItem = position | ||
| 43 | if (currentList.indices.contains(selectedItem)) { | ||
| 44 | currentList[selectedItem].onSelectionStateChanged(true) | ||
| 45 | } | ||
| 46 | if (currentList.indices.contains(previouslySelectedItem)) { | ||
| 47 | currentList[previouslySelectedItem].onSelectionStateChanged(false) | ||
| 48 | } | ||
| 49 | onItemChanged(previouslySelectedItem) | ||
| 50 | onItemChanged(selectedItem) | ||
| 51 | callback?.invoke(position) | ||
| 52 | } | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Removes a given item from the list and notifies the underlying adapter of the change. If the | ||
| 56 | * currently selected item was the item that was removed, the item at the position provided | ||
| 57 | * by [defaultSelection] will be made the new selection. Invokes [callback] last. | ||
| 58 | * @param position Index of the item that was removed | ||
| 59 | * @param callback Lambda that's called at the end of the list changes and has the removed and | ||
| 60 | * selected list positions passed in as parameters | ||
| 61 | */ | ||
| 62 | fun removeSelectableItem( | ||
| 63 | position: Int, | ||
| 64 | callback: ((removedPosition: Int, selectedPosition: Int) -> Unit)? | ||
| 65 | ) { | ||
| 66 | removeItem(position) | ||
| 67 | if (position == selectedItem) { | ||
| 68 | selectedItem = getDefaultSelection() | ||
| 69 | currentList[selectedItem].onSelectionStateChanged(true) | ||
| 70 | onItemChanged(selectedItem) | ||
| 71 | } else if (position < selectedItem) { | ||
| 72 | selectedItem-- | ||
| 73 | } | ||
| 74 | callback?.invoke(position, selectedItem) | ||
| 75 | } | ||
| 76 | |||
| 77 | override fun addItem(item: Model, position: Int, callback: ((Int) -> Unit)?) { | ||
| 78 | super.addItem(item, position, callback) | ||
| 79 | if (position <= selectedItem && position != -1) { | ||
| 80 | selectedItem++ | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | override fun replaceList(newList: List<Model>) { | ||
| 85 | super.replaceList(newList) | ||
| 86 | findSelectedItem() | ||
| 87 | } | ||
| 88 | |||
| 89 | private fun findSelectedItem() { | ||
| 90 | for (i in currentList.indices) { | ||
| 91 | if (currentList[i].selected) { | ||
| 92 | selectedItem = i | ||
| 93 | break | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | private fun getDefaultSelection(): Int = | ||
| 99 | when (defaultSelection) { | ||
| 100 | DefaultSelection.Start -> currentList.indices.first | ||
| 101 | DefaultSelection.End -> currentList.indices.last | ||
| 102 | } | ||
| 103 | |||
| 104 | enum class DefaultSelection { Start, End } | ||
| 105 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt index 15c7ca3c9..94c151325 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt | |||
| @@ -5,48 +5,28 @@ package org.yuzu.yuzu_emu.adapters | |||
| 5 | 5 | ||
| 6 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
| 7 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
| 8 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 9 | import androidx.recyclerview.widget.DiffUtil | ||
| 10 | import androidx.recyclerview.widget.ListAdapter | ||
| 11 | import androidx.recyclerview.widget.RecyclerView | ||
| 12 | import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding | 8 | import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding |
| 13 | import org.yuzu.yuzu_emu.model.Addon | 9 | import org.yuzu.yuzu_emu.model.Addon |
| 10 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 14 | 11 | ||
| 15 | class AddonAdapter : ListAdapter<Addon, AddonAdapter.AddonViewHolder>( | 12 | class AddonAdapter : AbstractDiffAdapter<Addon, AddonAdapter.AddonViewHolder>() { |
| 16 | AsyncDifferConfig.Builder(DiffCallback()).build() | ||
| 17 | ) { | ||
| 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder { | 13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder { |
| 19 | ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 14 | ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 20 | .also { return AddonViewHolder(it) } | 15 | .also { return AddonViewHolder(it) } |
| 21 | } | 16 | } |
| 22 | 17 | ||
| 23 | override fun getItemCount(): Int = currentList.size | ||
| 24 | |||
| 25 | override fun onBindViewHolder(holder: AddonViewHolder, position: Int) = | ||
| 26 | holder.bind(currentList[position]) | ||
| 27 | |||
| 28 | inner class AddonViewHolder(val binding: ListItemAddonBinding) : | 18 | inner class AddonViewHolder(val binding: ListItemAddonBinding) : |
| 29 | RecyclerView.ViewHolder(binding.root) { | 19 | AbstractViewHolder<Addon>(binding) { |
| 30 | fun bind(addon: Addon) { | 20 | override fun bind(model: Addon) { |
| 31 | binding.root.setOnClickListener { | 21 | binding.root.setOnClickListener { |
| 32 | binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked | 22 | binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked |
| 33 | } | 23 | } |
| 34 | binding.title.text = addon.title | 24 | binding.title.text = model.title |
| 35 | binding.version.text = addon.version | 25 | binding.version.text = model.version |
| 36 | binding.addonSwitch.setOnCheckedChangeListener { _, checked -> | 26 | binding.addonSwitch.setOnCheckedChangeListener { _, checked -> |
| 37 | addon.enabled = checked | 27 | model.enabled = checked |
| 38 | } | 28 | } |
| 39 | binding.addonSwitch.isChecked = addon.enabled | 29 | binding.addonSwitch.isChecked = model.enabled |
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | private class DiffCallback : DiffUtil.ItemCallback<Addon>() { | ||
| 44 | override fun areItemsTheSame(oldItem: Addon, newItem: Addon): Boolean { | ||
| 45 | return oldItem == newItem | ||
| 46 | } | ||
| 47 | |||
| 48 | override fun areContentsTheSame(oldItem: Addon, newItem: Addon): Boolean { | ||
| 49 | return oldItem == newItem | ||
| 50 | } | 30 | } |
| 51 | } | 31 | } |
| 52 | } | 32 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt index 4a05c5be9..41d7f72b8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt | |||
| @@ -4,13 +4,11 @@ | |||
| 4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
| 5 | 5 | ||
| 6 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
| 7 | import android.view.View | ||
| 8 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
| 9 | import android.widget.Toast | 8 | import android.widget.Toast |
| 10 | import androidx.core.content.res.ResourcesCompat | 9 | import androidx.core.content.res.ResourcesCompat |
| 11 | import androidx.fragment.app.FragmentActivity | 10 | import androidx.fragment.app.FragmentActivity |
| 12 | import androidx.navigation.findNavController | 11 | import androidx.navigation.findNavController |
| 13 | import androidx.recyclerview.widget.RecyclerView | ||
| 14 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 12 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
| 15 | import org.yuzu.yuzu_emu.NativeLibrary | 13 | import org.yuzu.yuzu_emu.NativeLibrary |
| 16 | import org.yuzu.yuzu_emu.R | 14 | import org.yuzu.yuzu_emu.R |
| @@ -19,72 +17,58 @@ import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding | |||
| 19 | import org.yuzu.yuzu_emu.model.Applet | 17 | import org.yuzu.yuzu_emu.model.Applet |
| 20 | import org.yuzu.yuzu_emu.model.AppletInfo | 18 | import org.yuzu.yuzu_emu.model.AppletInfo |
| 21 | import org.yuzu.yuzu_emu.model.Game | 19 | import org.yuzu.yuzu_emu.model.Game |
| 20 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 22 | 21 | ||
| 23 | class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) : | 22 | class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) : |
| 24 | RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(), | 23 | AbstractListAdapter<Applet, AppletAdapter.AppletViewHolder>(applets) { |
| 25 | View.OnClickListener { | ||
| 26 | |||
| 27 | override fun onCreateViewHolder( | 24 | override fun onCreateViewHolder( |
| 28 | parent: ViewGroup, | 25 | parent: ViewGroup, |
| 29 | viewType: Int | 26 | viewType: Int |
| 30 | ): AppletAdapter.AppletViewHolder { | 27 | ): AppletAdapter.AppletViewHolder { |
| 31 | CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 28 | CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 32 | .apply { root.setOnClickListener(this@AppletAdapter) } | ||
| 33 | .also { return AppletViewHolder(it) } | 29 | .also { return AppletViewHolder(it) } |
| 34 | } | 30 | } |
| 35 | 31 | ||
| 36 | override fun onBindViewHolder(holder: AppletViewHolder, position: Int) = | ||
| 37 | holder.bind(applets[position]) | ||
| 38 | |||
| 39 | override fun getItemCount(): Int = applets.size | ||
| 40 | |||
| 41 | override fun onClick(view: View) { | ||
| 42 | val applet = (view.tag as AppletViewHolder).applet | ||
| 43 | val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId) | ||
| 44 | if (appletPath.isEmpty()) { | ||
| 45 | Toast.makeText( | ||
| 46 | YuzuApplication.appContext, | ||
| 47 | R.string.applets_error_applet, | ||
| 48 | Toast.LENGTH_SHORT | ||
| 49 | ).show() | ||
| 50 | return | ||
| 51 | } | ||
| 52 | |||
| 53 | if (applet.appletInfo == AppletInfo.Cabinet) { | ||
| 54 | view.findNavController() | ||
| 55 | .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment) | ||
| 56 | return | ||
| 57 | } | ||
| 58 | |||
| 59 | NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId) | ||
| 60 | val appletGame = Game( | ||
| 61 | title = YuzuApplication.appContext.getString(applet.titleId), | ||
| 62 | path = appletPath | ||
| 63 | ) | ||
| 64 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame) | ||
| 65 | view.findNavController().navigate(action) | ||
| 66 | } | ||
| 67 | |||
| 68 | inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) : | 32 | inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) : |
| 69 | RecyclerView.ViewHolder(binding.root) { | 33 | AbstractViewHolder<Applet>(binding) { |
| 70 | lateinit var applet: Applet | 34 | override fun bind(model: Applet) { |
| 71 | 35 | binding.title.setText(model.titleId) | |
| 72 | init { | 36 | binding.description.setText(model.descriptionId) |
| 73 | itemView.tag = this | ||
| 74 | } | ||
| 75 | |||
| 76 | fun bind(applet: Applet) { | ||
| 77 | this.applet = applet | ||
| 78 | |||
| 79 | binding.title.setText(applet.titleId) | ||
| 80 | binding.description.setText(applet.descriptionId) | ||
| 81 | binding.icon.setImageDrawable( | 37 | binding.icon.setImageDrawable( |
| 82 | ResourcesCompat.getDrawable( | 38 | ResourcesCompat.getDrawable( |
| 83 | binding.icon.context.resources, | 39 | binding.icon.context.resources, |
| 84 | applet.iconId, | 40 | model.iconId, |
| 85 | binding.icon.context.theme | 41 | binding.icon.context.theme |
| 86 | ) | 42 | ) |
| 87 | ) | 43 | ) |
| 44 | |||
| 45 | binding.root.setOnClickListener { onClick(model) } | ||
| 46 | } | ||
| 47 | |||
| 48 | fun onClick(applet: Applet) { | ||
| 49 | val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId) | ||
| 50 | if (appletPath.isEmpty()) { | ||
| 51 | Toast.makeText( | ||
| 52 | binding.root.context, | ||
| 53 | R.string.applets_error_applet, | ||
| 54 | Toast.LENGTH_SHORT | ||
| 55 | ).show() | ||
| 56 | return | ||
| 57 | } | ||
| 58 | |||
| 59 | if (applet.appletInfo == AppletInfo.Cabinet) { | ||
| 60 | binding.root.findNavController() | ||
| 61 | .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment) | ||
| 62 | return | ||
| 63 | } | ||
| 64 | |||
| 65 | NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId) | ||
| 66 | val appletGame = Game( | ||
| 67 | title = YuzuApplication.appContext.getString(applet.titleId), | ||
| 68 | path = appletPath | ||
| 69 | ) | ||
| 70 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame) | ||
| 71 | binding.root.findNavController().navigate(action) | ||
| 88 | } | 72 | } |
| 89 | } | 73 | } |
| 90 | } | 74 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt index e7b7c0f2f..a56137148 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt | |||
| @@ -4,12 +4,10 @@ | |||
| 4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
| 5 | 5 | ||
| 6 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
| 7 | import android.view.View | ||
| 8 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
| 9 | import androidx.core.content.res.ResourcesCompat | 8 | import androidx.core.content.res.ResourcesCompat |
| 10 | import androidx.fragment.app.Fragment | 9 | import androidx.fragment.app.Fragment |
| 11 | import androidx.navigation.fragment.findNavController | 10 | import androidx.navigation.fragment.findNavController |
| 12 | import androidx.recyclerview.widget.RecyclerView | ||
| 13 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 11 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
| 14 | import org.yuzu.yuzu_emu.NativeLibrary | 12 | import org.yuzu.yuzu_emu.NativeLibrary |
| 15 | import org.yuzu.yuzu_emu.R | 13 | import org.yuzu.yuzu_emu.R |
| @@ -19,54 +17,43 @@ import org.yuzu.yuzu_emu.model.CabinetMode | |||
| 19 | import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder | 17 | import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder |
| 20 | import org.yuzu.yuzu_emu.model.AppletInfo | 18 | import org.yuzu.yuzu_emu.model.AppletInfo |
| 21 | import org.yuzu.yuzu_emu.model.Game | 19 | import org.yuzu.yuzu_emu.model.Game |
| 20 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 22 | 21 | ||
| 23 | class CabinetLauncherDialogAdapter(val fragment: Fragment) : | 22 | class CabinetLauncherDialogAdapter(val fragment: Fragment) : |
| 24 | RecyclerView.Adapter<CabinetModeViewHolder>(), | 23 | AbstractListAdapter<CabinetMode, CabinetModeViewHolder>( |
| 25 | View.OnClickListener { | 24 | CabinetMode.values().copyOfRange(1, CabinetMode.entries.size).toList() |
| 26 | private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size) | 25 | ) { |
| 27 | 26 | ||
| 28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder { | 27 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder { |
| 29 | DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 28 | DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 30 | .apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) } | ||
| 31 | .also { return CabinetModeViewHolder(it) } | 29 | .also { return CabinetModeViewHolder(it) } |
| 32 | } | 30 | } |
| 33 | 31 | ||
| 34 | override fun getItemCount(): Int = cabinetModes.size | ||
| 35 | |||
| 36 | override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) = | ||
| 37 | holder.bind(cabinetModes[position]) | ||
| 38 | |||
| 39 | override fun onClick(view: View) { | ||
| 40 | val mode = (view.tag as CabinetModeViewHolder).cabinetMode | ||
| 41 | val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId) | ||
| 42 | NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId) | ||
| 43 | NativeLibrary.setCabinetMode(mode.id) | ||
| 44 | val appletGame = Game( | ||
| 45 | title = YuzuApplication.appContext.getString(R.string.cabinet_applet), | ||
| 46 | path = appletPath | ||
| 47 | ) | ||
| 48 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame) | ||
| 49 | fragment.findNavController().navigate(action) | ||
| 50 | } | ||
| 51 | |||
| 52 | inner class CabinetModeViewHolder(val binding: DialogListItemBinding) : | 32 | inner class CabinetModeViewHolder(val binding: DialogListItemBinding) : |
| 53 | RecyclerView.ViewHolder(binding.root) { | 33 | AbstractViewHolder<CabinetMode>(binding) { |
| 54 | lateinit var cabinetMode: CabinetMode | 34 | override fun bind(model: CabinetMode) { |
| 55 | |||
| 56 | init { | ||
| 57 | itemView.tag = this | ||
| 58 | } | ||
| 59 | |||
| 60 | fun bind(cabinetMode: CabinetMode) { | ||
| 61 | this.cabinetMode = cabinetMode | ||
| 62 | binding.icon.setImageDrawable( | 35 | binding.icon.setImageDrawable( |
| 63 | ResourcesCompat.getDrawable( | 36 | ResourcesCompat.getDrawable( |
| 64 | binding.icon.context.resources, | 37 | binding.icon.context.resources, |
| 65 | cabinetMode.iconId, | 38 | model.iconId, |
| 66 | binding.icon.context.theme | 39 | binding.icon.context.theme |
| 67 | ) | 40 | ) |
| 68 | ) | 41 | ) |
| 69 | binding.title.setText(cabinetMode.titleId) | 42 | binding.title.setText(model.titleId) |
| 43 | |||
| 44 | binding.root.setOnClickListener { onClick(model) } | ||
| 45 | } | ||
| 46 | |||
| 47 | private fun onClick(mode: CabinetMode) { | ||
| 48 | val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId) | ||
| 49 | NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId) | ||
| 50 | NativeLibrary.setCabinetMode(mode.id) | ||
| 51 | val appletGame = Game( | ||
| 52 | title = YuzuApplication.appContext.getString(R.string.cabinet_applet), | ||
| 53 | path = appletPath | ||
| 54 | ) | ||
| 55 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame) | ||
| 56 | fragment.findNavController().navigate(action) | ||
| 70 | } | 57 | } |
| 71 | } | 58 | } |
| 72 | } | 59 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt index d290a656c..d6f17cf29 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt | |||
| @@ -7,65 +7,39 @@ import android.text.TextUtils | |||
| 7 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
| 8 | import android.view.View | 8 | import android.view.View |
| 9 | import android.view.ViewGroup | 9 | import android.view.ViewGroup |
| 10 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 11 | import androidx.recyclerview.widget.DiffUtil | ||
| 12 | import androidx.recyclerview.widget.ListAdapter | ||
| 13 | import androidx.recyclerview.widget.RecyclerView | ||
| 14 | import org.yuzu.yuzu_emu.R | ||
| 15 | import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding | 10 | import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding |
| 11 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | ||
| 12 | import org.yuzu.yuzu_emu.model.Driver | ||
| 16 | import org.yuzu.yuzu_emu.model.DriverViewModel | 13 | import org.yuzu.yuzu_emu.model.DriverViewModel |
| 17 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 14 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
| 18 | import org.yuzu.yuzu_emu.utils.GpuDriverMetadata | ||
| 19 | 15 | ||
| 20 | class DriverAdapter(private val driverViewModel: DriverViewModel) : | 16 | class DriverAdapter(private val driverViewModel: DriverViewModel) : |
| 21 | ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>( | 17 | AbstractSingleSelectionList<Driver, DriverAdapter.DriverViewHolder>( |
| 22 | AsyncDifferConfig.Builder(DiffCallback()).build() | 18 | driverViewModel.driverList.value |
| 23 | ) { | 19 | ) { |
| 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder { | 20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder { |
| 25 | val binding = | 21 | CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 26 | CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 22 | .also { return DriverViewHolder(it) } |
| 27 | return DriverViewHolder(binding) | ||
| 28 | } | ||
| 29 | |||
| 30 | override fun getItemCount(): Int = currentList.size | ||
| 31 | |||
| 32 | override fun onBindViewHolder(holder: DriverViewHolder, position: Int) = | ||
| 33 | holder.bind(currentList[position]) | ||
| 34 | |||
| 35 | private fun onSelectDriver(position: Int) { | ||
| 36 | driverViewModel.setSelectedDriverIndex(position) | ||
| 37 | notifyItemChanged(driverViewModel.previouslySelectedDriver) | ||
| 38 | notifyItemChanged(driverViewModel.selectedDriver) | ||
| 39 | } | ||
| 40 | |||
| 41 | private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) { | ||
| 42 | if (driverViewModel.selectedDriver > position) { | ||
| 43 | driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1) | ||
| 44 | } | ||
| 45 | if (GpuDriverHelper.customDriverSettingData == driverData.second) { | ||
| 46 | driverViewModel.setSelectedDriverIndex(0) | ||
| 47 | } | ||
| 48 | driverViewModel.driversToDelete.add(driverData.first) | ||
| 49 | driverViewModel.removeDriver(driverData) | ||
| 50 | notifyItemRemoved(position) | ||
| 51 | notifyItemChanged(driverViewModel.selectedDriver) | ||
| 52 | } | 23 | } |
| 53 | 24 | ||
| 54 | inner class DriverViewHolder(val binding: CardDriverOptionBinding) : | 25 | inner class DriverViewHolder(val binding: CardDriverOptionBinding) : |
| 55 | RecyclerView.ViewHolder(binding.root) { | 26 | AbstractViewHolder<Driver>(binding) { |
| 56 | private lateinit var driverData: Pair<String, GpuDriverMetadata> | 27 | override fun bind(model: Driver) { |
| 57 | |||
| 58 | fun bind(driverData: Pair<String, GpuDriverMetadata>) { | ||
| 59 | this.driverData = driverData | ||
| 60 | val driver = driverData.second | ||
| 61 | |||
| 62 | binding.apply { | 28 | binding.apply { |
| 63 | radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition | 29 | radioButton.isChecked = model.selected |
| 64 | root.setOnClickListener { | 30 | root.setOnClickListener { |
| 65 | onSelectDriver(bindingAdapterPosition) | 31 | selectItem(bindingAdapterPosition) { |
| 32 | driverViewModel.onDriverSelected(it) | ||
| 33 | driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global) | ||
| 34 | } | ||
| 66 | } | 35 | } |
| 67 | buttonDelete.setOnClickListener { | 36 | buttonDelete.setOnClickListener { |
| 68 | onDeleteDriver(driverData, bindingAdapterPosition) | 37 | removeSelectableItem( |
| 38 | bindingAdapterPosition | ||
| 39 | ) { removedPosition: Int, selectedPosition: Int -> | ||
| 40 | driverViewModel.onDriverRemoved(removedPosition, selectedPosition) | ||
| 41 | driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global) | ||
| 42 | } | ||
| 69 | } | 43 | } |
| 70 | 44 | ||
| 71 | // Delay marquee by 3s | 45 | // Delay marquee by 3s |
| @@ -80,38 +54,19 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) : | |||
| 80 | }, | 54 | }, |
| 81 | 3000 | 55 | 3000 |
| 82 | ) | 56 | ) |
| 83 | if (driver.name == null) { | 57 | title.text = model.title |
| 84 | title.setText(R.string.system_gpu_driver) | 58 | version.text = model.version |
| 85 | description.text = "" | 59 | description.text = model.description |
| 86 | version.text = "" | 60 | if (model.description.isNotEmpty()) { |
| 87 | version.visibility = View.GONE | ||
| 88 | description.visibility = View.GONE | ||
| 89 | buttonDelete.visibility = View.GONE | ||
| 90 | } else { | ||
| 91 | title.text = driver.name | ||
| 92 | version.text = driver.version | ||
| 93 | description.text = driver.description | ||
| 94 | version.visibility = View.VISIBLE | 61 | version.visibility = View.VISIBLE |
| 95 | description.visibility = View.VISIBLE | 62 | description.visibility = View.VISIBLE |
| 96 | buttonDelete.visibility = View.VISIBLE | 63 | buttonDelete.visibility = View.VISIBLE |
| 64 | } else { | ||
| 65 | version.visibility = View.GONE | ||
| 66 | description.visibility = View.GONE | ||
| 67 | buttonDelete.visibility = View.GONE | ||
| 97 | } | 68 | } |
| 98 | } | 69 | } |
| 99 | } | 70 | } |
| 100 | } | 71 | } |
| 101 | |||
| 102 | private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() { | ||
| 103 | override fun areItemsTheSame( | ||
| 104 | oldItem: Pair<String, GpuDriverMetadata>, | ||
| 105 | newItem: Pair<String, GpuDriverMetadata> | ||
| 106 | ): Boolean { | ||
| 107 | return oldItem.first == newItem.first | ||
| 108 | } | ||
| 109 | |||
| 110 | override fun areContentsTheSame( | ||
| 111 | oldItem: Pair<String, GpuDriverMetadata>, | ||
| 112 | newItem: Pair<String, GpuDriverMetadata> | ||
| 113 | ): Boolean { | ||
| 114 | return oldItem.second == newItem.second | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | 72 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt index ab657a7b9..3d8f0bda8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt | |||
| @@ -8,19 +8,14 @@ import android.text.TextUtils | |||
| 8 | import android.view.LayoutInflater | 8 | import android.view.LayoutInflater |
| 9 | import android.view.ViewGroup | 9 | import android.view.ViewGroup |
| 10 | import androidx.fragment.app.FragmentActivity | 10 | import androidx.fragment.app.FragmentActivity |
| 11 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 12 | import androidx.recyclerview.widget.DiffUtil | ||
| 13 | import androidx.recyclerview.widget.ListAdapter | ||
| 14 | import androidx.recyclerview.widget.RecyclerView | ||
| 15 | import org.yuzu.yuzu_emu.databinding.CardFolderBinding | 11 | import org.yuzu.yuzu_emu.databinding.CardFolderBinding |
| 16 | import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment | 12 | import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment |
| 17 | import org.yuzu.yuzu_emu.model.GameDir | 13 | import org.yuzu.yuzu_emu.model.GameDir |
| 18 | import org.yuzu.yuzu_emu.model.GamesViewModel | 14 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 15 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 19 | 16 | ||
| 20 | class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) : | 17 | class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) : |
| 21 | ListAdapter<GameDir, FolderAdapter.FolderViewHolder>( | 18 | AbstractDiffAdapter<GameDir, FolderAdapter.FolderViewHolder>() { |
| 22 | AsyncDifferConfig.Builder(DiffCallback()).build() | ||
| 23 | ) { | ||
| 24 | override fun onCreateViewHolder( | 19 | override fun onCreateViewHolder( |
| 25 | parent: ViewGroup, | 20 | parent: ViewGroup, |
| 26 | viewType: Int | 21 | viewType: Int |
| @@ -29,18 +24,11 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie | |||
| 29 | .also { return FolderViewHolder(it) } | 24 | .also { return FolderViewHolder(it) } |
| 30 | } | 25 | } |
| 31 | 26 | ||
| 32 | override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) = | ||
| 33 | holder.bind(currentList[position]) | ||
| 34 | |||
| 35 | inner class FolderViewHolder(val binding: CardFolderBinding) : | 27 | inner class FolderViewHolder(val binding: CardFolderBinding) : |
| 36 | RecyclerView.ViewHolder(binding.root) { | 28 | AbstractViewHolder<GameDir>(binding) { |
| 37 | private lateinit var gameDir: GameDir | 29 | override fun bind(model: GameDir) { |
| 38 | |||
| 39 | fun bind(gameDir: GameDir) { | ||
| 40 | this.gameDir = gameDir | ||
| 41 | |||
| 42 | binding.apply { | 30 | binding.apply { |
| 43 | path.text = Uri.parse(gameDir.uriString).path | 31 | path.text = Uri.parse(model.uriString).path |
| 44 | path.postDelayed( | 32 | path.postDelayed( |
| 45 | { | 33 | { |
| 46 | path.isSelected = true | 34 | path.isSelected = true |
| @@ -50,7 +38,7 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie | |||
| 50 | ) | 38 | ) |
| 51 | 39 | ||
| 52 | buttonEdit.setOnClickListener { | 40 | buttonEdit.setOnClickListener { |
| 53 | GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir) | 41 | GameFolderPropertiesDialogFragment.newInstance(model) |
| 54 | .show( | 42 | .show( |
| 55 | activity.supportFragmentManager, | 43 | activity.supportFragmentManager, |
| 56 | GameFolderPropertiesDialogFragment.TAG | 44 | GameFolderPropertiesDialogFragment.TAG |
| @@ -58,19 +46,9 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie | |||
| 58 | } | 46 | } |
| 59 | 47 | ||
| 60 | buttonDelete.setOnClickListener { | 48 | buttonDelete.setOnClickListener { |
| 61 | gamesViewModel.removeFolder(this@FolderViewHolder.gameDir) | 49 | gamesViewModel.removeFolder(model) |
| 62 | } | 50 | } |
| 63 | } | 51 | } |
| 64 | } | 52 | } |
| 65 | } | 53 | } |
| 66 | |||
| 67 | private class DiffCallback : DiffUtil.ItemCallback<GameDir>() { | ||
| 68 | override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean { | ||
| 69 | return oldItem == newItem | ||
| 70 | } | ||
| 71 | |||
| 72 | override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean { | ||
| 73 | return oldItem == newItem | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | 54 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index a578f0de8..e26c2e0ab 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt | |||
| @@ -9,7 +9,6 @@ import android.graphics.drawable.LayerDrawable | |||
| 9 | import android.net.Uri | 9 | import android.net.Uri |
| 10 | import android.text.TextUtils | 10 | import android.text.TextUtils |
| 11 | import android.view.LayoutInflater | 11 | import android.view.LayoutInflater |
| 12 | import android.view.View | ||
| 13 | import android.view.ViewGroup | 12 | import android.view.ViewGroup |
| 14 | import android.widget.ImageView | 13 | import android.widget.ImageView |
| 15 | import android.widget.Toast | 14 | import android.widget.Toast |
| @@ -25,10 +24,6 @@ import androidx.lifecycle.ViewModelProvider | |||
| 25 | import androidx.lifecycle.lifecycleScope | 24 | import androidx.lifecycle.lifecycleScope |
| 26 | import androidx.navigation.findNavController | 25 | import androidx.navigation.findNavController |
| 27 | import androidx.preference.PreferenceManager | 26 | import androidx.preference.PreferenceManager |
| 28 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 29 | import androidx.recyclerview.widget.DiffUtil | ||
| 30 | import androidx.recyclerview.widget.ListAdapter | ||
| 31 | import androidx.recyclerview.widget.RecyclerView | ||
| 32 | import kotlinx.coroutines.Dispatchers | 27 | import kotlinx.coroutines.Dispatchers |
| 33 | import kotlinx.coroutines.launch | 28 | import kotlinx.coroutines.launch |
| 34 | import kotlinx.coroutines.withContext | 29 | import kotlinx.coroutines.withContext |
| @@ -36,122 +31,26 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections | |||
| 36 | import org.yuzu.yuzu_emu.R | 31 | import org.yuzu.yuzu_emu.R |
| 37 | import org.yuzu.yuzu_emu.YuzuApplication | 32 | import org.yuzu.yuzu_emu.YuzuApplication |
| 38 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 33 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 39 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | ||
| 40 | import org.yuzu.yuzu_emu.databinding.CardGameBinding | 34 | import org.yuzu.yuzu_emu.databinding.CardGameBinding |
| 41 | import org.yuzu.yuzu_emu.model.Game | 35 | import org.yuzu.yuzu_emu.model.Game |
| 42 | import org.yuzu.yuzu_emu.model.GamesViewModel | 36 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 43 | import org.yuzu.yuzu_emu.utils.GameIconUtils | 37 | import org.yuzu.yuzu_emu.utils.GameIconUtils |
| 38 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 44 | 39 | ||
| 45 | class GameAdapter(private val activity: AppCompatActivity) : | 40 | class GameAdapter(private val activity: AppCompatActivity) : |
| 46 | ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), | 41 | AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>() { |
| 47 | View.OnClickListener, | ||
| 48 | View.OnLongClickListener { | ||
| 49 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { | 42 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { |
| 50 | // Create a new view. | 43 | CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 51 | val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 44 | .also { return GameViewHolder(it) } |
| 52 | binding.cardGame.setOnClickListener(this) | ||
| 53 | binding.cardGame.setOnLongClickListener(this) | ||
| 54 | |||
| 55 | // Use that view to create a ViewHolder. | ||
| 56 | return GameViewHolder(binding) | ||
| 57 | } | ||
| 58 | |||
| 59 | override fun onBindViewHolder(holder: GameViewHolder, position: Int) = | ||
| 60 | holder.bind(currentList[position]) | ||
| 61 | |||
| 62 | override fun getItemCount(): Int = currentList.size | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Launches the game that was clicked on. | ||
| 66 | * | ||
| 67 | * @param view The card representing the game the user wants to play. | ||
| 68 | */ | ||
| 69 | override fun onClick(view: View) { | ||
| 70 | val holder = view.tag as GameViewHolder | ||
| 71 | |||
| 72 | val gameExists = DocumentFile.fromSingleUri( | ||
| 73 | YuzuApplication.appContext, | ||
| 74 | Uri.parse(holder.game.path) | ||
| 75 | )?.exists() == true | ||
| 76 | if (!gameExists) { | ||
| 77 | Toast.makeText( | ||
| 78 | YuzuApplication.appContext, | ||
| 79 | R.string.loader_error_file_not_found, | ||
| 80 | Toast.LENGTH_LONG | ||
| 81 | ).show() | ||
| 82 | |||
| 83 | ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true) | ||
| 84 | return | ||
| 85 | } | ||
| 86 | |||
| 87 | val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 88 | preferences.edit() | ||
| 89 | .putLong( | ||
| 90 | holder.game.keyLastPlayedTime, | ||
| 91 | System.currentTimeMillis() | ||
| 92 | ) | ||
| 93 | .apply() | ||
| 94 | |||
| 95 | val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { | ||
| 96 | action = Intent.ACTION_VIEW | ||
| 97 | data = Uri.parse(holder.game.path) | ||
| 98 | } | ||
| 99 | |||
| 100 | activity.lifecycleScope.launch { | ||
| 101 | withContext(Dispatchers.IO) { | ||
| 102 | val layerDrawable = ResourcesCompat.getDrawable( | ||
| 103 | YuzuApplication.appContext.resources, | ||
| 104 | R.drawable.shortcut, | ||
| 105 | null | ||
| 106 | ) as LayerDrawable | ||
| 107 | layerDrawable.setDrawableByLayerId( | ||
| 108 | R.id.shortcut_foreground, | ||
| 109 | GameIconUtils.getGameIcon(activity, holder.game) | ||
| 110 | .toDrawable(YuzuApplication.appContext.resources) | ||
| 111 | ) | ||
| 112 | val inset = YuzuApplication.appContext.resources | ||
| 113 | .getDimensionPixelSize(R.dimen.icon_inset) | ||
| 114 | layerDrawable.setLayerInset(1, inset, inset, inset, inset) | ||
| 115 | val shortcut = | ||
| 116 | ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path) | ||
| 117 | .setShortLabel(holder.game.title) | ||
| 118 | .setIcon( | ||
| 119 | IconCompat.createWithAdaptiveBitmap( | ||
| 120 | layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888) | ||
| 121 | ) | ||
| 122 | ) | ||
| 123 | .setIntent(openIntent) | ||
| 124 | .build() | ||
| 125 | ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut) | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game, true) | ||
| 130 | view.findNavController().navigate(action) | ||
| 131 | } | ||
| 132 | |||
| 133 | override fun onLongClick(view: View): Boolean { | ||
| 134 | val holder = view.tag as GameViewHolder | ||
| 135 | val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(holder.game) | ||
| 136 | view.findNavController().navigate(action) | ||
| 137 | return true | ||
| 138 | } | 45 | } |
| 139 | 46 | ||
| 140 | inner class GameViewHolder(val binding: CardGameBinding) : | 47 | inner class GameViewHolder(val binding: CardGameBinding) : |
| 141 | RecyclerView.ViewHolder(binding.root) { | 48 | AbstractViewHolder<Game>(binding) { |
| 142 | lateinit var game: Game | 49 | override fun bind(model: Game) { |
| 143 | |||
| 144 | init { | ||
| 145 | binding.cardGame.tag = this | ||
| 146 | } | ||
| 147 | |||
| 148 | fun bind(game: Game) { | ||
| 149 | this.game = game | ||
| 150 | |||
| 151 | binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP | 50 | binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP |
| 152 | GameIconUtils.loadGameIcon(game, binding.imageGameScreen) | 51 | GameIconUtils.loadGameIcon(model, binding.imageGameScreen) |
| 153 | 52 | ||
| 154 | binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ") | 53 | binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ") |
| 155 | 54 | ||
| 156 | binding.textGameTitle.postDelayed( | 55 | binding.textGameTitle.postDelayed( |
| 157 | { | 56 | { |
| @@ -160,16 +59,79 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 160 | }, | 59 | }, |
| 161 | 3000 | 60 | 3000 |
| 162 | ) | 61 | ) |
| 62 | |||
| 63 | binding.cardGame.setOnClickListener { onClick(model) } | ||
| 64 | binding.cardGame.setOnLongClickListener { onLongClick(model) } | ||
| 163 | } | 65 | } |
| 164 | } | ||
| 165 | 66 | ||
| 166 | private class DiffCallback : DiffUtil.ItemCallback<Game>() { | 67 | fun onClick(game: Game) { |
| 167 | override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { | 68 | val gameExists = DocumentFile.fromSingleUri( |
| 168 | return oldItem == newItem | 69 | YuzuApplication.appContext, |
| 70 | Uri.parse(game.path) | ||
| 71 | )?.exists() == true | ||
| 72 | if (!gameExists) { | ||
| 73 | Toast.makeText( | ||
| 74 | YuzuApplication.appContext, | ||
| 75 | R.string.loader_error_file_not_found, | ||
| 76 | Toast.LENGTH_LONG | ||
| 77 | ).show() | ||
| 78 | |||
| 79 | ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true) | ||
| 80 | return | ||
| 81 | } | ||
| 82 | |||
| 83 | val preferences = | ||
| 84 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 85 | preferences.edit() | ||
| 86 | .putLong( | ||
| 87 | game.keyLastPlayedTime, | ||
| 88 | System.currentTimeMillis() | ||
| 89 | ) | ||
| 90 | .apply() | ||
| 91 | |||
| 92 | val openIntent = | ||
| 93 | Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { | ||
| 94 | action = Intent.ACTION_VIEW | ||
| 95 | data = Uri.parse(game.path) | ||
| 96 | } | ||
| 97 | |||
| 98 | activity.lifecycleScope.launch { | ||
| 99 | withContext(Dispatchers.IO) { | ||
| 100 | val layerDrawable = ResourcesCompat.getDrawable( | ||
| 101 | YuzuApplication.appContext.resources, | ||
| 102 | R.drawable.shortcut, | ||
| 103 | null | ||
| 104 | ) as LayerDrawable | ||
| 105 | layerDrawable.setDrawableByLayerId( | ||
| 106 | R.id.shortcut_foreground, | ||
| 107 | GameIconUtils.getGameIcon(activity, game) | ||
| 108 | .toDrawable(YuzuApplication.appContext.resources) | ||
| 109 | ) | ||
| 110 | val inset = YuzuApplication.appContext.resources | ||
| 111 | .getDimensionPixelSize(R.dimen.icon_inset) | ||
| 112 | layerDrawable.setLayerInset(1, inset, inset, inset, inset) | ||
| 113 | val shortcut = | ||
| 114 | ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path) | ||
| 115 | .setShortLabel(game.title) | ||
| 116 | .setIcon( | ||
| 117 | IconCompat.createWithAdaptiveBitmap( | ||
| 118 | layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888) | ||
| 119 | ) | ||
| 120 | ) | ||
| 121 | .setIntent(openIntent) | ||
| 122 | .build() | ||
| 123 | ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut) | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true) | ||
| 128 | binding.root.findNavController().navigate(action) | ||
| 169 | } | 129 | } |
| 170 | 130 | ||
| 171 | override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { | 131 | fun onLongClick(game: Game): Boolean { |
| 172 | return oldItem == newItem | 132 | val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(game) |
| 133 | binding.root.findNavController().navigate(action) | ||
| 134 | return true | ||
| 173 | } | 135 | } |
| 174 | } | 136 | } |
| 175 | } | 137 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt index 95841d786..0046d5314 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt | |||
| @@ -12,23 +12,22 @@ import androidx.lifecycle.Lifecycle | |||
| 12 | import androidx.lifecycle.LifecycleOwner | 12 | import androidx.lifecycle.LifecycleOwner |
| 13 | import androidx.lifecycle.lifecycleScope | 13 | import androidx.lifecycle.lifecycleScope |
| 14 | import androidx.lifecycle.repeatOnLifecycle | 14 | import androidx.lifecycle.repeatOnLifecycle |
| 15 | import androidx.recyclerview.widget.RecyclerView | ||
| 16 | import kotlinx.coroutines.launch | 15 | import kotlinx.coroutines.launch |
| 17 | import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding | 16 | import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding |
| 18 | import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding | 17 | import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding |
| 19 | import org.yuzu.yuzu_emu.model.GameProperty | 18 | import org.yuzu.yuzu_emu.model.GameProperty |
| 20 | import org.yuzu.yuzu_emu.model.InstallableProperty | 19 | import org.yuzu.yuzu_emu.model.InstallableProperty |
| 21 | import org.yuzu.yuzu_emu.model.SubmenuProperty | 20 | import org.yuzu.yuzu_emu.model.SubmenuProperty |
| 21 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 22 | 22 | ||
| 23 | class GamePropertiesAdapter( | 23 | class GamePropertiesAdapter( |
| 24 | private val viewLifecycle: LifecycleOwner, | 24 | private val viewLifecycle: LifecycleOwner, |
| 25 | private var properties: List<GameProperty> | 25 | private var properties: List<GameProperty> |
| 26 | ) : | 26 | ) : AbstractListAdapter<GameProperty, AbstractViewHolder<GameProperty>>(properties) { |
| 27 | RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() { | ||
| 28 | override fun onCreateViewHolder( | 27 | override fun onCreateViewHolder( |
| 29 | parent: ViewGroup, | 28 | parent: ViewGroup, |
| 30 | viewType: Int | 29 | viewType: Int |
| 31 | ): GamePropertyViewHolder { | 30 | ): AbstractViewHolder<GameProperty> { |
| 32 | val inflater = LayoutInflater.from(parent.context) | 31 | val inflater = LayoutInflater.from(parent.context) |
| 33 | return when (viewType) { | 32 | return when (viewType) { |
| 34 | PropertyType.Submenu.ordinal -> { | 33 | PropertyType.Submenu.ordinal -> { |
| @@ -51,11 +50,6 @@ class GamePropertiesAdapter( | |||
| 51 | } | 50 | } |
| 52 | } | 51 | } |
| 53 | 52 | ||
| 54 | override fun getItemCount(): Int = properties.size | ||
| 55 | |||
| 56 | override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) = | ||
| 57 | holder.bind(properties[position]) | ||
| 58 | |||
| 59 | override fun getItemViewType(position: Int): Int { | 53 | override fun getItemViewType(position: Int): Int { |
| 60 | return when (properties[position]) { | 54 | return when (properties[position]) { |
| 61 | is SubmenuProperty -> PropertyType.Submenu.ordinal | 55 | is SubmenuProperty -> PropertyType.Submenu.ordinal |
| @@ -63,14 +57,10 @@ class GamePropertiesAdapter( | |||
| 63 | } | 57 | } |
| 64 | } | 58 | } |
| 65 | 59 | ||
| 66 | sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { | ||
| 67 | abstract fun bind(property: GameProperty) | ||
| 68 | } | ||
| 69 | |||
| 70 | inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) : | 60 | inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) : |
| 71 | GamePropertyViewHolder(binding.root) { | 61 | AbstractViewHolder<GameProperty>(binding) { |
| 72 | override fun bind(property: GameProperty) { | 62 | override fun bind(model: GameProperty) { |
| 73 | val submenuProperty = property as SubmenuProperty | 63 | val submenuProperty = model as SubmenuProperty |
| 74 | 64 | ||
| 75 | binding.root.setOnClickListener { | 65 | binding.root.setOnClickListener { |
| 76 | submenuProperty.action.invoke() | 66 | submenuProperty.action.invoke() |
| @@ -108,9 +98,9 @@ class GamePropertiesAdapter( | |||
| 108 | } | 98 | } |
| 109 | 99 | ||
| 110 | inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) : | 100 | inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) : |
| 111 | GamePropertyViewHolder(binding.root) { | 101 | AbstractViewHolder<GameProperty>(binding) { |
| 112 | override fun bind(property: GameProperty) { | 102 | override fun bind(model: GameProperty) { |
| 113 | val installableProperty = property as InstallableProperty | 103 | val installableProperty = model as InstallableProperty |
| 114 | 104 | ||
| 115 | binding.title.setText(installableProperty.titleId) | 105 | binding.title.setText(installableProperty.titleId) |
| 116 | binding.description.setText(installableProperty.descriptionId) | 106 | binding.description.setText(installableProperty.descriptionId) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index 58ce343f4..b512845d5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt | |||
| @@ -14,69 +14,37 @@ import androidx.lifecycle.Lifecycle | |||
| 14 | import androidx.lifecycle.LifecycleOwner | 14 | import androidx.lifecycle.LifecycleOwner |
| 15 | import androidx.lifecycle.lifecycleScope | 15 | import androidx.lifecycle.lifecycleScope |
| 16 | import androidx.lifecycle.repeatOnLifecycle | 16 | import androidx.lifecycle.repeatOnLifecycle |
| 17 | import androidx.recyclerview.widget.RecyclerView | ||
| 18 | import kotlinx.coroutines.launch | 17 | import kotlinx.coroutines.launch |
| 19 | import org.yuzu.yuzu_emu.R | 18 | import org.yuzu.yuzu_emu.R |
| 20 | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding | 19 | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding |
| 21 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 20 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 22 | import org.yuzu.yuzu_emu.model.HomeSetting | 21 | import org.yuzu.yuzu_emu.model.HomeSetting |
| 22 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 23 | 23 | ||
| 24 | class HomeSettingAdapter( | 24 | class HomeSettingAdapter( |
| 25 | private val activity: AppCompatActivity, | 25 | private val activity: AppCompatActivity, |
| 26 | private val viewLifecycle: LifecycleOwner, | 26 | private val viewLifecycle: LifecycleOwner, |
| 27 | var options: List<HomeSetting> | 27 | options: List<HomeSetting> |
| 28 | ) : | 28 | ) : AbstractListAdapter<HomeSetting, HomeSettingAdapter.HomeOptionViewHolder>(options) { |
| 29 | RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), | ||
| 30 | View.OnClickListener { | ||
| 31 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { | 29 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { |
| 32 | val binding = | 30 | CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 33 | CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 31 | .also { return HomeOptionViewHolder(it) } |
| 34 | binding.root.setOnClickListener(this) | ||
| 35 | return HomeOptionViewHolder(binding) | ||
| 36 | } | ||
| 37 | |||
| 38 | override fun getItemCount(): Int { | ||
| 39 | return options.size | ||
| 40 | } | ||
| 41 | |||
| 42 | override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) { | ||
| 43 | holder.bind(options[position]) | ||
| 44 | } | ||
| 45 | |||
| 46 | override fun onClick(view: View) { | ||
| 47 | val holder = view.tag as HomeOptionViewHolder | ||
| 48 | if (holder.option.isEnabled.invoke()) { | ||
| 49 | holder.option.onClick.invoke() | ||
| 50 | } else { | ||
| 51 | MessageDialogFragment.newInstance( | ||
| 52 | activity, | ||
| 53 | titleId = holder.option.disabledTitleId, | ||
| 54 | descriptionId = holder.option.disabledMessageId | ||
| 55 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) | ||
| 56 | } | ||
| 57 | } | 32 | } |
| 58 | 33 | ||
| 59 | inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) : | 34 | inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) : |
| 60 | RecyclerView.ViewHolder(binding.root) { | 35 | AbstractViewHolder<HomeSetting>(binding) { |
| 61 | lateinit var option: HomeSetting | 36 | override fun bind(model: HomeSetting) { |
| 62 | 37 | binding.optionTitle.text = activity.resources.getString(model.titleId) | |
| 63 | init { | 38 | binding.optionDescription.text = activity.resources.getString(model.descriptionId) |
| 64 | itemView.tag = this | ||
| 65 | } | ||
| 66 | |||
| 67 | fun bind(option: HomeSetting) { | ||
| 68 | this.option = option | ||
| 69 | binding.optionTitle.text = activity.resources.getString(option.titleId) | ||
| 70 | binding.optionDescription.text = activity.resources.getString(option.descriptionId) | ||
| 71 | binding.optionIcon.setImageDrawable( | 39 | binding.optionIcon.setImageDrawable( |
| 72 | ResourcesCompat.getDrawable( | 40 | ResourcesCompat.getDrawable( |
| 73 | activity.resources, | 41 | activity.resources, |
| 74 | option.iconId, | 42 | model.iconId, |
| 75 | activity.theme | 43 | activity.theme |
| 76 | ) | 44 | ) |
| 77 | ) | 45 | ) |
| 78 | 46 | ||
| 79 | when (option.titleId) { | 47 | when (model.titleId) { |
| 80 | R.string.get_early_access -> | 48 | R.string.get_early_access -> |
| 81 | binding.optionLayout.background = | 49 | binding.optionLayout.background = |
| 82 | ContextCompat.getDrawable( | 50 | ContextCompat.getDrawable( |
| @@ -85,7 +53,7 @@ class HomeSettingAdapter( | |||
| 85 | ) | 53 | ) |
| 86 | } | 54 | } |
| 87 | 55 | ||
| 88 | if (!option.isEnabled.invoke()) { | 56 | if (!model.isEnabled.invoke()) { |
| 89 | binding.optionTitle.alpha = 0.5f | 57 | binding.optionTitle.alpha = 0.5f |
| 90 | binding.optionDescription.alpha = 0.5f | 58 | binding.optionDescription.alpha = 0.5f |
| 91 | binding.optionIcon.alpha = 0.5f | 59 | binding.optionIcon.alpha = 0.5f |
| @@ -93,7 +61,7 @@ class HomeSettingAdapter( | |||
| 93 | 61 | ||
| 94 | viewLifecycle.lifecycleScope.launch { | 62 | viewLifecycle.lifecycleScope.launch { |
| 95 | viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { | 63 | viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { |
| 96 | option.details.collect { updateOptionDetails(it) } | 64 | model.details.collect { updateOptionDetails(it) } |
| 97 | } | 65 | } |
| 98 | } | 66 | } |
| 99 | binding.optionDetail.postDelayed( | 67 | binding.optionDetail.postDelayed( |
| @@ -103,6 +71,20 @@ class HomeSettingAdapter( | |||
| 103 | }, | 71 | }, |
| 104 | 3000 | 72 | 3000 |
| 105 | ) | 73 | ) |
| 74 | |||
| 75 | binding.root.setOnClickListener { onClick(model) } | ||
| 76 | } | ||
| 77 | |||
| 78 | private fun onClick(model: HomeSetting) { | ||
| 79 | if (model.isEnabled.invoke()) { | ||
| 80 | model.onClick.invoke() | ||
| 81 | } else { | ||
| 82 | MessageDialogFragment.newInstance( | ||
| 83 | activity, | ||
| 84 | titleId = model.disabledTitleId, | ||
| 85 | descriptionId = model.disabledMessageId | ||
| 86 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) | ||
| 87 | } | ||
| 106 | } | 88 | } |
| 107 | 89 | ||
| 108 | private fun updateOptionDetails(detailString: String) { | 90 | private fun updateOptionDetails(detailString: String) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt index e960fbaab..4218c4e52 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt | |||
| @@ -6,43 +6,33 @@ package org.yuzu.yuzu_emu.adapters | |||
| 6 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
| 7 | import android.view.View | 7 | import android.view.View |
| 8 | import android.view.ViewGroup | 8 | import android.view.ViewGroup |
| 9 | import androidx.recyclerview.widget.RecyclerView | ||
| 10 | import org.yuzu.yuzu_emu.databinding.CardInstallableBinding | 9 | import org.yuzu.yuzu_emu.databinding.CardInstallableBinding |
| 11 | import org.yuzu.yuzu_emu.model.Installable | 10 | import org.yuzu.yuzu_emu.model.Installable |
| 11 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 12 | 12 | ||
| 13 | class InstallableAdapter(private val installables: List<Installable>) : | 13 | class InstallableAdapter(installables: List<Installable>) : |
| 14 | RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() { | 14 | AbstractListAdapter<Installable, InstallableAdapter.InstallableViewHolder>(installables) { |
| 15 | override fun onCreateViewHolder( | 15 | override fun onCreateViewHolder( |
| 16 | parent: ViewGroup, | 16 | parent: ViewGroup, |
| 17 | viewType: Int | 17 | viewType: Int |
| 18 | ): InstallableAdapter.InstallableViewHolder { | 18 | ): InstallableAdapter.InstallableViewHolder { |
| 19 | val binding = | 19 | CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 20 | CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 20 | .also { return InstallableViewHolder(it) } |
| 21 | return InstallableViewHolder(binding) | ||
| 22 | } | 21 | } |
| 23 | 22 | ||
| 24 | override fun getItemCount(): Int = installables.size | ||
| 25 | |||
| 26 | override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) = | ||
| 27 | holder.bind(installables[position]) | ||
| 28 | |||
| 29 | inner class InstallableViewHolder(val binding: CardInstallableBinding) : | 23 | inner class InstallableViewHolder(val binding: CardInstallableBinding) : |
| 30 | RecyclerView.ViewHolder(binding.root) { | 24 | AbstractViewHolder<Installable>(binding) { |
| 31 | lateinit var installable: Installable | 25 | override fun bind(model: Installable) { |
| 32 | 26 | binding.title.setText(model.titleId) | |
| 33 | fun bind(installable: Installable) { | 27 | binding.description.setText(model.descriptionId) |
| 34 | this.installable = installable | ||
| 35 | |||
| 36 | binding.title.setText(installable.titleId) | ||
| 37 | binding.description.setText(installable.descriptionId) | ||
| 38 | 28 | ||
| 39 | if (installable.install != null) { | 29 | if (model.install != null) { |
| 40 | binding.buttonInstall.visibility = View.VISIBLE | 30 | binding.buttonInstall.visibility = View.VISIBLE |
| 41 | binding.buttonInstall.setOnClickListener { installable.install.invoke() } | 31 | binding.buttonInstall.setOnClickListener { model.install.invoke() } |
| 42 | } | 32 | } |
| 43 | if (installable.export != null) { | 33 | if (model.export != null) { |
| 44 | binding.buttonExport.visibility = View.VISIBLE | 34 | binding.buttonExport.visibility = View.VISIBLE |
| 45 | binding.buttonExport.setOnClickListener { installable.export.invoke() } | 35 | binding.buttonExport.setOnClickListener { model.export.invoke() } |
| 46 | } | 36 | } |
| 47 | } | 37 | } |
| 48 | } | 38 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt index bc6ff1364..38bb1f96f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt | |||
| @@ -7,49 +7,33 @@ import android.view.LayoutInflater | |||
| 7 | import android.view.View | 7 | import android.view.View |
| 8 | import android.view.ViewGroup | 8 | import android.view.ViewGroup |
| 9 | import androidx.appcompat.app.AppCompatActivity | 9 | import androidx.appcompat.app.AppCompatActivity |
| 10 | import androidx.recyclerview.widget.RecyclerView | ||
| 11 | import androidx.recyclerview.widget.RecyclerView.ViewHolder | ||
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 13 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | 10 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
| 14 | import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment | 11 | import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment |
| 15 | import org.yuzu.yuzu_emu.model.License | 12 | import org.yuzu.yuzu_emu.model.License |
| 13 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 16 | 14 | ||
| 17 | class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) : | 15 | class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) : |
| 18 | RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(), | 16 | AbstractListAdapter<License, LicenseAdapter.LicenseViewHolder>(licenses) { |
| 19 | View.OnClickListener { | ||
| 20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder { | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder { |
| 21 | val binding = | 18 | ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 22 | ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 19 | .also { return LicenseViewHolder(it) } |
| 23 | binding.root.setOnClickListener(this) | ||
| 24 | return LicenseViewHolder(binding) | ||
| 25 | } | 20 | } |
| 26 | 21 | ||
| 27 | override fun getItemCount(): Int = licenses.size | 22 | inner class LicenseViewHolder(val binding: ListItemSettingBinding) : |
| 23 | AbstractViewHolder<License>(binding) { | ||
| 24 | override fun bind(model: License) { | ||
| 25 | binding.apply { | ||
| 26 | textSettingName.text = root.context.getString(model.titleId) | ||
| 27 | textSettingDescription.text = root.context.getString(model.descriptionId) | ||
| 28 | textSettingValue.visibility = View.GONE | ||
| 28 | 29 | ||
| 29 | override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) { | 30 | root.setOnClickListener { onClick(model) } |
| 30 | holder.bind(licenses[position]) | 31 | } |
| 31 | } | ||
| 32 | |||
| 33 | override fun onClick(view: View) { | ||
| 34 | val license = (view.tag as LicenseViewHolder).license | ||
| 35 | LicenseBottomSheetDialogFragment.newInstance(license) | ||
| 36 | .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG) | ||
| 37 | } | ||
| 38 | |||
| 39 | inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) { | ||
| 40 | lateinit var license: License | ||
| 41 | |||
| 42 | init { | ||
| 43 | itemView.tag = this | ||
| 44 | } | 32 | } |
| 45 | 33 | ||
| 46 | fun bind(license: License) { | 34 | private fun onClick(license: License) { |
| 47 | this.license = license | 35 | LicenseBottomSheetDialogFragment.newInstance(license) |
| 48 | 36 | .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG) | |
| 49 | val context = YuzuApplication.appContext | ||
| 50 | binding.textSettingName.text = context.getString(license.titleId) | ||
| 51 | binding.textSettingDescription.text = context.getString(license.descriptionId) | ||
| 52 | binding.textSettingValue.visibility = View.GONE | ||
| 53 | } | 37 | } |
| 54 | } | 38 | } |
| 55 | } | 39 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt index 6b46d359e..02118e1a8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt | |||
| @@ -10,7 +10,6 @@ import android.view.ViewGroup | |||
| 10 | import androidx.appcompat.app.AppCompatActivity | 10 | import androidx.appcompat.app.AppCompatActivity |
| 11 | import androidx.core.content.res.ResourcesCompat | 11 | import androidx.core.content.res.ResourcesCompat |
| 12 | import androidx.lifecycle.ViewModelProvider | 12 | import androidx.lifecycle.ViewModelProvider |
| 13 | import androidx.recyclerview.widget.RecyclerView | ||
| 14 | import com.google.android.material.button.MaterialButton | 13 | import com.google.android.material.button.MaterialButton |
| 15 | import org.yuzu.yuzu_emu.databinding.PageSetupBinding | 14 | import org.yuzu.yuzu_emu.databinding.PageSetupBinding |
| 16 | import org.yuzu.yuzu_emu.model.HomeViewModel | 15 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| @@ -18,31 +17,19 @@ import org.yuzu.yuzu_emu.model.SetupCallback | |||
| 18 | import org.yuzu.yuzu_emu.model.SetupPage | 17 | import org.yuzu.yuzu_emu.model.SetupPage |
| 19 | import org.yuzu.yuzu_emu.model.StepState | 18 | import org.yuzu.yuzu_emu.model.StepState |
| 20 | import org.yuzu.yuzu_emu.utils.ViewUtils | 19 | import org.yuzu.yuzu_emu.utils.ViewUtils |
| 20 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
| 21 | 21 | ||
| 22 | class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : | 22 | class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) : |
| 23 | RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { | 23 | AbstractListAdapter<SetupPage, SetupAdapter.SetupPageViewHolder>(pages) { |
| 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder { | 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder { |
| 25 | val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false) | 25 | PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
| 26 | return SetupPageViewHolder(binding) | 26 | .also { return SetupPageViewHolder(it) } |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | override fun getItemCount(): Int = pages.size | ||
| 30 | |||
| 31 | override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) = | ||
| 32 | holder.bind(pages[position]) | ||
| 33 | |||
| 34 | inner class SetupPageViewHolder(val binding: PageSetupBinding) : | 29 | inner class SetupPageViewHolder(val binding: PageSetupBinding) : |
| 35 | RecyclerView.ViewHolder(binding.root), SetupCallback { | 30 | AbstractViewHolder<SetupPage>(binding), SetupCallback { |
| 36 | lateinit var page: SetupPage | 31 | override fun bind(model: SetupPage) { |
| 37 | 32 | if (model.stepCompleted.invoke() == StepState.COMPLETE) { | |
| 38 | init { | ||
| 39 | itemView.tag = this | ||
| 40 | } | ||
| 41 | |||
| 42 | fun bind(page: SetupPage) { | ||
| 43 | this.page = page | ||
| 44 | |||
| 45 | if (page.stepCompleted.invoke() == StepState.COMPLETE) { | ||
| 46 | binding.buttonAction.visibility = View.INVISIBLE | 33 | binding.buttonAction.visibility = View.INVISIBLE |
| 47 | binding.textConfirmation.visibility = View.VISIBLE | 34 | binding.textConfirmation.visibility = View.VISIBLE |
| 48 | } | 35 | } |
| @@ -50,31 +37,31 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) | |||
| 50 | binding.icon.setImageDrawable( | 37 | binding.icon.setImageDrawable( |
| 51 | ResourcesCompat.getDrawable( | 38 | ResourcesCompat.getDrawable( |
| 52 | activity.resources, | 39 | activity.resources, |
| 53 | page.iconId, | 40 | model.iconId, |
| 54 | activity.theme | 41 | activity.theme |
| 55 | ) | 42 | ) |
| 56 | ) | 43 | ) |
| 57 | binding.textTitle.text = activity.resources.getString(page.titleId) | 44 | binding.textTitle.text = activity.resources.getString(model.titleId) |
| 58 | binding.textDescription.text = | 45 | binding.textDescription.text = |
| 59 | Html.fromHtml(activity.resources.getString(page.descriptionId), 0) | 46 | Html.fromHtml(activity.resources.getString(model.descriptionId), 0) |
| 60 | 47 | ||
| 61 | binding.buttonAction.apply { | 48 | binding.buttonAction.apply { |
| 62 | text = activity.resources.getString(page.buttonTextId) | 49 | text = activity.resources.getString(model.buttonTextId) |
| 63 | if (page.buttonIconId != 0) { | 50 | if (model.buttonIconId != 0) { |
| 64 | icon = ResourcesCompat.getDrawable( | 51 | icon = ResourcesCompat.getDrawable( |
| 65 | activity.resources, | 52 | activity.resources, |
| 66 | page.buttonIconId, | 53 | model.buttonIconId, |
| 67 | activity.theme | 54 | activity.theme |
| 68 | ) | 55 | ) |
| 69 | } | 56 | } |
| 70 | iconGravity = | 57 | iconGravity = |
| 71 | if (page.leftAlignedIcon) { | 58 | if (model.leftAlignedIcon) { |
| 72 | MaterialButton.ICON_GRAVITY_START | 59 | MaterialButton.ICON_GRAVITY_START |
| 73 | } else { | 60 | } else { |
| 74 | MaterialButton.ICON_GRAVITY_END | 61 | MaterialButton.ICON_GRAVITY_END |
| 75 | } | 62 | } |
| 76 | setOnClickListener { | 63 | setOnClickListener { |
| 77 | page.buttonAction.invoke(this@SetupPageViewHolder) | 64 | model.buttonAction.invoke(this@SetupPageViewHolder) |
| 78 | } | 65 | } |
| 79 | } | 66 | } |
| 80 | } | 67 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt index cc71254dc..9dabb9c41 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.fragments | 4 | package org.yuzu.yuzu_emu.fragments |
| 5 | 5 | ||
| 6 | import android.annotation.SuppressLint | ||
| 6 | import android.os.Bundle | 7 | import android.os.Bundle |
| 7 | import android.view.LayoutInflater | 8 | import android.view.LayoutInflater |
| 8 | import android.view.View | 9 | import android.view.View |
| @@ -13,20 +14,26 @@ import androidx.core.view.WindowInsetsCompat | |||
| 13 | import androidx.core.view.updatePadding | 14 | import androidx.core.view.updatePadding |
| 14 | import androidx.fragment.app.Fragment | 15 | import androidx.fragment.app.Fragment |
| 15 | import androidx.fragment.app.activityViewModels | 16 | import androidx.fragment.app.activityViewModels |
| 17 | import androidx.lifecycle.Lifecycle | ||
| 16 | import androidx.lifecycle.lifecycleScope | 18 | import androidx.lifecycle.lifecycleScope |
| 19 | import androidx.lifecycle.repeatOnLifecycle | ||
| 17 | import androidx.navigation.findNavController | 20 | import androidx.navigation.findNavController |
| 18 | import androidx.navigation.fragment.navArgs | 21 | import androidx.navigation.fragment.navArgs |
| 19 | import androidx.recyclerview.widget.GridLayoutManager | 22 | import androidx.recyclerview.widget.GridLayoutManager |
| 20 | import com.google.android.material.transition.MaterialSharedAxis | 23 | import com.google.android.material.transition.MaterialSharedAxis |
| 21 | import kotlinx.coroutines.flow.collectLatest | 24 | import kotlinx.coroutines.Dispatchers |
| 22 | import kotlinx.coroutines.launch | 25 | import kotlinx.coroutines.launch |
| 26 | import kotlinx.coroutines.withContext | ||
| 23 | import org.yuzu.yuzu_emu.R | 27 | import org.yuzu.yuzu_emu.R |
| 24 | import org.yuzu.yuzu_emu.adapters.DriverAdapter | 28 | import org.yuzu.yuzu_emu.adapters.DriverAdapter |
| 25 | import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding | 29 | import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding |
| 30 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | ||
| 31 | import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver | ||
| 26 | import org.yuzu.yuzu_emu.model.DriverViewModel | 32 | import org.yuzu.yuzu_emu.model.DriverViewModel |
| 27 | import org.yuzu.yuzu_emu.model.HomeViewModel | 33 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 28 | import org.yuzu.yuzu_emu.utils.FileUtil | 34 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 29 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 35 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
| 36 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 30 | import java.io.File | 37 | import java.io.File |
| 31 | import java.io.IOException | 38 | import java.io.IOException |
| 32 | 39 | ||
| @@ -55,12 +62,43 @@ class DriverManagerFragment : Fragment() { | |||
| 55 | return binding.root | 62 | return binding.root |
| 56 | } | 63 | } |
| 57 | 64 | ||
| 65 | // This is using the correct scope, lint is just acting up | ||
| 66 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
| 58 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 67 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 59 | super.onViewCreated(view, savedInstanceState) | 68 | super.onViewCreated(view, savedInstanceState) |
| 60 | homeViewModel.setNavigationVisibility(visible = false, animated = true) | 69 | homeViewModel.setNavigationVisibility(visible = false, animated = true) |
| 61 | homeViewModel.setStatusBarShadeVisibility(visible = false) | 70 | homeViewModel.setStatusBarShadeVisibility(visible = false) |
| 62 | 71 | ||
| 63 | driverViewModel.onOpenDriverManager(args.game) | 72 | driverViewModel.onOpenDriverManager(args.game) |
| 73 | if (NativeConfig.isPerGameConfigLoaded()) { | ||
| 74 | binding.toolbarDrivers.inflateMenu(R.menu.menu_driver_manager) | ||
| 75 | driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global) | ||
| 76 | binding.toolbarDrivers.setOnMenuItemClickListener { | ||
| 77 | when (it.itemId) { | ||
| 78 | R.id.menu_driver_clear -> { | ||
| 79 | StringSetting.DRIVER_PATH.global = true | ||
| 80 | driverViewModel.updateDriverList() | ||
| 81 | (binding.listDrivers.adapter as DriverAdapter) | ||
| 82 | .replaceList(driverViewModel.driverList.value) | ||
| 83 | driverViewModel.showClearButton(false) | ||
| 84 | true | ||
| 85 | } | ||
| 86 | |||
| 87 | else -> false | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | viewLifecycleOwner.lifecycleScope.apply { | ||
| 92 | launch { | ||
| 93 | repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 94 | driverViewModel.showClearButton.collect { | ||
| 95 | binding.toolbarDrivers.menu | ||
| 96 | .findItem(R.id.menu_driver_clear).isVisible = it | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 64 | 102 | ||
| 65 | if (!driverViewModel.isInteractionAllowed.value) { | 103 | if (!driverViewModel.isInteractionAllowed.value) { |
| 66 | DriversLoadingDialogFragment().show( | 104 | DriversLoadingDialogFragment().show( |
| @@ -85,25 +123,6 @@ class DriverManagerFragment : Fragment() { | |||
| 85 | adapter = DriverAdapter(driverViewModel) | 123 | adapter = DriverAdapter(driverViewModel) |
| 86 | } | 124 | } |
| 87 | 125 | ||
| 88 | viewLifecycleOwner.lifecycleScope.apply { | ||
| 89 | launch { | ||
| 90 | driverViewModel.driverList.collectLatest { | ||
| 91 | (binding.listDrivers.adapter as DriverAdapter).submitList(it) | ||
| 92 | } | ||
| 93 | } | ||
| 94 | launch { | ||
| 95 | driverViewModel.newDriverInstalled.collect { | ||
| 96 | if (_binding != null && it) { | ||
| 97 | (binding.listDrivers.adapter as DriverAdapter).apply { | ||
| 98 | notifyItemChanged(driverViewModel.previouslySelectedDriver) | ||
| 99 | notifyItemChanged(driverViewModel.selectedDriver) | ||
| 100 | driverViewModel.setNewDriverInstalled(false) | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | setInsets() | 126 | setInsets() |
| 108 | } | 127 | } |
| 109 | 128 | ||
| @@ -160,7 +179,7 @@ class DriverManagerFragment : Fragment() { | |||
| 160 | false | 179 | false |
| 161 | ) { | 180 | ) { |
| 162 | val driverPath = | 181 | val driverPath = |
| 163 | "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}" | 182 | "${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}" |
| 164 | val driverFile = File(driverPath) | 183 | val driverFile = File(driverPath) |
| 165 | 184 | ||
| 166 | // Ignore file exceptions when a user selects an invalid zip | 185 | // Ignore file exceptions when a user selects an invalid zip |
| @@ -177,12 +196,21 @@ class DriverManagerFragment : Fragment() { | |||
| 177 | 196 | ||
| 178 | val driverData = GpuDriverHelper.getMetadataFromZip(driverFile) | 197 | val driverData = GpuDriverHelper.getMetadataFromZip(driverFile) |
| 179 | val driverInList = | 198 | val driverInList = |
| 180 | driverViewModel.driverList.value.firstOrNull { it.second == driverData } | 199 | driverViewModel.driverData.firstOrNull { it.second == driverData } |
| 181 | if (driverInList != null) { | 200 | if (driverInList != null) { |
| 182 | return@newInstance getString(R.string.driver_already_installed) | 201 | return@newInstance getString(R.string.driver_already_installed) |
| 183 | } else { | 202 | } else { |
| 184 | driverViewModel.addDriver(Pair(driverPath, driverData)) | 203 | driverViewModel.onDriverAdded(Pair(driverPath, driverData)) |
| 185 | driverViewModel.setNewDriverInstalled(true) | 204 | withContext(Dispatchers.Main) { |
| 205 | if (_binding != null) { | ||
| 206 | val adapter = binding.listDrivers.adapter as DriverAdapter | ||
| 207 | adapter.addItem(driverData.toDriver()) | ||
| 208 | adapter.selectItem(adapter.currentList.indices.last) | ||
| 209 | driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global) | ||
| 210 | binding.listDrivers | ||
| 211 | .smoothScrollToPosition(adapter.currentList.indices.last) | ||
| 212 | } | ||
| 213 | } | ||
| 186 | } | 214 | } |
| 187 | return@newInstance Any() | 215 | return@newInstance Any() |
| 188 | }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG) | 216 | }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt new file mode 100644 index 000000000..de342212a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.utils.GpuDriverMetadata | ||
| 7 | |||
| 8 | data class Driver( | ||
| 9 | override var selected: Boolean, | ||
| 10 | val title: String, | ||
| 11 | val version: String = "", | ||
| 12 | val description: String = "" | ||
| 13 | ) : SelectableItem { | ||
| 14 | override fun onSelectionStateChanged(selected: Boolean) { | ||
| 15 | this.selected = selected | ||
| 16 | } | ||
| 17 | |||
| 18 | companion object { | ||
| 19 | fun GpuDriverMetadata.toDriver(selected: Boolean = false): Driver = | ||
| 20 | Driver( | ||
| 21 | selected, | ||
| 22 | this.name ?: "", | ||
| 23 | this.version ?: "", | ||
| 24 | this.description ?: "" | ||
| 25 | ) | ||
| 26 | } | ||
| 27 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index 76accf8f3..15ae3a42b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt | |||
| @@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers | |||
| 9 | import kotlinx.coroutines.flow.MutableStateFlow | 9 | import kotlinx.coroutines.flow.MutableStateFlow |
| 10 | import kotlinx.coroutines.flow.SharingStarted | 10 | import kotlinx.coroutines.flow.SharingStarted |
| 11 | import kotlinx.coroutines.flow.StateFlow | 11 | import kotlinx.coroutines.flow.StateFlow |
| 12 | import kotlinx.coroutines.flow.asStateFlow | ||
| 12 | import kotlinx.coroutines.flow.combine | 13 | import kotlinx.coroutines.flow.combine |
| 13 | import kotlinx.coroutines.flow.stateIn | 14 | import kotlinx.coroutines.flow.stateIn |
| 14 | import kotlinx.coroutines.launch | 15 | import kotlinx.coroutines.launch |
| @@ -17,11 +18,10 @@ import org.yuzu.yuzu_emu.R | |||
| 17 | import org.yuzu.yuzu_emu.YuzuApplication | 18 | import org.yuzu.yuzu_emu.YuzuApplication |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | 19 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting |
| 19 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 20 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 20 | import org.yuzu.yuzu_emu.utils.FileUtil | 21 | import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver |
| 21 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 22 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
| 22 | import org.yuzu.yuzu_emu.utils.GpuDriverMetadata | 23 | import org.yuzu.yuzu_emu.utils.GpuDriverMetadata |
| 23 | import org.yuzu.yuzu_emu.utils.NativeConfig | 24 | import org.yuzu.yuzu_emu.utils.NativeConfig |
| 24 | import java.io.BufferedOutputStream | ||
| 25 | import java.io.File | 25 | import java.io.File |
| 26 | 26 | ||
| 27 | class DriverViewModel : ViewModel() { | 27 | class DriverViewModel : ViewModel() { |
| @@ -38,97 +38,81 @@ class DriverViewModel : ViewModel() { | |||
| 38 | !loading && ready && !deleting | 38 | !loading && ready && !deleting |
| 39 | }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false) | 39 | }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false) |
| 40 | 40 | ||
| 41 | private val _driverList = MutableStateFlow(GpuDriverHelper.getDrivers()) | 41 | var driverData = GpuDriverHelper.getDrivers() |
| 42 | val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList | ||
| 43 | 42 | ||
| 44 | var previouslySelectedDriver = 0 | 43 | private val _driverList = MutableStateFlow(emptyList<Driver>()) |
| 45 | var selectedDriver = -1 | 44 | val driverList: StateFlow<List<Driver>> get() = _driverList |
| 46 | 45 | ||
| 47 | // Used for showing which driver is currently installed within the driver manager card | 46 | // Used for showing which driver is currently installed within the driver manager card |
| 48 | private val _selectedDriverTitle = MutableStateFlow("") | 47 | private val _selectedDriverTitle = MutableStateFlow("") |
| 49 | val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle | 48 | val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle |
| 50 | 49 | ||
| 51 | private val _newDriverInstalled = MutableStateFlow(false) | 50 | private val _showClearButton = MutableStateFlow(false) |
| 52 | val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled | 51 | val showClearButton = _showClearButton.asStateFlow() |
| 53 | 52 | ||
| 54 | val driversToDelete = mutableListOf<String>() | 53 | private val driversToDelete = mutableListOf<String>() |
| 55 | 54 | ||
| 56 | init { | 55 | init { |
| 57 | val currentDriverMetadata = GpuDriverHelper.installedCustomDriverData | 56 | updateDriverList() |
| 58 | findSelectedDriver(currentDriverMetadata) | 57 | updateDriverNameForGame(null) |
| 59 | 58 | } | |
| 60 | // If a user had installed a driver before the manager was implemented, this zips | ||
| 61 | // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can | ||
| 62 | // be indexed and exported as expected. | ||
| 63 | if (selectedDriver == -1) { | ||
| 64 | val driverToSave = | ||
| 65 | File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip") | ||
| 66 | driverToSave.createNewFile() | ||
| 67 | FileUtil.zipFromInternalStorage( | ||
| 68 | File(GpuDriverHelper.driverInstallationPath!!), | ||
| 69 | GpuDriverHelper.driverInstallationPath!!, | ||
| 70 | BufferedOutputStream(driverToSave.outputStream()) | ||
| 71 | ) | ||
| 72 | _driverList.value.add(Pair(driverToSave.path, currentDriverMetadata)) | ||
| 73 | setSelectedDriverIndex(_driverList.value.size - 1) | ||
| 74 | } | ||
| 75 | 59 | ||
| 76 | // If a user had installed a driver before the config was reworked to be multiplatform, | 60 | fun reloadDriverData() { |
| 77 | // we have save the path of the previously selected driver to the new setting. | 61 | _areDriversLoading.value = true |
| 78 | if (StringSetting.DRIVER_PATH.getString(true).isEmpty() && selectedDriver > 0 && | 62 | driverData = GpuDriverHelper.getDrivers() |
| 79 | StringSetting.DRIVER_PATH.global | 63 | updateDriverList() |
| 80 | ) { | 64 | _areDriversLoading.value = false |
| 81 | StringSetting.DRIVER_PATH.setString(_driverList.value[selectedDriver].first) | 65 | } |
| 82 | NativeConfig.saveGlobalConfig() | 66 | |
| 83 | } else { | 67 | fun updateDriverList() { |
| 84 | findSelectedDriver(GpuDriverHelper.customDriverSettingData) | 68 | val selectedDriver = GpuDriverHelper.customDriverSettingData |
| 69 | val newDriverList = mutableListOf( | ||
| 70 | Driver( | ||
| 71 | selectedDriver == GpuDriverMetadata(), | ||
| 72 | YuzuApplication.appContext.getString(R.string.system_gpu_driver) | ||
| 73 | ) | ||
| 74 | ) | ||
| 75 | driverData.forEach { | ||
| 76 | newDriverList.add(it.second.toDriver(it.second == selectedDriver)) | ||
| 85 | } | 77 | } |
| 86 | updateDriverNameForGame(null) | 78 | _driverList.value = newDriverList |
| 87 | } | 79 | } |
| 88 | 80 | ||
| 89 | fun setSelectedDriverIndex(value: Int) { | 81 | fun onOpenDriverManager(game: Game?) { |
| 90 | if (selectedDriver != -1) { | 82 | if (game != null) { |
| 91 | previouslySelectedDriver = selectedDriver | 83 | SettingsFile.loadCustomConfig(game) |
| 92 | } | 84 | } |
| 93 | selectedDriver = value | 85 | updateDriverList() |
| 94 | } | 86 | } |
| 95 | 87 | ||
| 96 | fun setNewDriverInstalled(value: Boolean) { | 88 | fun showClearButton(value: Boolean) { |
| 97 | _newDriverInstalled.value = value | 89 | _showClearButton.value = value |
| 98 | } | 90 | } |
| 99 | 91 | ||
| 100 | fun addDriver(driverData: Pair<String, GpuDriverMetadata>) { | 92 | fun onDriverSelected(position: Int) { |
| 101 | val driverIndex = _driverList.value.indexOfFirst { it == driverData } | 93 | if (position == 0) { |
| 102 | if (driverIndex == -1) { | 94 | StringSetting.DRIVER_PATH.setString("") |
| 103 | _driverList.value.add(driverData) | ||
| 104 | setSelectedDriverIndex(_driverList.value.size - 1) | ||
| 105 | _selectedDriverTitle.value = driverData.second.name | ||
| 106 | ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) | ||
| 107 | } else { | 95 | } else { |
| 108 | setSelectedDriverIndex(driverIndex) | 96 | StringSetting.DRIVER_PATH.setString(driverData[position - 1].first) |
| 109 | } | 97 | } |
| 110 | } | 98 | } |
| 111 | 99 | ||
| 112 | fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) { | 100 | fun onDriverRemoved(removedPosition: Int, selectedPosition: Int) { |
| 113 | _driverList.value.remove(driverData) | 101 | driversToDelete.add(driverData[removedPosition - 1].first) |
| 102 | driverData.removeAt(removedPosition - 1) | ||
| 103 | onDriverSelected(selectedPosition) | ||
| 114 | } | 104 | } |
| 115 | 105 | ||
| 116 | fun onOpenDriverManager(game: Game?) { | 106 | fun onDriverAdded(driver: Pair<String, GpuDriverMetadata>) { |
| 117 | if (game != null) { | 107 | if (driversToDelete.contains(driver.first)) { |
| 118 | SettingsFile.loadCustomConfig(game) | 108 | driversToDelete.remove(driver.first) |
| 119 | } | ||
| 120 | |||
| 121 | val driverPath = StringSetting.DRIVER_PATH.getString() | ||
| 122 | if (driverPath.isEmpty()) { | ||
| 123 | setSelectedDriverIndex(0) | ||
| 124 | } else { | ||
| 125 | findSelectedDriver(GpuDriverHelper.getMetadataFromZip(File(driverPath))) | ||
| 126 | } | 109 | } |
| 110 | driverData.add(driver) | ||
| 111 | onDriverSelected(driverData.size) | ||
| 127 | } | 112 | } |
| 128 | 113 | ||
| 129 | fun onCloseDriverManager(game: Game?) { | 114 | fun onCloseDriverManager(game: Game?) { |
| 130 | _isDeletingDrivers.value = true | 115 | _isDeletingDrivers.value = true |
| 131 | StringSetting.DRIVER_PATH.setString(driverList.value[selectedDriver].first) | ||
| 132 | updateDriverNameForGame(game) | 116 | updateDriverNameForGame(game) |
| 133 | if (game == null) { | 117 | if (game == null) { |
| 134 | NativeConfig.saveGlobalConfig() | 118 | NativeConfig.saveGlobalConfig() |
| @@ -181,20 +165,6 @@ class DriverViewModel : ViewModel() { | |||
| 181 | } | 165 | } |
| 182 | } | 166 | } |
| 183 | 167 | ||
| 184 | private fun findSelectedDriver(currentDriverMetadata: GpuDriverMetadata) { | ||
| 185 | if (driverList.value.size == 1) { | ||
| 186 | setSelectedDriverIndex(0) | ||
| 187 | return | ||
| 188 | } | ||
| 189 | |||
| 190 | driverList.value.forEachIndexed { i: Int, driver: Pair<String, GpuDriverMetadata> -> | ||
| 191 | if (driver.second == currentDriverMetadata) { | ||
| 192 | setSelectedDriverIndex(i) | ||
| 193 | return | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | fun updateDriverNameForGame(game: Game?) { | 168 | fun updateDriverNameForGame(game: Game?) { |
| 199 | if (!GpuDriverHelper.supportsCustomDriverLoading()) { | 169 | if (!GpuDriverHelper.supportsCustomDriverLoading()) { |
| 200 | return | 170 | return |
| @@ -217,7 +187,6 @@ class DriverViewModel : ViewModel() { | |||
| 217 | 187 | ||
| 218 | private fun setDriverReady() { | 188 | private fun setDriverReady() { |
| 219 | _isDriverReady.value = true | 189 | _isDriverReady.value = true |
| 220 | _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name | 190 | updateName() |
| 221 | ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) | ||
| 222 | } | 191 | } |
| 223 | } | 192 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt new file mode 100644 index 000000000..11c22d323 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | interface SelectableItem { | ||
| 7 | var selected: Boolean | ||
| 8 | fun onSelectionStateChanged(selected: Boolean) | ||
| 9 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 622ae996e..644289e25 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | |||
| @@ -41,6 +41,7 @@ import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment | |||
| 41 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 41 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment |
| 42 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 42 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 43 | import org.yuzu.yuzu_emu.model.AddonViewModel | 43 | import org.yuzu.yuzu_emu.model.AddonViewModel |
| 44 | import org.yuzu.yuzu_emu.model.DriverViewModel | ||
| 44 | import org.yuzu.yuzu_emu.model.GamesViewModel | 45 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 45 | import org.yuzu.yuzu_emu.model.HomeViewModel | 46 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 46 | import org.yuzu.yuzu_emu.model.TaskState | 47 | import org.yuzu.yuzu_emu.model.TaskState |
| @@ -58,6 +59,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 58 | private val gamesViewModel: GamesViewModel by viewModels() | 59 | private val gamesViewModel: GamesViewModel by viewModels() |
| 59 | private val taskViewModel: TaskViewModel by viewModels() | 60 | private val taskViewModel: TaskViewModel by viewModels() |
| 60 | private val addonViewModel: AddonViewModel by viewModels() | 61 | private val addonViewModel: AddonViewModel by viewModels() |
| 62 | private val driverViewModel: DriverViewModel by viewModels() | ||
| 61 | 63 | ||
| 62 | override var themeId: Int = 0 | 64 | override var themeId: Int = 0 |
| 63 | 65 | ||
| @@ -689,6 +691,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 689 | NativeLibrary.initializeSystem(true) | 691 | NativeLibrary.initializeSystem(true) |
| 690 | NativeConfig.initializeGlobalConfig() | 692 | NativeConfig.initializeGlobalConfig() |
| 691 | gamesViewModel.reloadGames(false) | 693 | gamesViewModel.reloadGames(false) |
| 694 | driverViewModel.reloadDriverData() | ||
| 692 | 695 | ||
| 693 | return@newInstance getString(R.string.user_data_import_success) | 696 | return@newInstance getString(R.string.user_data_import_success) |
| 694 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 697 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt index 685272288..a8f9dcc34 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt | |||
| @@ -62,9 +62,6 @@ object GpuDriverHelper { | |||
| 62 | ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name } | 62 | ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name } |
| 63 | ?.distinct() | 63 | ?.distinct() |
| 64 | ?.toMutableList() ?: mutableListOf() | 64 | ?.toMutableList() ?: mutableListOf() |
| 65 | |||
| 66 | // TODO: Get system driver information | ||
| 67 | drivers.add(0, Pair("", GpuDriverMetadata())) | ||
| 68 | return drivers | 65 | return drivers |
| 69 | } | 66 | } |
| 70 | 67 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt new file mode 100644 index 000000000..7101ad434 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.viewholder | ||
| 5 | |||
| 6 | import androidx.recyclerview.widget.RecyclerView | ||
| 7 | import androidx.viewbinding.ViewBinding | ||
| 8 | import org.yuzu.yuzu_emu.adapters.AbstractDiffAdapter | ||
| 9 | import org.yuzu.yuzu_emu.adapters.AbstractListAdapter | ||
| 10 | |||
| 11 | /** | ||
| 12 | * [RecyclerView.ViewHolder] meant to work together with a [AbstractDiffAdapter] or a | ||
| 13 | * [AbstractListAdapter] so we can run [bind] on each list item without needing a manual hookup. | ||
| 14 | */ | ||
| 15 | abstract class AbstractViewHolder<Model>(binding: ViewBinding) : | ||
| 16 | RecyclerView.ViewHolder(binding.root) { | ||
| 17 | abstract fun bind(model: Model) | ||
| 18 | } | ||
diff --git a/src/android/app/src/main/res/menu/menu_driver_manager.xml b/src/android/app/src/main/res/menu/menu_driver_manager.xml new file mode 100644 index 000000000..dee5d57b6 --- /dev/null +++ b/src/android/app/src/main/res/menu/menu_driver_manager.xml | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto"> | ||
| 4 | |||
| 5 | <item | ||
| 6 | android:id="@+id/menu_driver_clear" | ||
| 7 | android:icon="@drawable/ic_clear" | ||
| 8 | android:title="@string/clear" | ||
| 9 | app:showAsAction="always" /> | ||
| 10 | |||
| 11 | </menu> | ||