Make rom extensions case insensitive

* Use standard margins
* Some clean up
This commit is contained in:
Willi Ye 2021-04-16 23:59:24 +02:00 committed by ◱ Mark
parent f410b20d58
commit 3c23302b82
20 changed files with 247 additions and 198 deletions

View File

@ -24,13 +24,9 @@ import androidx.core.view.size
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.adapter.AppViewItem import emu.skyline.adapter.*
import emu.skyline.adapter.GenericAdapter
import emu.skyline.adapter.HeaderViewItem
import emu.skyline.adapter.LayoutType
import emu.skyline.data.AppItem import emu.skyline.data.AppItem
import emu.skyline.data.DataItem import emu.skyline.data.DataItem
import emu.skyline.data.HeaderItem import emu.skyline.data.HeaderItem
@ -46,7 +42,7 @@ import kotlin.math.roundToInt
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
companion object { companion object {
private val formatOrder = arrayOf(RomFormat.NSP, RomFormat.XCI, RomFormat.NRO, RomFormat.NSO, RomFormat.NCA) private val formatOrder = listOf(RomFormat.NSP, RomFormat.XCI, RomFormat.NRO, RomFormat.NSO, RomFormat.NCA)
} }
private val binding by lazy { MainActivityBinding.inflate(layoutInflater) } private val binding by lazy { MainActivityBinding.inflate(layoutInflater) }
@ -107,6 +103,18 @@ class MainActivity : AppCompatActivity() {
PreferenceManager.setDefaultValues(this, R.xml.preferences, false) PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
adapter.apply {
setHeaderItems(listOf(HeaderRomFilterItem(formatOrder, if (settings.filter == 0) null else formatOrder[settings.filter - 1]) { romFormat ->
settings.filter = romFormat?.let { formatOrder.indexOf(romFormat) + 1 } ?: 0
formatFilter = romFormat
populateAdapter()
}))
setOnFilterPublishedListener {
binding.appList.post { binding.appList.smoothScrollToPosition(0) }
}
}
setupAppList() setupAppList()
binding.swipeRefreshLayout.apply { binding.swipeRefreshLayout.apply {
@ -116,21 +124,6 @@ class MainActivity : AppCompatActivity() {
setOnRefreshListener { loadRoms(false) } setOnRefreshListener { loadRoms(false) }
} }
for (format in formatOrder) {
binding.chipGroup.addView(Chip(this, null, R.attr.chipChoiceStyle).apply { text = format.name })
}
binding.chipGroup.setOnCheckedChangeListener { group, checkedId ->
for (i in 0 until group.childCount) {
if (group.getChildAt(i).id == checkedId) {
settings.filter = i
formatFilter = if (i == 0) null else formatOrder[i - 1]
populateAdapter()
break
}
}
}
binding.chipGroup.check(binding.chipGroup.getChildAt(settings.filter).id)
viewModel.stateData.observe(this, ::handleState) viewModel.stateData.observe(this, ::handleState)
loadRoms(!settings.refreshRequired) loadRoms(!settings.refreshRequired)
@ -170,7 +163,10 @@ class MainActivity : AppCompatActivity() {
} }
} }
if (layoutParams.spanSize == gridLayoutManager.spanCount) outRect.right = padding if (layoutParams.spanSize == gridLayoutManager.spanCount) {
outRect.left = 0
outRect.right = 0
}
} }
} }
@ -241,7 +237,7 @@ class MainActivity : AppCompatActivity() {
private fun getDataItems() = mutableListOf<DataItem>().apply { private fun getDataItems() = mutableListOf<DataItem>().apply {
appEntries?.let { entries -> appEntries?.let { entries ->
val formats = formatFilter?.let { arrayOf(it) } ?: formatOrder val formats = formatFilter?.let { listOf(it) } ?: formatOrder
for (format in formats) { for (format in formats) {
entries[format]?.let { entries[format]?.let {
add(HeaderItem(format.name)) add(HeaderItem(format.name))

View File

@ -1,5 +1,6 @@
package emu.skyline package emu.skyline
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
@ -16,12 +17,13 @@ class RomProvider @Inject constructor(@ApplicationContext private val context :
/** /**
* This adds all files in [directory] with [extension] as an entry using [RomFile] to load metadata * This adds all files in [directory] with [extension] as an entry using [RomFile] to load metadata
*/ */
@SuppressLint("DefaultLocale")
private fun addEntries(fileFormats : Map<String, RomFormat>, directory : DocumentFile, entries : HashMap<RomFormat, ArrayList<AppEntry>>) { private fun addEntries(fileFormats : Map<String, RomFormat>, directory : DocumentFile, entries : HashMap<RomFormat, ArrayList<AppEntry>>) {
directory.listFiles().forEach { file -> directory.listFiles().forEach { file ->
if (file.isDirectory) { if (file.isDirectory) {
addEntries(fileFormats, file, entries) addEntries(fileFormats, file, entries)
} else { } else {
fileFormats[file.name?.substringAfterLast(".")]?.let { romFormat -> fileFormats[file.name?.substringAfterLast(".")?.toLowerCase()]?.let { romFormat->
entries.getOrPut(romFormat, { arrayListOf() }).add(RomFile(context, romFormat, file.uri).appEntry) entries.getOrPut(romFormat, { arrayListOf() }).add(RomFile(context, romFormat, file.uri).appEntry)
} }
} }
@ -29,8 +31,8 @@ class RomProvider @Inject constructor(@ApplicationContext private val context :
} }
fun loadRoms(searchLocation : Uri) = DocumentFile.fromTreeUri(context, searchLocation)!!.let { documentFile -> fun loadRoms(searchLocation : Uri) = DocumentFile.fromTreeUri(context, searchLocation)!!.let { documentFile ->
val entries = hashMapOf<RomFormat, ArrayList<AppEntry>>() hashMapOf<RomFormat, ArrayList<AppEntry>>().apply {
addEntries(mapOf("nro" to NRO, "nso" to NSO, "nca" to NCA, "nsp" to NSP, "xci" to XCI), documentFile, entries) addEntries(mapOf("nro" to NRO, "nso" to NSO, "nca" to NCA, "nsp" to NSP, "xci" to XCI), documentFile, this)
entries }
} }
} }

View File

@ -84,17 +84,17 @@ private typealias InteractionFunction = (appItem : AppItem) -> Unit
class AppViewItem(var layoutType : LayoutType, private val item : AppItem, private val missingIcon : Bitmap, private val onClick : InteractionFunction, private val onLongClick : InteractionFunction) : GenericListItem<LayoutBinding<*>>() { class AppViewItem(var layoutType : LayoutType, private val item : AppItem, private val missingIcon : Bitmap, private val onClick : InteractionFunction, private val onLongClick : InteractionFunction) : GenericListItem<LayoutBinding<*>>() {
override fun getViewBindingFactory() = LayoutBindingFactory(layoutType) override fun getViewBindingFactory() = LayoutBindingFactory(layoutType)
override fun bind(holder : GenericViewHolder<LayoutBinding<*>>, position : Int) { override fun bind(binding : LayoutBinding<*>, position : Int) {
holder.binding.textTitle.text = item.title binding.textTitle.text = item.title
holder.binding.textSubtitle.text = item.subTitle ?: item.loaderResultString(holder.binding.root.context) binding.textSubtitle.text = item.subTitle ?: item.loaderResultString(binding.root.context)
holder.binding.icon.setImageBitmap(item.icon ?: missingIcon) binding.icon.setImageBitmap(item.icon ?: missingIcon)
if (layoutType == LayoutType.List) { if (layoutType == LayoutType.List) {
holder.binding.icon.setOnClickListener { showIconDialog(it.context, item) } binding.icon.setOnClickListener { showIconDialog(it.context, item) }
} }
holder.itemView.findViewById<View>(R.id.item_click_layout).apply { binding.root.findViewById<View>(R.id.item_click_layout).apply {
setOnClickListener { onClick.invoke(item) } setOnClickListener { onClick.invoke(item) }
setOnLongClickListener { true.also { onLongClick.invoke(item) } } setOnLongClickListener { true.also { onLongClick.invoke(item) } }
} }

View File

@ -16,6 +16,8 @@ import info.debatty.java.stringsimilarity.Cosine
import info.debatty.java.stringsimilarity.JaroWinkler import info.debatty.java.stringsimilarity.JaroWinkler
import java.util.* import java.util.*
typealias OnFilterPublishedListener = () -> Unit
/** /**
* Can handle any view types with [GenericListItem] implemented, [GenericListItem] are differentiated by the return value of [GenericListItem.getViewBindingFactory] * Can handle any view types with [GenericListItem] implemented, [GenericListItem] are differentiated by the return value of [GenericListItem.getViewBindingFactory]
*/ */
@ -29,6 +31,7 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>(), F
} }
private val asyncListDiffer = AsyncListDiffer(this, DIFFER) private val asyncListDiffer = AsyncListDiffer(this, DIFFER)
private val headerItems = mutableListOf<GenericListItem<out ViewBinding>>()
private val allItems = mutableListOf<GenericListItem<out ViewBinding>>() private val allItems = mutableListOf<GenericListItem<out ViewBinding>>()
val currentItems : List<GenericListItem<in ViewBinding>> get() = asyncListDiffer.currentList val currentItems : List<GenericListItem<in ViewBinding>> get() = asyncListDiffer.currentList
@ -36,12 +39,14 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>(), F
private val viewTypesMapping = mutableMapOf<ViewBindingFactory, Int>() private val viewTypesMapping = mutableMapOf<ViewBindingFactory, Int>()
private var onFilterPublishedListener : OnFilterPublishedListener? = null
override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) = GenericViewHolder(viewTypesMapping.filterValues { it == viewType }.keys.single().createBinding(parent)) override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) = GenericViewHolder(viewTypesMapping.filterValues { it == viewType }.keys.single().createBinding(parent))
override fun onBindViewHolder(holder : GenericViewHolder<ViewBinding>, position : Int) { override fun onBindViewHolder(holder : GenericViewHolder<ViewBinding>, position : Int) {
currentItems[position].apply { currentItems[position].apply {
adapter = this@GenericAdapter adapter = this@GenericAdapter
bind(holder, position) bind(holder.binding, position)
} }
} }
@ -49,12 +54,22 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>(), F
override fun getItemViewType(position : Int) = viewTypesMapping.getOrPut(currentItems[position].getViewBindingFactory()) { viewTypesMapping.size } override fun getItemViewType(position : Int) = viewTypesMapping.getOrPut(currentItems[position].getViewBindingFactory()) { viewTypesMapping.size }
fun setHeaderItems(items : List<GenericListItem<*>>) {
headerItems.clear()
headerItems.addAll(items)
filter.filter(currentSearchTerm)
}
fun setItems(items : List<GenericListItem<*>>) { fun setItems(items : List<GenericListItem<*>>) {
allItems.clear() allItems.clear()
allItems.addAll(items) allItems.addAll(items)
filter.filter(currentSearchTerm) filter.filter(currentSearchTerm)
} }
fun setOnFilterPublishedListener(listener : OnFilterPublishedListener) {
onFilterPublishedListener = listener
}
/** /**
* This returns an instance of the filter object which is used to search for items in the view * This returns an instance of the filter object which is used to search for items in the view
*/ */
@ -84,13 +99,11 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>(), F
/** /**
* This performs filtering on the items in [allItems] based on similarity to [term] * This performs filtering on the items in [allItems] based on similarity to [term]
*/ */
override fun performFiltering(term : CharSequence) : FilterResults { override fun performFiltering(term : CharSequence) = (term as String).toLowerCase(Locale.getDefault()).let { lowerCaseTerm ->
val results = FilterResults() currentSearchTerm = lowerCaseTerm
currentSearchTerm = (term as String).toLowerCase(Locale.getDefault())
if (term.isEmpty()) { with(if (term.isEmpty()) {
results.values = allItems.toMutableList() allItems.toMutableList()
results.count = allItems.size
} else { } else {
val filterData = mutableListOf<GenericListItem<*>>() val filterData = mutableListOf<GenericListItem<*>>()
@ -100,10 +113,13 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>(), F
for (result in topResults) for (result in topResults)
if (result.score >= avgScore) filterData.add(result.item) if (result.score >= avgScore) filterData.add(result.item)
results.values = filterData filterData
results.count = filterData.size }) {
FilterResults().apply {
values = headerItems + this@with
count = headerItems.size + size
}
} }
return results
} }
/** /**
@ -112,6 +128,7 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>(), F
override fun publishResults(charSequence : CharSequence, results : FilterResults) { override fun publishResults(charSequence : CharSequence, results : FilterResults) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
asyncListDiffer.submitList(results.values as List<GenericListItem<ViewBinding>>) asyncListDiffer.submitList(results.values as List<GenericListItem<ViewBinding>>)
onFilterPublishedListener?.invoke()
} }
} }
} }

View File

@ -24,7 +24,7 @@ abstract class GenericListItem<V : ViewBinding> {
abstract fun getViewBindingFactory() : ViewBindingFactory abstract fun getViewBindingFactory() : ViewBindingFactory
abstract fun bind(holder : GenericViewHolder<V>, position : Int) abstract fun bind(binding : V, position : Int)
/** /**
* Used for filtering * Used for filtering
@ -33,10 +33,7 @@ abstract class GenericListItem<V : ViewBinding> {
open fun areItemsTheSame(other : GenericListItem<V>) = this == other open fun areItemsTheSame(other : GenericListItem<V>) = this == other
/** open fun areContentsTheSame(other : GenericListItem<V>) = this == other
* Will only be called when [areItemsTheSame] returns true, thus returning true by default
*/
open fun areContentsTheSame(other : GenericListItem<V>) = true
open val fullSpan : Boolean = false open val fullSpan : Boolean = false
} }

View File

@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.adapter
import android.view.ViewGroup
import com.google.android.material.chip.Chip
import emu.skyline.R
import emu.skyline.databinding.HeaderRomFilterBinding
import emu.skyline.loader.RomFormat
object HeaderRomFilterBindingFactory : ViewBindingFactory {
override fun createBinding(parent : ViewGroup) = HeaderRomFilterBinding.inflate(parent.inflater(), parent, false)
}
typealias OnFilterClickedListener = (format : RomFormat?) -> Unit
class HeaderRomFilterItem(private val formats : List<RomFormat>, selectedFormat : RomFormat?, private val onFilterClickedListener : OnFilterClickedListener) : GenericListItem<HeaderRomFilterBinding>() {
private var selection = selectedFormat?.let { formats.indexOf(it) + 1 } ?: 0
override fun getViewBindingFactory() = HeaderRomFilterBindingFactory
override fun bind(binding : HeaderRomFilterBinding, position : Int) {
binding.chipGroup.removeViews(1, binding.chipGroup.childCount - 1)
for (format in formats) {
binding.chipGroup.addView(Chip(binding.root.context, null, R.attr.chipChoiceStyle).apply { text = format.name })
}
binding.chipGroup.setOnCheckedChangeListener { group, checkedId ->
for (i in 0 until group.childCount) {
if (group.getChildAt(i).id == checkedId) {
selection = i
onFilterClickedListener(if (i == 0) null else formats[i - 1])
break
}
}
}
binding.chipGroup.check(binding.chipGroup.getChildAt(selection).id)
}
override val fullSpan = true
}

View File

@ -15,8 +15,8 @@ object HeaderBindingFactory : ViewBindingFactory {
class HeaderViewItem(private val text : String) : GenericListItem<SectionItemBinding>() { class HeaderViewItem(private val text : String) : GenericListItem<SectionItemBinding>() {
override fun getViewBindingFactory() = HeaderBindingFactory override fun getViewBindingFactory() = HeaderBindingFactory
override fun bind(holder : GenericViewHolder<SectionItemBinding>, position : Int) { override fun bind(binding : SectionItemBinding, position : Int) {
holder.binding.textTitle.text = text binding.textTitle.text = text
} }
override fun toString() = "" override fun toString() = ""

View File

@ -47,11 +47,11 @@ class LogBinding(parent : ViewGroup) : ILogBinding {
data class LogViewItem(private val compact : Boolean, private val message : String, private val level : String) : GenericListItem<ILogBinding>() { data class LogViewItem(private val compact : Boolean, private val message : String, private val level : String) : GenericListItem<ILogBinding>() {
override fun getViewBindingFactory() = LogBindingFactory(compact) override fun getViewBindingFactory() = LogBindingFactory(compact)
override fun bind(holder : GenericViewHolder<ILogBinding>, position : Int) { override fun bind(binding : ILogBinding, position : Int) {
holder.binding.textTitle.text = message binding.textTitle.text = message
holder.binding.textSubTitle?.text = level binding.textSubTitle?.text = level
holder.binding.root.setOnClickListener { binding.root.setOnClickListener {
it.context.getSystemService(ClipboardManager::class.java).setPrimaryClip(ClipData.newPlainText("Log Message", "$message ($level)")) it.context.getSystemService(ClipboardManager::class.java).setPrimaryClip(ClipData.newPlainText("Log Message", "$message ($level)"))
Toast.makeText(it.context, "Copied to clipboard", Toast.LENGTH_LONG).show() Toast.makeText(it.context, "Copied to clipboard", Toast.LENGTH_LONG).show()
} }

View File

@ -6,7 +6,6 @@
package emu.skyline.adapter.controller package emu.skyline.adapter.controller
import emu.skyline.adapter.GenericListItem import emu.skyline.adapter.GenericListItem
import emu.skyline.adapter.GenericViewHolder
import emu.skyline.databinding.ControllerItemBinding import emu.skyline.databinding.ControllerItemBinding
import emu.skyline.di.getInputManager import emu.skyline.di.getInputManager
import emu.skyline.input.ButtonGuestEvent import emu.skyline.input.ButtonGuestEvent
@ -16,14 +15,14 @@ import emu.skyline.input.ButtonId
* This item is used to display a particular [button] mapping for the controller * This item is used to display a particular [button] mapping for the controller
*/ */
class ControllerButtonViewItem(private val controllerId : Int, val button : ButtonId, private val onClick : (item : ControllerButtonViewItem, position : Int) -> Unit) : ControllerViewItem() { class ControllerButtonViewItem(private val controllerId : Int, val button : ButtonId, private val onClick : (item : ControllerButtonViewItem, position : Int) -> Unit) : ControllerViewItem() {
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) { override fun bind(binding : ControllerItemBinding, position : Int) {
content = button.long?.let { holder.itemView.context.getString(it) } ?: button.toString() content = button.long?.let { binding.root.context.getString(it) } ?: button.toString()
val guestEvent = ButtonGuestEvent(controllerId, button) val guestEvent = ButtonGuestEvent(controllerId, button)
subContent = holder.binding.root.context.getInputManager().eventMap.filter { it.value is ButtonGuestEvent && it.value == guestEvent }.keys.firstOrNull()?.toString() ?: "" subContent = binding.root.context.getInputManager().eventMap.filter { it.value is ButtonGuestEvent && it.value == guestEvent }.keys.firstOrNull()?.toString() ?: ""
super.bind(holder, position) super.bind(binding, position)
holder.binding.root.setOnClickListener { onClick.invoke(this, position) } binding.root.setOnClickListener { onClick.invoke(this, position) }
} }
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerButtonViewItem && controllerId == other.controllerId override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerButtonViewItem && controllerId == other.controllerId

View File

@ -20,13 +20,13 @@ object ControllerCheckBoxBindingFactory : ViewBindingFactory {
class ControllerCheckBoxViewItem(var title : String, var summary : String, var checked : Boolean, private val onCheckedChange : (item : ControllerCheckBoxViewItem, position : Int) -> Unit) : GenericListItem<ControllerCheckboxItemBinding>() { class ControllerCheckBoxViewItem(var title : String, var summary : String, var checked : Boolean, private val onCheckedChange : (item : ControllerCheckBoxViewItem, position : Int) -> Unit) : GenericListItem<ControllerCheckboxItemBinding>() {
override fun getViewBindingFactory() = ControllerCheckBoxBindingFactory override fun getViewBindingFactory() = ControllerCheckBoxBindingFactory
override fun bind(holder : GenericViewHolder<ControllerCheckboxItemBinding>, position : Int) { override fun bind(binding : ControllerCheckboxItemBinding, position : Int) {
holder.binding.textTitle.isGone = title.isEmpty() binding.textTitle.isGone = title.isEmpty()
holder.binding.textTitle.text = title binding.textTitle.text = title
holder.binding.textSubtitle.isGone = summary.isEmpty() binding.textSubtitle.isGone = summary.isEmpty()
holder.binding.textSubtitle.text = summary binding.textSubtitle.text = summary
holder.binding.checkbox.isChecked = checked binding.checkbox.isChecked = checked
holder.itemView.setOnClickListener { binding.root.setOnClickListener {
checked = !checked checked = !checked
onCheckedChange.invoke(this, position) onCheckedChange.invoke(this, position)
} }

View File

@ -19,8 +19,8 @@ import emu.skyline.input.JoyConLeftController
* @param type The type of controller setting this item is displaying * @param type The type of controller setting this item is displaying
*/ */
class ControllerGeneralViewItem(private val controllerId : Int, val type : GeneralType, private val onClick : (item : ControllerGeneralViewItem, position : Int) -> Unit) : ControllerViewItem() { class ControllerGeneralViewItem(private val controllerId : Int, val type : GeneralType, private val onClick : (item : ControllerGeneralViewItem, position : Int) -> Unit) : ControllerViewItem() {
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) { override fun bind(binding : ControllerItemBinding, position : Int) {
val context = holder.itemView.context val context = binding.root.context
val controller = context.getInputManager().controllers[controllerId]!! val controller = context.getInputManager().controllers[controllerId]!!
content = context.getString(type.stringRes) content = context.getString(type.stringRes)
@ -36,9 +36,9 @@ class ControllerGeneralViewItem(private val controllerId : Int, val type : Gener
GeneralType.RumbleDevice -> controller.rumbleDeviceName ?: context.getString(R.string.none) GeneralType.RumbleDevice -> controller.rumbleDeviceName ?: context.getString(R.string.none)
} }
super.bind(holder, position) super.bind(binding, position)
holder.binding.root.setOnClickListener { onClick.invoke(this, position) } binding.root.setOnClickListener { onClick.invoke(this, position) }
} }
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerGeneralViewItem && controllerId == other.controllerId override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerGeneralViewItem && controllerId == other.controllerId

View File

@ -2,7 +2,6 @@ package emu.skyline.adapter.controller
import android.view.ViewGroup import android.view.ViewGroup
import emu.skyline.adapter.GenericListItem import emu.skyline.adapter.GenericListItem
import emu.skyline.adapter.GenericViewHolder
import emu.skyline.adapter.ViewBindingFactory import emu.skyline.adapter.ViewBindingFactory
import emu.skyline.adapter.inflater import emu.skyline.adapter.inflater
import emu.skyline.databinding.ControllerHeaderBinding import emu.skyline.databinding.ControllerHeaderBinding
@ -14,8 +13,8 @@ object ControllerHeaderBindingFactory : ViewBindingFactory {
class ControllerHeaderItem(private val text : String) : GenericListItem<ControllerHeaderBinding>() { class ControllerHeaderItem(private val text : String) : GenericListItem<ControllerHeaderBinding>() {
override fun getViewBindingFactory() = ControllerHeaderBindingFactory override fun getViewBindingFactory() = ControllerHeaderBindingFactory
override fun bind(holder : GenericViewHolder<ControllerHeaderBinding>, position : Int) { override fun bind(binding : ControllerHeaderBinding, position : Int) {
holder.binding.root.text = text binding.root.text = text
} }
override fun areItemsTheSame(other : GenericListItem<ControllerHeaderBinding>) = other is ControllerHeaderItem override fun areItemsTheSame(other : GenericListItem<ControllerHeaderBinding>) = other is ControllerHeaderItem

View File

@ -18,8 +18,8 @@ import emu.skyline.input.StickId
* This item is used to display all information regarding a [stick] and it's mappings for the controller * This item is used to display all information regarding a [stick] and it's mappings for the controller
*/ */
class ControllerStickViewItem(private val controllerId : Int, val stick : StickId, private val onClick : (item : ControllerStickViewItem, position : Int) -> Unit) : ControllerViewItem(stick.toString()) { class ControllerStickViewItem(private val controllerId : Int, val stick : StickId, private val onClick : (item : ControllerStickViewItem, position : Int) -> Unit) : ControllerViewItem(stick.toString()) {
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) { override fun bind(binding : ControllerItemBinding, position : Int) {
val context = holder.itemView.context val context = binding.root.context
val inputManager = context.getInputManager() val inputManager = context.getInputManager()
val buttonGuestEvent = ButtonGuestEvent(controllerId, stick.button) val buttonGuestEvent = ButtonGuestEvent(controllerId, stick.button)
@ -39,9 +39,9 @@ class ControllerStickViewItem(private val controllerId : Int, val stick : StickI
subContent = "${context.getString(R.string.button)}: $button\n${context.getString(R.string.up)}: $yAxisPlus\n${context.getString(R.string.down)}: $yAxisMinus\n${context.getString(R.string.left)}: $xAxisMinus\n${context.getString(R.string.right)}: $xAxisPlus" subContent = "${context.getString(R.string.button)}: $button\n${context.getString(R.string.up)}: $yAxisPlus\n${context.getString(R.string.down)}: $yAxisMinus\n${context.getString(R.string.left)}: $xAxisMinus\n${context.getString(R.string.right)}: $xAxisPlus"
super.bind(holder, position) super.bind(binding, position)
holder.binding.root.setOnClickListener { onClick.invoke(this, position) } binding.root.setOnClickListener { onClick.invoke(this, position) }
} }
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerStickViewItem && controllerId == other.controllerId override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerStickViewItem && controllerId == other.controllerId

View File

@ -15,15 +15,15 @@ import emu.skyline.input.ControllerType
* This item is used to display the [type] of the currently active controller * This item is used to display the [type] of the currently active controller
*/ */
class ControllerTypeViewItem(private val type : ControllerType, private val onClick : (item : ControllerTypeViewItem, position : Int) -> Unit) : ControllerViewItem() { class ControllerTypeViewItem(private val type : ControllerType, private val onClick : (item : ControllerTypeViewItem, position : Int) -> Unit) : ControllerViewItem() {
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) { override fun bind(binding : ControllerItemBinding, position : Int) {
val context = holder.itemView.context val context = binding.root.context
content = context.getString(R.string.controller_type) content = context.getString(R.string.controller_type)
subContent = context.getString(type.stringRes) subContent = context.getString(type.stringRes)
super.bind(holder, position) super.bind(binding, position)
holder.itemView.setOnClickListener { onClick.invoke(this, position) } binding.root.setOnClickListener { onClick.invoke(this, position) }
} }
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerTypeViewItem override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerTypeViewItem

View File

@ -23,17 +23,17 @@ open class ControllerViewItem(var content : String = "", var subContent : String
override fun getViewBindingFactory() = ControllerBindingFactory override fun getViewBindingFactory() = ControllerBindingFactory
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) { override fun bind(binding : ControllerItemBinding, position : Int) {
this.position = position this.position = position
holder.binding.textTitle.apply { binding.textTitle.apply {
isGone = content.isEmpty() isGone = content.isEmpty()
text = content text = content
} }
holder.binding.textSubtitle.apply { binding.textSubtitle.apply {
isGone = subContent.isEmpty() isGone = subContent.isEmpty()
text = subContent text = subContent
} }
onClick?.let { onClick -> holder.itemView.setOnClickListener { onClick.invoke() } } onClick?.let { onClick -> binding.root.setOnClickListener { onClick.invoke() } }
} }
fun update() = adapter?.notifyItemChanged(position) fun update() = adapter?.notifyItemChanged(position)

View File

@ -11,10 +11,10 @@
android:id="@+id/item_click_layout" android:id="@+id/item_click_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="14dp" android:layout_marginStart="10dp"
android:layout_marginTop="14dp" android:layout_marginTop="10dp"
android:layout_marginEnd="14dp" android:layout_marginEnd="10dp"
android:layout_marginBottom="14dp" android:layout_marginBottom="10dp"
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:cardElevation="2dp"> app:cardElevation="2dp">
@ -31,8 +31,8 @@
android:id="@+id/text_title" android:id="@+id/text_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="20dp" android:layout_marginStart="10dp"
android:layout_marginEnd="20dp" android:layout_marginEnd="10dp"
android:ellipsize="marquee" android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever" android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true" android:singleLine="true"
@ -44,9 +44,9 @@
android:id="@+id/text_subtitle" android:id="@+id/text_subtitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="20dp" android:layout_marginStart="10dp"
android:layout_marginEnd="20dp" android:layout_marginEnd="10dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="10dp"
android:ellipsize="marquee" android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever" android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true" android:singleLine="true"

View File

@ -5,12 +5,12 @@
android:id="@+id/item_click_layout" android:id="@+id/item_click_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:layout_marginStart="10dp"
android:layout_marginTop="12dp" android:layout_marginTop="10dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="10dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="10dp"
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:cardElevation="4dp"> app:cardElevation="2dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -12,8 +12,8 @@
<ImageView <ImageView
android:id="@+id/icon" android:id="@+id/icon"
android:layout_width="50dp" android:layout_width="56dp"
android:layout_height="50dp" android:layout_height="56dp"
android:contentDescription="@string/icon" android:contentDescription="@string/icon"
android:focusable="false" android:focusable="false"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="4dp"
app:checkedChip="@id/all_chip"
app:chipSpacingHorizontal="16dp"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@+id/all_chip"
style="?attr/chipChoiceStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>

View File

@ -79,9 +79,9 @@
android:id="@+id/title_text" android:id="@+id/title_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="18dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="18dp" android:layout_marginEnd="16dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/app_name" android:text="@string/app_name"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
@ -94,8 +94,8 @@
android:id="@+id/sub_text" android:id="@+id/sub_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="18dp" android:layout_marginStart="16dp"
android:layout_marginEnd="18dp" android:layout_marginEnd="16dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:letterSpacing="0.1" android:letterSpacing="0.1"
android:text="@string/emulator" android:text="@string/emulator"
@ -108,9 +108,9 @@
android:id="@+id/search_bar" android:id="@+id/search_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="64dp"
android:layout_marginStart="12dp" android:layout_marginStart="10dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="10dp"
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:cardElevation="2dp" app:cardElevation="2dp"
app:layout_constraintTop_toBottomOf="@id/sub_text" /> app:layout_constraintTop_toBottomOf="@id/sub_text" />
@ -125,44 +125,11 @@
android:layout_marginTop="-8dp" android:layout_marginTop="-8dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="18dp"
android:paddingTop="16dp"
android:paddingEnd="18dp"
android:paddingBottom="4dp"
app:checkedChip="@id/all_chip"
app:chipSpacingHorizontal="16dp"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@+id/all_chip"
style="?attr/chipChoiceStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/app_list" android:id="@+id/app_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" /> android:clipToPadding="false"
</LinearLayout> android:overScrollMode="never" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>