diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 52be86d7..dbfb038c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -59,6 +59,13 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="emu.skyline.SettingsActivity" />
+
+
+
+ try {
+ val logMeta = logLine.split("|", limit = 3)
+
+ if (logMeta[0].startsWith("1")) {
+ val level = logMeta[1].toInt()
+ if (level > logLevel) return@forEachLine
+
+ adapter.addItem(LogViewItem(compact, logMeta[2].replace('\\', '\n'), logLevels[level]))
+ } else {
+ adapter.addItem(HeaderViewItem(logMeta[1]))
+ }
+ } catch (ignored : IndexOutOfBoundsException) {
+ } catch (ignored : NumberFormatException) {
+ }
}
} catch (e : FileNotFoundException) {
Log.w("Logger", "IO Error during access of log file: " + e.message)
@@ -149,10 +161,10 @@ class LogActivity : AppCompatActivity() {
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
urlConnection.setRequestProperty("Referer", "https://hastebin.com/")
- val bufferedWriter = urlConnection.outputStream.bufferedWriter()
- bufferedWriter.write(logFile.readText())
- bufferedWriter.flush()
- bufferedWriter.close()
+ urlConnection.outputStream.bufferedWriter().use {
+ it.write(logFile.readText())
+ it.flush()
+ }
if (urlConnection.responseCode != 200) {
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.responseCode)
diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt
index 47a467da..f3341527 100644
--- a/app/src/main/java/emu/skyline/MainActivity.kt
+++ b/app/src/main/java/emu/skyline/MainActivity.kt
@@ -18,17 +18,21 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.SearchView
import androidx.core.animation.doOnEnd
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.toBitmap
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
-import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
-import emu.skyline.adapter.AppAdapter
-import emu.skyline.adapter.GridLayoutSpan
+import emu.skyline.adapter.AppViewItem
+import emu.skyline.adapter.GenericAdapter
+import emu.skyline.adapter.HeaderViewItem
import emu.skyline.adapter.LayoutType
import emu.skyline.data.AppItem
+import emu.skyline.data.BaseElement
+import emu.skyline.data.BaseHeader
import emu.skyline.loader.LoaderResult
import emu.skyline.loader.RomFile
import emu.skyline.loader.RomFormat
@@ -41,6 +45,10 @@ import kotlin.concurrent.thread
import kotlin.math.ceil
class MainActivity : AppCompatActivity() {
+ companion object {
+ private val TAG = MainActivity::class.java.simpleName
+ }
+
/**
* This is used to get/set shared preferences
*/
@@ -49,29 +57,38 @@ class MainActivity : AppCompatActivity() {
/**
* The adapter used for adding elements to [app_list]
*/
- private lateinit var adapter : AppAdapter
+ private val adapter = GenericAdapter()
private var reloading = AtomicBoolean()
private val layoutType get() = LayoutType.values()[sharedPreferences.getString("layout_type", "1")!!.toInt()]
+ private val missingIcon by lazy { ContextCompat.getDrawable(this, R.drawable.default_icon)!!.toBitmap(256, 256) }
+
+ private fun AppItem.toViewItem() = AppViewItem(layoutType, this, missingIcon, ::selectStartGame, ::selectShowGameDialog)
+
/**
* This adds all files in [directory] with [extension] as an entry in [adapter] using [RomFile] to load metadata
*/
- private fun addEntries(extension : String, romFormat : RomFormat, directory : DocumentFile, found : Boolean = false) : Boolean {
+ private fun addEntries(extension : String, romFormat : RomFormat, directory : DocumentFile, romElements : ArrayList, found : Boolean = false) : Boolean {
var foundCurrent = found
directory.listFiles().forEach { file ->
if (file.isDirectory) {
- foundCurrent = addEntries(extension, romFormat, file, foundCurrent)
+ foundCurrent = addEntries(extension, romFormat, file, romElements, foundCurrent)
} else {
if (extension.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
RomFile(this, romFormat, file.uri).let { romFile ->
val finalFoundCurrent = foundCurrent
runOnUiThread {
- if (!finalFoundCurrent) adapter.addHeader(romFormat.name)
+ if (!finalFoundCurrent) {
+ romElements.add(BaseHeader(romFormat.name))
+ adapter.addItem(HeaderViewItem(romFormat.name))
+ }
- adapter.addItem(AppItem(romFile.appEntry))
+ romElements.add(AppItem(romFile.appEntry).also {
+ adapter.addItem(it.toViewItem())
+ })
}
foundCurrent = true
@@ -86,15 +103,22 @@ class MainActivity : AppCompatActivity() {
/**
* This refreshes the contents of the adapter by either trying to load cached adapter data or searches for them to recreate a list
*
- * @param tryLoad If this is false then trying to load cached adapter data is skipped entirely
+ * @param loadFromFile If this is false then trying to load cached adapter data is skipped entirely
*/
- private fun refreshAdapter(tryLoad : Boolean) {
- if (tryLoad) {
+ private fun refreshAdapter(loadFromFile : Boolean) {
+ val romsFile = File(applicationContext.filesDir.canonicalPath + "/roms.bin")
+
+ if (loadFromFile) {
try {
- adapter.load(File(applicationContext.filesDir.canonicalPath + "/roms.bin"))
+ loadSerializedList(romsFile).forEach {
+ if (it is BaseHeader)
+ adapter.addItem(HeaderViewItem(it.title))
+ else if (it is AppItem)
+ adapter.addItem(it.toViewItem())
+ }
return
} catch (e : Exception) {
- Log.w("refreshFiles", "Ran into exception while loading: ${e.message}")
+ Log.w(TAG, "Ran into exception while loading: ${e.message}")
}
}
@@ -107,22 +131,26 @@ class MainActivity : AppCompatActivity() {
}
try {
- runOnUiThread { adapter.clear() }
+ runOnUiThread { adapter.removeAllItems() }
val searchLocation = DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!
- var foundRoms = addEntries("nro", RomFormat.NRO, searchLocation)
- foundRoms = foundRoms or addEntries("nso", RomFormat.NSO, searchLocation)
- foundRoms = foundRoms or addEntries("nca", RomFormat.NCA, searchLocation)
- foundRoms = foundRoms or addEntries("nsp", RomFormat.NSP, searchLocation)
+ val romElements = ArrayList()
+ addEntries("nro", RomFormat.NRO, searchLocation, romElements)
+ addEntries("nso", RomFormat.NSO, searchLocation, romElements)
+ addEntries("nca", RomFormat.NCA, searchLocation, romElements)
+ addEntries("nsp", RomFormat.NSP, searchLocation, romElements)
runOnUiThread {
- if (!foundRoms) adapter.addHeader(getString(R.string.no_rom))
+ if (romElements.isEmpty()) {
+ romElements.add(BaseHeader(getString(R.string.no_rom)))
+ adapter.addItem(HeaderViewItem(getString(R.string.no_rom)))
+ }
try {
- adapter.save(File(applicationContext.filesDir.canonicalPath + "/roms.bin"))
+ romElements.serialize(romsFile)
} catch (e : IOException) {
- Log.w("refreshFiles", "Ran into exception while saving: ${e.message}")
+ Log.w(TAG, "Ran into exception while saving: ${e.message}")
}
}
@@ -216,22 +244,27 @@ class MainActivity : AppCompatActivity() {
}
}
+ private fun setAppListDecoration() {
+ when (layoutType) {
+ LayoutType.List -> app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
+
+ LayoutType.Grid, LayoutType.GridCompact -> if (app_list.itemDecorationCount > 0) app_list.removeItemDecorationAt(0)
+ }
+ }
+
private fun setupAppList() {
+ app_list.adapter = adapter
+
val itemWidth = 225
val metrics = resources.displayMetrics
val gridSpan = ceil((metrics.widthPixels / metrics.density) / itemWidth).toInt()
- adapter = AppAdapter(layoutType = layoutType, onClick = ::selectStartGame, onLongClick = ::selectShowGameDialog)
- app_list.adapter = adapter
- app_list.layoutManager = when (adapter.layoutType) {
- LayoutType.List -> LinearLayoutManager(this).also { app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL)) }
-
- LayoutType.Grid, LayoutType.GridCompact -> GridLayoutManager(this, gridSpan).apply {
- spanSizeLookup = GridLayoutSpan(adapter, gridSpan).also {
- if (app_list.itemDecorationCount > 0) app_list.removeItemDecorationAt(0)
- }
+ app_list.layoutManager = GridLayoutManager(this, gridSpan).apply {
+ spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
+ override fun getSpanSize(position : Int) = if (layoutType == LayoutType.List || adapter.currentItems[position] is HeaderViewItem) gridSpan else 1
}
}
+ setAppListDecoration()
if (sharedPreferences.getString("search_location", "") == "") {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
@@ -337,8 +370,19 @@ class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
- if (layoutType != adapter.layoutType) {
- setupAppList()
+ var layoutTypeChanged = false
+ for (appViewItem in adapter.allItems.filterIsInstance(AppViewItem::class.java)) {
+ if (layoutType != appViewItem.layoutType) {
+ appViewItem.layoutType = layoutType
+ layoutTypeChanged = true
+ } else {
+ break
+ }
+ }
+
+ if (layoutTypeChanged) {
+ adapter.notifyAllItemsChanged()
+ setAppListDecoration()
}
val gridCardMagin = resources.getDimensionPixelSize(R.dimen.app_card_margin_half)
diff --git a/app/src/main/java/emu/skyline/SerializationHelper.kt b/app/src/main/java/emu/skyline/SerializationHelper.kt
new file mode 100644
index 00000000..5dcbd958
--- /dev/null
+++ b/app/src/main/java/emu/skyline/SerializationHelper.kt
@@ -0,0 +1,22 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline
+
+import java.io.File
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
+import java.io.Serializable
+
+fun ArrayList.serialize(file : File) {
+ ObjectOutputStream(file.outputStream()).use {
+ it.writeObject(this)
+ }
+}
+
+@Suppress("UNCHECKED_CAST")
+fun loadSerializedList(file : File) = ObjectInputStream(file.inputStream()).use {
+ it.readObject()
+} as ArrayList
diff --git a/app/src/main/java/emu/skyline/adapter/AppAdapter.kt b/app/src/main/java/emu/skyline/adapter/AppAdapter.kt
deleted file mode 100644
index 689bc65b..00000000
--- a/app/src/main/java/emu/skyline/adapter/AppAdapter.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * SPDX-License-Identifier: MPL-2.0
- * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
- */
-
-package emu.skyline.adapter
-
-import android.app.Dialog
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.Window
-import android.widget.ImageView
-import android.widget.RelativeLayout
-import androidx.core.content.ContextCompat
-import androidx.core.graphics.drawable.toBitmap
-import androidx.recyclerview.widget.RecyclerView
-import emu.skyline.R
-import emu.skyline.data.AppItem
-import kotlinx.android.extensions.LayoutContainer
-import kotlinx.android.synthetic.main.app_item_grid_compact.*
-
-/**
- * This enumerates the type of layouts the menu can be in
- */
-enum class LayoutType(val layoutRes : Int) {
- List(R.layout.app_item_linear),
- Grid(R.layout.app_item_grid),
- GridCompact(R.layout.app_item_grid_compact)
-}
-
-private typealias InteractionFunction = (appItem : AppItem) -> Unit
-
-/**
- * This adapter is used to display all found applications using their metadata
- */
-internal class AppAdapter(val layoutType : LayoutType, private val onClick : InteractionFunction, private val onLongClick : InteractionFunction) : HeaderAdapter() {
- private lateinit var context : Context
- private val missingIcon by lazy { ContextCompat.getDrawable(context, R.drawable.default_icon)!!.toBitmap(256, 256) }
-
- /**
- * This adds a header to the view with the contents of [string]
- */
- fun addHeader(string : String) {
- super.addHeader(BaseHeader(string))
- }
-
- private class ItemViewHolder(override val containerView : View) : RecyclerView.ViewHolder(containerView), LayoutContainer
-
- private class HeaderViewHolder(override val containerView : View) : RecyclerView.ViewHolder(containerView), LayoutContainer
-
- /**
- * This function creates the view-holder of type [viewType] with the layout parent as [parent]
- */
- override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) : RecyclerView.ViewHolder {
- context = parent.context
-
- val inflater = LayoutInflater.from(context)
- val view = when (ElementType.values()[viewType]) {
- ElementType.Item -> inflater.inflate(layoutType.layoutRes, parent, false)
-
- ElementType.Header -> inflater.inflate(R.layout.section_item, parent, false)
- }
-
- return when (ElementType.values()[viewType]) {
- ElementType.Item -> ItemViewHolder(view)
-
- ElementType.Header -> HeaderViewHolder(view)
- }
- }
-
- /**
- * This function binds the item at [position] to the supplied [holder]
- */
- override fun onBindViewHolder(holder : RecyclerView.ViewHolder, position : Int) {
- val item = getItem(position)
-
- if (item is AppItem && holder is ItemViewHolder) {
- holder.text_title.text = item.title
- holder.text_subtitle.text = item.subTitle ?: item.loaderResultString(holder.text_subtitle.context)
-
- holder.icon.setImageBitmap(item.icon ?: missingIcon)
-
- if (layoutType == LayoutType.List) {
- holder.icon.setOnClickListener { showIconDialog(item) }
- }
-
- when (layoutType) {
- LayoutType.List -> holder.itemView
- LayoutType.Grid, LayoutType.GridCompact -> holder.card_app_item_grid
- }.apply {
- setOnClickListener { onClick.invoke(item) }
- setOnLongClickListener { true.also { onLongClick.invoke(item) } }
- }
- } else if (item is BaseHeader && holder is HeaderViewHolder) {
- holder.text_title.text = item.title
- }
- }
-
- private fun showIconDialog(appItem : AppItem) {
- val builder = Dialog(context)
- builder.requestWindowFeature(Window.FEATURE_NO_TITLE)
- builder.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
-
- val imageView = ImageView(context)
- imageView.setImageBitmap(appItem.icon ?: missingIcon)
-
- builder.addContentView(imageView, RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
- builder.show()
- }
-}
diff --git a/app/src/main/java/emu/skyline/adapter/AppViewItem.kt b/app/src/main/java/emu/skyline/adapter/AppViewItem.kt
new file mode 100644
index 00000000..6a70514e
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/AppViewItem.kt
@@ -0,0 +1,73 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import emu.skyline.R
+import emu.skyline.data.AppItem
+import kotlinx.android.synthetic.main.app_item_grid_compact.*
+
+/**
+ * This enumerates the type of layouts the menu can be in
+ */
+enum class LayoutType(val layoutRes : Int) {
+ List(R.layout.app_item_linear),
+ Grid(R.layout.app_item_grid),
+ GridCompact(R.layout.app_item_grid_compact)
+}
+
+private typealias InteractionFunction = (appItem : AppItem) -> Unit
+
+private data class AppLayoutFactory(private val layoutType : LayoutType) : GenericLayoutFactory {
+ override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(layoutType.layoutRes, parent, false)
+}
+
+class AppViewItem(var layoutType : LayoutType, private val item : AppItem, private val missingIcon : Bitmap, private val onClick : InteractionFunction, private val onLongClick : InteractionFunction) : GenericViewHolderBinder() {
+ override fun getLayoutFactory() : GenericLayoutFactory = AppLayoutFactory(layoutType)
+
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ holder.text_title.text = item.title
+ holder.text_subtitle.text = item.subTitle ?: item.loaderResultString(holder.text_subtitle.context)
+
+ holder.icon.setImageBitmap(item.icon ?: missingIcon)
+
+ if (layoutType == LayoutType.List) {
+ holder.icon.setOnClickListener { showIconDialog(holder.icon.context, item) }
+ }
+
+ when (layoutType) {
+ LayoutType.List -> holder.itemView
+ LayoutType.Grid, LayoutType.GridCompact -> holder.card_app_item_grid
+ }.apply {
+ setOnClickListener { onClick.invoke(item) }
+ setOnLongClickListener { true.also { onLongClick.invoke(item) } }
+ }
+ }
+
+ private fun showIconDialog(context : Context, appItem : AppItem) {
+ val builder = Dialog(context)
+ builder.requestWindowFeature(Window.FEATURE_NO_TITLE)
+ builder.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+
+ val imageView = ImageView(context)
+ imageView.setImageBitmap(appItem.icon ?: missingIcon)
+
+ builder.addContentView(imageView, RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
+ builder.show()
+ }
+
+ override fun toString() = item.key()
+}
diff --git a/app/src/main/java/emu/skyline/adapter/ControllerAdapter.kt b/app/src/main/java/emu/skyline/adapter/ControllerAdapter.kt
deleted file mode 100644
index fe23b3ca..00000000
--- a/app/src/main/java/emu/skyline/adapter/ControllerAdapter.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * SPDX-License-Identifier: MPL-2.0
- * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
- */
-
-package emu.skyline.adapter
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
-import emu.skyline.R
-import emu.skyline.data.BaseItem
-import emu.skyline.input.*
-import kotlinx.android.extensions.LayoutContainer
-import kotlinx.android.synthetic.main.controller_item.*
-import kotlinx.android.synthetic.main.section_item.text_title
-
-/**
- * This is a class that holds everything relevant to a single item in the controller configuration list
- *
- * @param content The main line of text describing what the item is
- * @param subContent The secondary line of text to show data more specific data about the item
- */
-abstract class ControllerItem(var content : String, var subContent : String) : BaseItem() {
- lateinit var adapter : ControllerAdapter
-
- var position : Int? = null
-
- /**
- * This function updates the visible contents of the item
- */
- fun update(content : String?, subContent : String?) {
- if (content != null)
- this.content = content
-
- if (subContent != null)
- this.subContent = subContent
-
- position?.let { adapter.notifyItemChanged(it) }
- }
-
- /**
- * This is used as a generic function to update the contents of the item
- */
- abstract fun update()
-}
-
-/**
- * This item is used to display the [type] of the currently active controller
- */
-class ControllerTypeItem(val context : Context, val type : ControllerType) : ControllerItem(context.getString(R.string.controller_type), context.getString(type.stringRes)) {
- /**
- * This function just updates [subContent] based on [type]
- */
- override fun update() = update(null, context.getString(type.stringRes))
-}
-
-/**
- * This item is used to display general settings items regarding controller
- *
- * @param type The type of controller setting this item is displaying
- */
-class ControllerGeneralItem(val context : ControllerActivity, val type : GeneralType) : ControllerItem(context.getString(type.stringRes), getSummary(context, type)) {
- companion object {
- /**
- * This returns the summary for [type] by using data encapsulated within [Controller]
- */
- fun getSummary(context : ControllerActivity, type : GeneralType) : String {
- val controller = InputManager.controllers[context.id]!!
-
- return when (type) {
- GeneralType.PartnerJoyCon -> {
- val partner = (controller as JoyConLeftController).partnerId
-
- if (partner != null)
- "${context.getString(R.string.controller)} #${partner + 1}"
- else
- context.getString(R.string.none)
- }
-
- GeneralType.RumbleDevice -> controller.rumbleDeviceName ?: context.getString(R.string.none)
- }
- }
- }
-
- /**
- * This function just updates [subContent] based on [getSummary]
- */
- override fun update() = update(null, getSummary(context, type))
-}
-
-/**
- * This item is used to display a particular [button] mapping for the controller
- */
-class ControllerButtonItem(val context : ControllerActivity, val button : ButtonId) : ControllerItem(button.long?.let { context.getString(it) } ?: button.toString(), getSummary(context, button)) {
- companion object {
- /**
- * This returns the summary for [button] by doing a reverse-lookup in [InputManager.eventMap]
- */
- fun getSummary(context : ControllerActivity, button : ButtonId) : String {
- val guestEvent = ButtonGuestEvent(context.id, button)
- return InputManager.eventMap.filter { it.value is ButtonGuestEvent && it.value == guestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
- }
- }
-
- /**
- * This function just updates [subContent] based on [getSummary]
- */
- override fun update() = update(null, getSummary(context, button))
-}
-
-/**
- * This item is used to display all information regarding a [stick] and it's mappings for the controller
- */
-class ControllerStickItem(val context : ControllerActivity, val stick : StickId) : ControllerItem(stick.toString(), getSummary(context, stick)) {
- companion object {
- /**
- * This returns the summary for [stick] by doing reverse-lookups in [InputManager.eventMap]
- */
- fun getSummary(context : ControllerActivity, stick : StickId) : String {
- val buttonGuestEvent = ButtonGuestEvent(context.id, stick.button)
- val button = InputManager.eventMap.filter { it.value is ButtonGuestEvent && it.value == buttonGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
-
- var axisGuestEvent = AxisGuestEvent(context.id, stick.yAxis, true)
- val yAxisPlus = InputManager.eventMap.filter { it.value is AxisGuestEvent && it.value == axisGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
-
- axisGuestEvent = AxisGuestEvent(context.id, stick.yAxis, false)
- val yAxisMinus = InputManager.eventMap.filter { it.value is AxisGuestEvent && it.value == axisGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
-
- axisGuestEvent = AxisGuestEvent(context.id, stick.xAxis, true)
- val xAxisPlus = InputManager.eventMap.filter { it.value is AxisGuestEvent && it.value == axisGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
-
- axisGuestEvent = AxisGuestEvent(context.id, stick.xAxis, false)
- val xAxisMinus = InputManager.eventMap.filter { it.value is AxisGuestEvent && it.value == axisGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
-
- return "${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"
- }
- }
-
- /**
- * This function just updates [subContent] based on [getSummary]
- */
- override fun update() = update(null, getSummary(context, stick))
-}
-
-class ControllerCheckBox()
-
-/**
- * This adapter is used to create a list which handles having a simple view
- */
-class ControllerAdapter(private val onItemClickCallback : (item : ControllerItem) -> Unit) : HeaderAdapter() {
- fun addHeader(string : String) {
- super.addHeader(BaseHeader(string))
- }
-
- fun addItem(item : ControllerItem) {
- item.adapter = this
- super.addItem(item)
- }
-
- private class ItemViewHolder(override val containerView : View) : RecyclerView.ViewHolder(containerView), LayoutContainer
-
- private class HeaderViewHolder(override val containerView : View) : RecyclerView.ViewHolder(containerView), LayoutContainer
-
- override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) : RecyclerView.ViewHolder = LayoutInflater.from(parent.context).let { layoutInflater ->
- when (ElementType.values()[viewType]) {
- ElementType.Header -> HeaderViewHolder(layoutInflater.inflate(R.layout.section_item, parent, false))
-
- ElementType.Item -> ItemViewHolder(layoutInflater.inflate(R.layout.controller_item, parent, false))
- }
- }
-
- override fun onBindViewHolder(holder : RecyclerView.ViewHolder, position : Int) {
- val item = getItem(position)
-
- if (item is ControllerItem && holder is ItemViewHolder) {
- item.position = position
-
- holder.text_title.text = item.content
- holder.text_subtitle.text = item.subContent
-
- holder.itemView.setOnClickListener { onItemClickCallback.invoke(item) }
- } else if (item is BaseHeader && holder is HeaderViewHolder) {
- holder.text_title.text = item.title
- }
- }
-}
diff --git a/app/src/main/java/emu/skyline/adapter/GenericAdapter.kt b/app/src/main/java/emu/skyline/adapter/GenericAdapter.kt
new file mode 100644
index 00000000..9299cf3e
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/GenericAdapter.kt
@@ -0,0 +1,116 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter
+
+import android.view.ViewGroup
+import android.widget.Filter
+import android.widget.Filterable
+import androidx.recyclerview.widget.RecyclerView
+import info.debatty.java.stringsimilarity.Cosine
+import info.debatty.java.stringsimilarity.JaroWinkler
+import java.util.*
+
+class GenericAdapter : RecyclerView.Adapter(), Filterable {
+ var currentSearchTerm = ""
+
+ val currentItems get() = if (currentSearchTerm.isEmpty()) allItems else filteredItems
+ val allItems = mutableListOf()
+ private var filteredItems = listOf()
+
+ private val viewTypesMapping = mutableMapOf()
+
+ override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) = GenericViewHolder(viewTypesMapping.filterValues { it == viewType }.keys.single().createLayout(parent))
+
+ override fun onBindViewHolder(holder : GenericViewHolder, position : Int) {
+ currentItems[position].apply {
+ adapter = this@GenericAdapter
+ bind(holder, position)
+ }
+ }
+
+ override fun getItemCount() = currentItems.size
+
+ override fun getItemViewType(position : Int) = viewTypesMapping.getOrPut(currentItems[position].getLayoutFactory(), { viewTypesMapping.size })
+
+ fun addItem(item : GenericViewHolderBinder) {
+ allItems.add(item)
+ notifyItemInserted(currentItems.size)
+ }
+
+ fun removeAllItems() {
+ val size = currentItems.size
+ allItems.clear()
+ notifyItemRangeRemoved(0, size)
+ }
+
+ fun notifyAllItemsChanged() {
+ notifyItemRangeChanged(0, currentItems.size)
+ }
+
+ /**
+ * This returns an instance of the filter object which is used to search for items in the view
+ */
+ override fun getFilter() = object : Filter() {
+ /**
+ * We use Jaro-Winkler distance for string similarity (https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance)
+ */
+ private val jw = JaroWinkler()
+
+ /**
+ * We use Cosine similarity for string similarity (https://en.wikipedia.org/wiki/Cosine_similarity)
+ */
+ private val cos = Cosine()
+
+ inner class ScoredItem(val score : Double, val item : GenericViewHolderBinder)
+
+ /**
+ * This sorts the items in [allItems] in relation to how similar they are to [currentSearchTerm]
+ */
+ fun extractSorted() = allItems.mapNotNull { item ->
+ item.toString().toLowerCase(Locale.getDefault()).let {
+ val similarity = (jw.similarity(currentSearchTerm, it)) + cos.similarity(currentSearchTerm, it) / 2
+ if (similarity != 0.0) ScoredItem(similarity, item) else null
+ }
+ }.apply {
+ sortedByDescending { it.score }
+ }
+
+ /**
+ * This performs filtering on the items in [allItems] based on similarity to [term]
+ */
+ override fun performFiltering(term : CharSequence) : FilterResults {
+ val results = FilterResults()
+ currentSearchTerm = (term as String).toLowerCase(Locale.getDefault())
+
+ if (term.isEmpty()) {
+ results.values = allItems.toMutableList()
+ results.count = allItems.size
+ } else {
+ val filterData = mutableListOf()
+
+ val topResults = extractSorted()
+ val avgScore = topResults.sumByDouble { it.score } / topResults.size
+
+ for (result in topResults)
+ if (result.score > avgScore) filterData.add(result.item)
+
+ results.values = filterData
+ results.count = filterData.size
+ }
+ return results
+ }
+
+ /**
+ * This publishes the results that were calculated in [performFiltering] to the view
+ */
+ override fun publishResults(charSequence : CharSequence, results : FilterResults) {
+ @Suppress("UNCHECKED_CAST")
+ filteredItems = results.values as List
+
+ notifyDataSetChanged()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/emu/skyline/adapter/GenericViewHolderBinder.kt b/app/src/main/java/emu/skyline/adapter/GenericViewHolderBinder.kt
new file mode 100644
index 00000000..3bf2a2f0
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/GenericViewHolderBinder.kt
@@ -0,0 +1,25 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.android.extensions.LayoutContainer
+
+class GenericViewHolder(override val containerView : View) : RecyclerView.ViewHolder(containerView), LayoutContainer
+
+interface GenericLayoutFactory {
+ fun createLayout(parent : ViewGroup) : View
+}
+
+abstract class GenericViewHolderBinder {
+ var adapter : GenericAdapter? = null
+
+ abstract fun getLayoutFactory() : GenericLayoutFactory
+
+ abstract fun bind(holder : GenericViewHolder, position : Int)
+}
diff --git a/app/src/main/java/emu/skyline/adapter/HeaderAdapter.kt b/app/src/main/java/emu/skyline/adapter/HeaderAdapter.kt
deleted file mode 100644
index 5698fd21..00000000
--- a/app/src/main/java/emu/skyline/adapter/HeaderAdapter.kt
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * SPDX-License-Identifier: MPL-2.0
- * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
- */
-
-package emu.skyline.adapter
-
-import android.util.SparseIntArray
-import android.widget.Filter
-import android.widget.Filterable
-import androidx.recyclerview.widget.GridLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import emu.skyline.data.BaseItem
-import info.debatty.java.stringsimilarity.Cosine
-import info.debatty.java.stringsimilarity.JaroWinkler
-import java.io.*
-import java.util.*
-import kotlin.collections.ArrayList
-
-/**
- * An enumeration of the type of elements in this adapter
- */
-enum class ElementType {
- Header,
- Item,
-}
-
-/**
- * This is an abstract class that all adapter element classes inherit from
- */
-abstract class BaseElement constructor(val elementType : ElementType) : Serializable
-
-/**
- * This is an abstract class that all adapter header classes inherit from
- */
-class BaseHeader constructor(val title : String) : BaseElement(ElementType.Header)
-
-/**
- * This adapter has the ability to have 2 types of elements specifically headers and items
- */
-abstract class HeaderAdapter : RecyclerView.Adapter(), Filterable, Serializable {
- /**
- * This holds all the elements in an array even if they may not be visible
- */
- var elementArray : ArrayList = ArrayList()
-
- /**
- * This holds the indices of all the visible items in [elementArray]
- */
- var visibleArray : ArrayList = ArrayList()
-
- /**
- * This holds the search term if there is any, to filter any items added during a search
- */
- private var searchTerm = ""
-
- /**
- * This functions adds [item] to [elementArray] and [visibleArray] based on the filter
- */
- fun addItem(item : ItemType) {
- elementArray.add(item)
- if (searchTerm.isNotEmpty()) {
- filter.filter(searchTerm)
- } else {
- visibleArray.add(elementArray.size - 1)
- notifyItemInserted(visibleArray.size)
- }
- }
-
- /**
- * This function adds [header] to [elementArray] and [visibleArray] based on if the filter is active
- */
- fun addHeader(header : HeaderType) {
- elementArray.add(header)
- if (searchTerm.isEmpty()) {
- visibleArray.add(elementArray.size - 1)
- notifyItemInserted(visibleArray.size)
- }
- }
-
- /**
- * This serializes [elementArray] into [file]
- */
- @Throws(IOException::class)
- fun save(file : File) {
- val fileObj = FileOutputStream(file)
- val out = ObjectOutputStream(fileObj)
- out.writeObject(elementArray)
- out.close()
- fileObj.close()
- }
-
- /**
- * This reads in [elementArray] from [file]
- */
- @Throws(IOException::class, ClassNotFoundException::class)
- open fun load(file : File) {
- val fileObj = FileInputStream(file)
- val input = ObjectInputStream(fileObj)
- @Suppress("UNCHECKED_CAST")
- elementArray = input.readObject() as ArrayList
- input.close()
- fileObj.close()
- filter.filter(searchTerm)
- }
-
- /**
- * This clears the view by clearing [elementArray] and [visibleArray]
- */
- fun clear() {
- elementArray.clear()
- visibleArray.clear()
- notifyDataSetChanged()
- }
-
- /**
- * This returns the amount of elements that should be drawn to the list
- */
- override fun getItemCount() : Int = visibleArray.size
-
- /**
- * This returns a particular element at [position]
- */
- fun getItem(position : Int) : BaseElement? {
- return elementArray[visibleArray[position]]
- }
-
- /**
- * This returns the type of an element at the specified position
- *
- * @param position The position of the element
- */
- override fun getItemViewType(position : Int) : Int {
- return elementArray[visibleArray[position]]!!.elementType.ordinal
- }
-
- /**
- * This returns an instance of the filter object which is used to search for items in the view
- */
- override fun getFilter() = object : Filter() {
- /**
- * We use Jaro-Winkler distance for string similarity (https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance)
- */
- private val jw = JaroWinkler()
-
- /**
- * We use Cosine similarity for string similarity (https://en.wikipedia.org/wiki/Cosine_similarity)
- */
- private val cos = Cosine()
-
- /**
- * This class is used to store the results of the item sorting
- *
- * @param score The score of this result
- * @param index The index of this item
- */
- inner class ScoredItem(val score : Double, val index : Int) {}
-
- /**
- * This sorts the items in [keyArray] in relation to how similar they are to [term]
- */
- fun extractSorted(term : String, keyArray : ArrayList) : Array {
- val scoredItems : MutableList = ArrayList()
-
- keyArray.forEachIndexed { index, item ->
- val similarity = (jw.similarity(term, item) + cos.similarity(term, item)) / 2
-
- if (similarity != 0.0)
- scoredItems.add(ScoredItem(similarity, index))
- }
-
- scoredItems.sortWith(compareByDescending { it.score })
-
- return scoredItems.toTypedArray()
- }
-
- /**
- * This performs filtering on the items in [elementArray] based on similarity to [term]
- */
- override fun performFiltering(term : CharSequence) : FilterResults {
- val results = FilterResults()
- searchTerm = (term as String).toLowerCase(Locale.getDefault())
-
- if (term.isEmpty()) {
- results.values = elementArray.indices.toMutableList()
- results.count = elementArray.size
- } else {
- val filterData = ArrayList()
-
- val keyArray = ArrayList()
- val keyIndex = SparseIntArray()
-
- for (index in elementArray.indices) {
- val item = elementArray[index]!!
-
- if (item is BaseItem && item.key() != null) {
- keyIndex.append(keyArray.size, index)
- keyArray.add(item.key()!!.toLowerCase(Locale.getDefault()))
- }
- }
-
- val topResults = extractSorted(searchTerm, keyArray)
- val avgScore = topResults.sumByDouble { it.score } / topResults.size
-
- for (result in topResults)
- if (result.score > avgScore)
- filterData.add(keyIndex[result.index])
-
- results.values = filterData
- results.count = filterData.size
- }
-
- return results
- }
-
- /**
- * This publishes the results that were calculated in [performFiltering] to the view
- */
- override fun publishResults(charSequence : CharSequence, results : FilterResults) {
- if (results.values is ArrayList<*>) {
- @Suppress("UNCHECKED_CAST")
- visibleArray = results.values as ArrayList
-
- notifyDataSetChanged()
- }
- }
- }
-}
-
-/**
- * This class is used to lookup the span based on the type of the element
- *
- * @param adapter The adapter which is used to deduce the type of the item based on the position
- * @param headerSpan The span size to return for headers
- */
-class GridLayoutSpan(val adapter : HeaderAdapter, private val headerSpan : Int) : GridLayoutManager.SpanSizeLookup() {
- /**
- * This returns the size of the span based on the type of the element at [position]
- */
- override fun getSpanSize(position : Int) : Int {
- val item = adapter.getItem(position)!!
- return if (item.elementType == ElementType.Item)
- 1
- else
- headerSpan
- }
-}
diff --git a/app/src/main/java/emu/skyline/adapter/HeaderViewItem.kt b/app/src/main/java/emu/skyline/adapter/HeaderViewItem.kt
new file mode 100644
index 00000000..176645fb
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/HeaderViewItem.kt
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import emu.skyline.R
+import kotlinx.android.synthetic.main.section_item.*
+
+private object HeaderLayoutFactory : GenericLayoutFactory {
+ override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(R.layout.section_item, parent, false)
+}
+
+class HeaderViewItem(private val text : String) : GenericViewHolderBinder() {
+ override fun getLayoutFactory() : GenericLayoutFactory = HeaderLayoutFactory
+
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ holder.text_title.text = text
+ }
+
+ override fun toString() = ""
+}
diff --git a/app/src/main/java/emu/skyline/adapter/LogAdapter.kt b/app/src/main/java/emu/skyline/adapter/LogAdapter.kt
deleted file mode 100644
index e845b5cf..00000000
--- a/app/src/main/java/emu/skyline/adapter/LogAdapter.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * SPDX-License-Identifier: MPL-2.0
- * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
- */
-
-package emu.skyline.adapter
-
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import androidx.recyclerview.widget.RecyclerView
-import emu.skyline.R
-import emu.skyline.data.BaseItem
-import kotlinx.android.extensions.LayoutContainer
-import kotlinx.android.synthetic.main.log_item.*
-
-/**
- * This class is used to hold all data about a log entry
- */
-internal class LogItem(val message : String, val level : String) : BaseItem() {
- /**
- * The log message itself is used as the search key
- */
- override fun key() : String? {
- return message
- }
-}
-
-/**
- * This adapter is used for displaying logs outputted by the application
- */
-internal class LogAdapter internal constructor(val context : Context, val compact : Boolean, private val debug_level : Int, private val level_str : Array) : HeaderAdapter() {
- private val clipboard : ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
-
- /**
- * This function adds a line to this log adapter
- */
- fun add(logLine : String) {
- try {
- val logMeta = logLine.split("|", limit = 3)
-
- if (logMeta[0].startsWith("1")) {
- val level = logMeta[1].toInt()
- if (level > debug_level) return
-
- addItem(LogItem(logMeta[2].replace('\\', '\n'), level_str[level]))
- } else {
- addHeader(BaseHeader(logMeta[1]))
- }
- } catch (ignored : IndexOutOfBoundsException) {
- } catch (ignored : NumberFormatException) {
- }
- }
-
- private class ItemViewHolder(override val containerView : View) : RecyclerView.ViewHolder(containerView), LayoutContainer
-
- private class HeaderViewHolder(override val containerView : View) : RecyclerView.ViewHolder(containerView), LayoutContainer
-
- /**
- * This function creates the view-holder of type [viewType] with the layout parent as [parent]
- */
- override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) : RecyclerView.ViewHolder {
- val inflater = LayoutInflater.from(context)
-
- val view = when (ElementType.values()[viewType]) {
- ElementType.Item -> inflater.inflate(if (compact) R.layout.log_item_compact else R.layout.log_item, parent, false)
-
- ElementType.Header -> inflater.inflate(R.layout.log_item, parent, false)
- }
-
- return when (ElementType.values()[viewType]) {
- ElementType.Item -> {
- if (compact) {
- ItemViewHolder(view)
- } else {
- ItemViewHolder(view)
- }
- }
-
- ElementType.Header -> {
- HeaderViewHolder(view)
- }
- }
- }
-
- /**
- * This function binds the item at [position] to the supplied [holder]
- */
- override fun onBindViewHolder(holder : RecyclerView.ViewHolder, position : Int) {
- val item = getItem(position)
-
- if (item is LogItem && holder is ItemViewHolder) {
- holder.text_title.text = item.message
- holder.text_subtitle?.text = item.level
-
- holder.itemView.setOnClickListener {
- clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.message + " (" + item.level + ")"))
- Toast.makeText(holder.itemView.context, "Copied to clipboard", Toast.LENGTH_LONG).show()
- }
- } else if (item is BaseHeader && holder is HeaderViewHolder) {
- holder.text_title.text = item.title
- }
- }
-}
diff --git a/app/src/main/java/emu/skyline/adapter/LogViewItem.kt b/app/src/main/java/emu/skyline/adapter/LogViewItem.kt
new file mode 100644
index 00000000..c5debb76
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/LogViewItem.kt
@@ -0,0 +1,33 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import emu.skyline.R
+import kotlinx.android.synthetic.main.log_item.*
+
+private data class LogLayoutFactory(private val compact : Boolean) : GenericLayoutFactory {
+ override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(if (compact) R.layout.log_item_compact else R.layout.log_item, parent, false)
+}
+
+class LogViewItem(private val compact : Boolean, private val message : String, private val level : String) : GenericViewHolderBinder() {
+ override fun getLayoutFactory() : GenericLayoutFactory = LogLayoutFactory(compact)
+
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ holder.text_title.text = message
+ holder.text_subtitle?.text = level
+
+ holder.itemView.setOnClickListener {
+ it.context.getSystemService(ClipboardManager::class.java).setPrimaryClip(ClipData.newPlainText("Log Message", "$message ($level)"))
+ Toast.makeText(it.context, "Copied to clipboard", Toast.LENGTH_LONG).show()
+ }
+ }
+}
diff --git a/app/src/main/java/emu/skyline/adapter/controller/ControllerButtonViewItem.kt b/app/src/main/java/emu/skyline/adapter/controller/ControllerButtonViewItem.kt
new file mode 100644
index 00000000..bbebac8f
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/controller/ControllerButtonViewItem.kt
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter.controller
+
+import emu.skyline.adapter.GenericViewHolder
+import emu.skyline.input.ButtonGuestEvent
+import emu.skyline.input.ButtonId
+import emu.skyline.input.InputManager
+
+/**
+ * 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() {
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ content = button.long?.let { holder.itemView.context.getString(it) } ?: button.toString()
+ val guestEvent = ButtonGuestEvent(controllerId, button)
+ subContent = InputManager.eventMap.filter { it.value is ButtonGuestEvent && it.value == guestEvent }.keys.firstOrNull()?.toString() ?: ""
+
+ super.bind(holder, position)
+
+ holder.itemView.setOnClickListener { onClick.invoke(this, position) }
+ }
+}
diff --git a/app/src/main/java/emu/skyline/adapter/controller/ControllerCheckBoxViewItem.kt b/app/src/main/java/emu/skyline/adapter/controller/ControllerCheckBoxViewItem.kt
new file mode 100644
index 00000000..3569e97d
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/controller/ControllerCheckBoxViewItem.kt
@@ -0,0 +1,33 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter.controller
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import emu.skyline.R
+import emu.skyline.adapter.GenericLayoutFactory
+import emu.skyline.adapter.GenericViewHolder
+import emu.skyline.adapter.GenericViewHolderBinder
+import kotlinx.android.synthetic.main.controller_checkbox_item.*
+
+private object ControllerCheckBoxLayoutFactory : GenericLayoutFactory {
+ override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(R.layout.controller_checkbox_item, parent, false)
+}
+
+class ControllerCheckBoxViewItem(var title : String, var summary : String, var checked : Boolean, private val onCheckedChange : (item : ControllerCheckBoxViewItem, position : Int) -> Unit) : GenericViewHolderBinder() {
+ override fun getLayoutFactory() : GenericLayoutFactory = ControllerCheckBoxLayoutFactory
+
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ holder.text_title.text = title
+ holder.text_subtitle.text = summary
+ holder.checkbox.isChecked = checked
+ holder.itemView.setOnClickListener {
+ checked = !checked
+ onCheckedChange.invoke(this, position)
+ }
+ }
+}
diff --git a/app/src/main/java/emu/skyline/adapter/controller/ControllerGeneralViewItem.kt b/app/src/main/java/emu/skyline/adapter/controller/ControllerGeneralViewItem.kt
new file mode 100644
index 00000000..bb08a074
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/controller/ControllerGeneralViewItem.kt
@@ -0,0 +1,41 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter.controller
+
+import emu.skyline.R
+import emu.skyline.adapter.GenericViewHolder
+import emu.skyline.input.GeneralType
+import emu.skyline.input.InputManager
+import emu.skyline.input.JoyConLeftController
+
+/**
+ * This item is used to display general settings items regarding controller
+ *
+ * @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() {
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ val context = holder.itemView.context
+ val controller = InputManager.controllers[controllerId]!!
+
+ content = context.getString(type.stringRes)
+ subContent = when (type) {
+ GeneralType.PartnerJoyCon -> {
+ val partner = (controller as JoyConLeftController).partnerId
+
+ if (partner != null)
+ "${context.getString(R.string.controller)} #${partner + 1}"
+ else
+ context.getString(R.string.none)
+ }
+
+ GeneralType.RumbleDevice -> controller.rumbleDeviceName ?: context.getString(R.string.none)
+ }
+ super.bind(holder, position)
+
+ holder.itemView.setOnClickListener { onClick.invoke(this, position) }
+ }
+}
diff --git a/app/src/main/java/emu/skyline/adapter/controller/ControllerStickViewItem.kt b/app/src/main/java/emu/skyline/adapter/controller/ControllerStickViewItem.kt
new file mode 100644
index 00000000..e7f02e1d
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/controller/ControllerStickViewItem.kt
@@ -0,0 +1,43 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter.controller
+
+import emu.skyline.R
+import emu.skyline.adapter.GenericViewHolder
+import emu.skyline.input.AxisGuestEvent
+import emu.skyline.input.ButtonGuestEvent
+import emu.skyline.input.InputManager
+import emu.skyline.input.StickId
+
+/**
+ * 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()) {
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ val context = holder.itemView.context
+
+ val buttonGuestEvent = ButtonGuestEvent(controllerId, stick.button)
+ val button = InputManager.eventMap.filter { it.value is ButtonGuestEvent && it.value == buttonGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
+
+ var axisGuestEvent = AxisGuestEvent(controllerId, stick.yAxis, true)
+ val yAxisPlus = InputManager.eventMap.filter { it.value is AxisGuestEvent && it.value == axisGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
+
+ axisGuestEvent = AxisGuestEvent(controllerId, stick.yAxis, false)
+ val yAxisMinus = InputManager.eventMap.filter { it.value is AxisGuestEvent && it.value == axisGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
+
+ axisGuestEvent = AxisGuestEvent(controllerId, stick.xAxis, true)
+ val xAxisPlus = InputManager.eventMap.filter { it.value is AxisGuestEvent && it.value == axisGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
+
+ axisGuestEvent = AxisGuestEvent(controllerId, stick.xAxis, false)
+ val xAxisMinus = InputManager.eventMap.filter { it.value is AxisGuestEvent && it.value == axisGuestEvent }.keys.firstOrNull()?.toString() ?: context.getString(R.string.none)
+
+ 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)
+
+ holder.itemView.setOnClickListener { onClick.invoke(this, position) }
+ }
+}
diff --git a/app/src/main/java/emu/skyline/adapter/controller/ControllerTypeViewItem.kt b/app/src/main/java/emu/skyline/adapter/controller/ControllerTypeViewItem.kt
new file mode 100644
index 00000000..fde16fbf
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/controller/ControllerTypeViewItem.kt
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter.controller
+
+import emu.skyline.R
+import emu.skyline.adapter.GenericViewHolder
+import emu.skyline.input.ControllerType
+
+/**
+ * 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() {
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ val context = holder.itemView.context
+
+ content = context.getString(R.string.controller_type)
+ subContent = context.getString(type.stringRes)
+
+ super.bind(holder, position)
+
+ holder.itemView.setOnClickListener { onClick.invoke(this, position) }
+ }
+}
diff --git a/app/src/main/java/emu/skyline/adapter/controller/ControllerViewItem.kt b/app/src/main/java/emu/skyline/adapter/controller/ControllerViewItem.kt
new file mode 100644
index 00000000..212ba79f
--- /dev/null
+++ b/app/src/main/java/emu/skyline/adapter/controller/ControllerViewItem.kt
@@ -0,0 +1,41 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.adapter.controller
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isGone
+import emu.skyline.R
+import emu.skyline.adapter.GenericLayoutFactory
+import emu.skyline.adapter.GenericViewHolder
+import emu.skyline.adapter.GenericViewHolderBinder
+import kotlinx.android.synthetic.main.controller_item.*
+
+private object ControllerLayoutFactory : GenericLayoutFactory {
+ override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(R.layout.controller_item, parent, false)
+}
+
+open class ControllerViewItem(var content : String = "", var subContent : String = "", private val onClick : (() -> Unit)? = null) : GenericViewHolderBinder() {
+ private var position = -1
+
+ override fun getLayoutFactory() : GenericLayoutFactory = ControllerLayoutFactory
+
+ override fun bind(holder : GenericViewHolder, position : Int) {
+ this.position = position
+ holder.text_title.apply {
+ isGone = content.isEmpty()
+ text = content
+ }
+ holder.text_subtitle.apply {
+ isGone = subContent.isEmpty()
+ text = subContent
+ }
+ onClick?.let { onClick -> holder.itemView.setOnClickListener { onClick.invoke() } }
+ }
+
+ fun update() = adapter?.notifyItemChanged(position)
+}
diff --git a/app/src/main/java/emu/skyline/data/AppItem.kt b/app/src/main/java/emu/skyline/data/AppItem.kt
index be6eb917..eead5b05 100644
--- a/app/src/main/java/emu/skyline/data/AppItem.kt
+++ b/app/src/main/java/emu/skyline/data/AppItem.kt
@@ -6,8 +6,6 @@
package emu.skyline.data
import android.content.Context
-import android.graphics.Bitmap
-import android.net.Uri
import emu.skyline.R
import emu.skyline.loader.AppEntry
import emu.skyline.loader.LoaderResult
@@ -19,32 +17,27 @@ class AppItem(val meta : AppEntry) : BaseItem() {
/**
* The icon of the application
*/
- val icon : Bitmap?
- get() = meta.icon
+ val icon get() = meta.icon
/**
* The title of the application
*/
- val title : String
- get() = meta.name
+ val title get() = meta.name
/**
* The string used as the sub-title, we currently use the author
*/
- val subTitle : String?
- get() = meta.author
+ val subTitle get() = meta.author
/**
* The URI of the application's image file
*/
- val uri : Uri
- get() = meta.uri
+ val uri get() = meta.uri
/**
* The format of the application ROM as a string
*/
- private val type : String
- get() = meta.format.name
+ private val type get() = meta.format.name
val loaderResult get() = meta.loaderResult
@@ -63,7 +56,5 @@ class AppItem(val meta : AppEntry) : BaseItem() {
/**
* The name and author is used as the key
*/
- override fun key() : String? {
- return if (meta.author != null) meta.name + " " + meta.author else meta.name
- }
+ override fun key() = meta.name + if (meta.author != null) " ${meta.author}" else ""
}
diff --git a/app/src/main/java/emu/skyline/data/BaseElement.kt b/app/src/main/java/emu/skyline/data/BaseElement.kt
new file mode 100644
index 00000000..25f6acd9
--- /dev/null
+++ b/app/src/main/java/emu/skyline/data/BaseElement.kt
@@ -0,0 +1,15 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.data
+
+import java.io.Serializable
+
+enum class ElementType {
+ Header,
+ Item
+}
+
+abstract class BaseElement(elementType : ElementType) : Serializable
diff --git a/app/src/main/java/emu/skyline/data/BaseHeader.kt b/app/src/main/java/emu/skyline/data/BaseHeader.kt
new file mode 100644
index 00000000..9a8cf466
--- /dev/null
+++ b/app/src/main/java/emu/skyline/data/BaseHeader.kt
@@ -0,0 +1,8 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.data
+
+class BaseHeader(val title : String) : BaseElement(ElementType.Header)
diff --git a/app/src/main/java/emu/skyline/data/BaseItem.kt b/app/src/main/java/emu/skyline/data/BaseItem.kt
index 4fd82dd1..fed245df 100644
--- a/app/src/main/java/emu/skyline/data/BaseItem.kt
+++ b/app/src/main/java/emu/skyline/data/BaseItem.kt
@@ -5,9 +5,6 @@
package emu.skyline.data
-import emu.skyline.adapter.BaseElement
-import emu.skyline.adapter.ElementType
-
/**
* This is an abstract class that all adapter item classes inherit from
*/
@@ -15,5 +12,5 @@ abstract class BaseItem : BaseElement(ElementType.Item) {
/**
* This function returns a string used for searching
*/
- open fun key() : String? = null
+ open fun key() : String = ""
}
diff --git a/app/src/main/java/emu/skyline/input/ControllerActivity.kt b/app/src/main/java/emu/skyline/input/ControllerActivity.kt
index 5eb7bcf8..2b16c215 100644
--- a/app/src/main/java/emu/skyline/input/ControllerActivity.kt
+++ b/app/src/main/java/emu/skyline/input/ControllerActivity.kt
@@ -5,16 +5,20 @@
package emu.skyline.input
+import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import emu.skyline.R
-import emu.skyline.adapter.*
+import emu.skyline.adapter.GenericAdapter
+import emu.skyline.adapter.HeaderViewItem
+import emu.skyline.adapter.controller.*
import emu.skyline.input.dialog.ButtonDialog
import emu.skyline.input.dialog.RumbleDialog
import emu.skyline.input.dialog.StickDialog
+import emu.skyline.input.onscreen.OnScreenEditActivity
import kotlinx.android.synthetic.main.controller_activity.*
import kotlinx.android.synthetic.main.titlebar.*
@@ -30,43 +34,54 @@ class ControllerActivity : AppCompatActivity() {
/**
* The adapter used by [controller_list] to hold all the items
*/
- private val adapter = ControllerAdapter(::onControllerItemClick)
+ private val adapter = GenericAdapter()
/**
* This is a map between a button and it's corresponding [ControllerItem] in [adapter]
*/
- val buttonMap = mutableMapOf()
+ val buttonMap = mutableMapOf()
/**
- * This is a map between an axis and it's corresponding [ControllerStickItem] in [adapter]
+ * This is a map between an axis and it's corresponding [ControllerStickViewItem] in [adapter]
*/
- val axisMap = mutableMapOf()
+ val axisMap = mutableMapOf()
/**
* This function updates the [adapter] based on information from [InputManager]
*/
private fun update() {
- adapter.clear()
+ adapter.removeAllItems()
val controller = InputManager.controllers[id]!!
- adapter.addItem(ControllerTypeItem(this, controller.type))
+ adapter.addItem(ControllerTypeViewItem(controller.type, onControllerTypeClick))
if (controller.type == ControllerType.None)
return
+ if (id == 0 && controller.type.firstController) {
+ adapter.addItem(HeaderViewItem(getString(R.string.osc)))
+ adapter.addItem(ControllerCheckBoxViewItem(getString(R.string.osc_enable), getString(R.string.osc_not_shown), false) { item, position ->
+ item.summary = getString(if (item.checked) R.string.osc_shown else R.string.osc_not_shown)
+ adapter.notifyItemChanged(position)
+ })
+
+ adapter.addItem(ControllerViewItem(content = getString(R.string.osc_edit), onClick = {
+ startActivity(Intent(this, OnScreenEditActivity::class.java))
+ }))
+ }
var wroteTitle = false
for (item in GeneralType.values()) {
if (item.compatibleControllers == null || item.compatibleControllers.contains(controller.type)) {
if (!wroteTitle) {
- adapter.addHeader(getString(R.string.general))
+ adapter.addItem(HeaderViewItem(getString(R.string.general)))
wroteTitle = true
}
- adapter.addItem(ControllerGeneralItem(this, item))
+ adapter.addItem(ControllerGeneralViewItem(id, item, onControllerGeneralClick))
}
}
@@ -74,11 +89,11 @@ class ControllerActivity : AppCompatActivity() {
for (stick in controller.type.sticks) {
if (!wroteTitle) {
- adapter.addHeader(getString(R.string.sticks))
+ adapter.addItem(HeaderViewItem(getString(R.string.sticks)))
wroteTitle = true
}
- val stickItem = ControllerStickItem(this, stick)
+ val stickItem = ControllerStickViewItem(id, stick, onControllerStickClick)
adapter.addItem(stickItem)
buttonMap[stick.button] = stickItem
@@ -98,11 +113,11 @@ class ControllerActivity : AppCompatActivity() {
for (button in controller.type.buttons.filter { it in buttonArray.second }) {
if (!wroteTitle) {
- adapter.addHeader(getString(buttonArray.first))
+ adapter.addItem(HeaderViewItem(getString(buttonArray.first)))
wroteTitle = true
}
- val buttonItem = ControllerButtonItem(this, button)
+ val buttonItem = ControllerButtonViewItem(id, button, onControllerButtonClick)
adapter.addItem(buttonItem)
buttonMap[button] = buttonItem
@@ -113,11 +128,11 @@ class ControllerActivity : AppCompatActivity() {
for (button in controller.type.buttons.filterNot { item -> buttonArrays.any { item in it.second } }.plus(ButtonId.Menu)) {
if (!wroteTitle) {
- adapter.addHeader(getString(R.string.misc_buttons))
+ adapter.addItem(HeaderViewItem(getString(R.string.misc_buttons)))
wroteTitle = true
}
- val buttonItem = ControllerButtonItem(this, button)
+ val buttonItem = ControllerButtonViewItem(id, button, onControllerButtonClick)
adapter.addItem(buttonItem)
buttonMap[button] = buttonItem
@@ -154,83 +169,81 @@ class ControllerActivity : AppCompatActivity() {
super.onPause()
}
- private fun onControllerItemClick(item : ControllerItem) {
- when (item) {
- is ControllerTypeItem -> {
- val controller = InputManager.controllers[id]!!
+ private val onControllerTypeClick = { item : ControllerTypeViewItem, _ : Int ->
+ val controller = InputManager.controllers[id]!!
- val types = ControllerType.values().apply { if (id != 0) filter { !it.firstController } }
- val typeNames = types.map { getString(it.stringRes) }.toTypedArray()
+ val types = ControllerType.values().apply { if (id != 0) filter { !it.firstController } }
+ val typeNames = types.map { getString(it.stringRes) }.toTypedArray()
+
+ MaterialAlertDialogBuilder(this)
+ .setTitle(item.content)
+ .setSingleChoiceItems(typeNames, types.indexOf(controller.type)) { dialog, typeIndex ->
+ val selectedType = types[typeIndex]
+ if (controller.type != selectedType) {
+ if (controller is JoyConLeftController)
+ controller.partnerId?.let { (InputManager.controllers[it] as JoyConRightController).partnerId = null }
+ else if (controller is JoyConRightController)
+ controller.partnerId?.let { (InputManager.controllers[it] as JoyConLeftController).partnerId = null }
+
+ InputManager.controllers[id] = when (selectedType) {
+ ControllerType.None -> Controller(id, ControllerType.None)
+ ControllerType.HandheldProController -> HandheldController(id)
+ ControllerType.ProController -> ProController(id)
+ ControllerType.JoyConLeft -> JoyConLeftController(id)
+ ControllerType.JoyConRight -> JoyConRightController(id)
+ }
+
+ update()
+ }
+
+ dialog.dismiss()
+ }
+ .show()
+ Unit
+ }
+
+ private val onControllerGeneralClick = { item : ControllerGeneralViewItem, _ : Int ->
+ when (item.type) {
+ GeneralType.PartnerJoyCon -> {
+ val controller = InputManager.controllers[id] as JoyConLeftController
+
+ val rJoyCons = InputManager.controllers.values.filter { it.type == ControllerType.JoyConRight }
+ val rJoyConNames = (listOf(getString(R.string.none)) + rJoyCons.map { "${getString(R.string.controller)} #${it.id + 1}" }).toTypedArray()
+
+ val partnerNameIndex = controller.partnerId?.let { partnerId ->
+ rJoyCons.withIndex().single { it.value.id == partnerId }.index + 1
+ } ?: 0
MaterialAlertDialogBuilder(this)
.setTitle(item.content)
- .setSingleChoiceItems(typeNames, types.indexOf(controller.type)) { dialog, typeIndex ->
- val selectedType = types[typeIndex]
- if (controller.type != selectedType) {
- if (controller is JoyConLeftController)
- controller.partnerId?.let { (InputManager.controllers[it] as JoyConRightController).partnerId = null }
- else if (controller is JoyConRightController)
- controller.partnerId?.let { (InputManager.controllers[it] as JoyConLeftController).partnerId = null }
+ .setSingleChoiceItems(rJoyConNames, partnerNameIndex) { dialog, index ->
+ (InputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = null
- InputManager.controllers[id] = when (selectedType) {
- ControllerType.None -> Controller(id, ControllerType.None)
- ControllerType.HandheldProController -> HandheldController(id)
- ControllerType.ProController -> ProController(id)
- ControllerType.JoyConLeft -> JoyConLeftController(id)
- ControllerType.JoyConRight -> JoyConRightController(id)
- }
+ controller.partnerId = if (index == 0) null else rJoyCons[index - 1].id
- update()
- }
+ if (controller.partnerId != null)
+ (InputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = controller.id
+
+ item.update()
dialog.dismiss()
}
.show()
}
- is ControllerGeneralItem -> {
- when (item.type) {
- GeneralType.PartnerJoyCon -> {
- val controller = InputManager.controllers[id] as JoyConLeftController
-
- val rJoyCons = InputManager.controllers.values.filter { it.type == ControllerType.JoyConRight }
- val rJoyConNames = (listOf(getString(R.string.none)) + rJoyCons.map { "${getString(R.string.controller)} #${it.id + 1}" }).toTypedArray()
-
- val partnerNameIndex = controller.partnerId?.let { partnerId ->
- rJoyCons.withIndex().single { it.value.id == partnerId }.index + 1
- } ?: 0
-
- MaterialAlertDialogBuilder(this)
- .setTitle(item.content)
- .setSingleChoiceItems(rJoyConNames, partnerNameIndex) { dialog, index ->
- (InputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = null
-
- controller.partnerId = if (index == 0) null else rJoyCons[index - 1].id
-
- if (controller.partnerId != null)
- (InputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = controller.id
-
- item.update()
-
- dialog.dismiss()
- }
- .show()
- }
-
- GeneralType.RumbleDevice -> {
- RumbleDialog(item).show(supportFragmentManager, null)
- }
- }
- }
-
- is ControllerButtonItem -> {
- ButtonDialog(item).show(supportFragmentManager, null)
- }
-
- is ControllerStickItem -> {
- StickDialog(item).show(supportFragmentManager, null)
+ GeneralType.RumbleDevice -> {
+ RumbleDialog(item).show(supportFragmentManager, null)
}
}
+ Unit
+ }
+
+ private val onControllerButtonClick = { item : ControllerButtonViewItem, _ : Int ->
+ ButtonDialog(item).show(supportFragmentManager, null)
+ }
+
+ private val onControllerStickClick = { item : ControllerStickViewItem, _ : Int ->
+ StickDialog(item).show(supportFragmentManager, null)
}
/**
diff --git a/app/src/main/java/emu/skyline/input/dialog/ButtonDialog.kt b/app/src/main/java/emu/skyline/input/dialog/ButtonDialog.kt
index b0ca1130..54b0a15c 100644
--- a/app/src/main/java/emu/skyline/input/dialog/ButtonDialog.kt
+++ b/app/src/main/java/emu/skyline/input/dialog/ButtonDialog.kt
@@ -14,7 +14,7 @@ import android.view.animation.LinearInterpolator
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import emu.skyline.R
-import emu.skyline.adapter.ControllerButtonItem
+import emu.skyline.adapter.controller.ControllerButtonViewItem
import emu.skyline.input.*
import kotlinx.android.synthetic.main.button_dialog.*
import kotlin.math.abs
@@ -22,9 +22,9 @@ import kotlin.math.abs
/**
* This dialog is used to set a device to map any buttons
*
- * @param item This is used to hold the [ControllerButtonItem] between instances
+ * @param item This is used to hold the [ControllerButtonViewItem] between instances
*/
-class ButtonDialog @JvmOverloads constructor(private val item : ControllerButtonItem? = null) : BottomSheetDialogFragment() {
+class ButtonDialog @JvmOverloads constructor(private val item : ControllerButtonViewItem? = null, private val position : Int? = null) : BottomSheetDialogFragment() {
/**
* This inflates the layout of the dialog after initial view creation
*/
diff --git a/app/src/main/java/emu/skyline/input/dialog/RumbleDialog.kt b/app/src/main/java/emu/skyline/input/dialog/RumbleDialog.kt
index 410ddcc1..d9464038 100644
--- a/app/src/main/java/emu/skyline/input/dialog/RumbleDialog.kt
+++ b/app/src/main/java/emu/skyline/input/dialog/RumbleDialog.kt
@@ -15,7 +15,7 @@ import android.view.animation.LinearInterpolator
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import emu.skyline.R
-import emu.skyline.adapter.ControllerGeneralItem
+import emu.skyline.adapter.controller.ControllerGeneralViewItem
import emu.skyline.input.ControllerActivity
import emu.skyline.input.InputManager
import kotlinx.android.synthetic.main.rumble_dialog.*
@@ -23,9 +23,9 @@ import kotlinx.android.synthetic.main.rumble_dialog.*
/**
* This dialog is used to set a device to pass on any rumble/force feedback data onto
*
- * @param item This is used to hold the [ControllerGeneralItem] between instances
+ * @param item This is used to hold the [ControllerGeneralViewItem] between instances
*/
-class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralItem? = null) : BottomSheetDialogFragment() {
+class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralViewItem? = null) : BottomSheetDialogFragment() {
/**
* This inflates the layout of the dialog after initial view creation
*/
diff --git a/app/src/main/java/emu/skyline/input/dialog/StickDialog.kt b/app/src/main/java/emu/skyline/input/dialog/StickDialog.kt
index ae2f6d81..d627d183 100644
--- a/app/src/main/java/emu/skyline/input/dialog/StickDialog.kt
+++ b/app/src/main/java/emu/skyline/input/dialog/StickDialog.kt
@@ -15,7 +15,7 @@ import android.view.animation.LinearInterpolator
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import emu.skyline.R
-import emu.skyline.adapter.ControllerStickItem
+import emu.skyline.adapter.controller.ControllerStickViewItem
import emu.skyline.input.*
import emu.skyline.input.MotionHostEvent.Companion.axes
import kotlinx.android.synthetic.main.stick_dialog.*
@@ -26,9 +26,9 @@ import kotlin.math.max
/**
* This dialog is used to set a device to map any sticks
*
- * @param item This is used to hold the [ControllerStickItem] between instances
+ * @param item This is used to hold the [ControllerStickViewItem] between instances
*/
-class StickDialog @JvmOverloads constructor(val item : ControllerStickItem? = null) : BottomSheetDialogFragment() {
+class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem? = null) : BottomSheetDialogFragment() {
/**
* This enumerates all of the stages this dialog can be in
*/
diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt
new file mode 100644
index 00000000..389cf34b
--- /dev/null
+++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt
@@ -0,0 +1,12 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package emu.skyline.input.onscreen
+
+import androidx.appcompat.app.AppCompatActivity
+
+class OnScreenEditActivity : AppCompatActivity() {
+
+}
diff --git a/app/src/main/java/emu/skyline/loader/RomFile.kt b/app/src/main/java/emu/skyline/loader/RomFile.kt
index 2eda6903..ff9fc790 100644
--- a/app/src/main/java/emu/skyline/loader/RomFile.kt
+++ b/app/src/main/java/emu/skyline/loader/RomFile.kt
@@ -10,7 +10,10 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
+import android.os.Build
import android.provider.OpenableColumns
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.*
@@ -60,12 +63,40 @@ enum class LoaderResult(val value : Int) {
/**
* This class is used to hold an application's metadata in a serializable way
*/
-class AppEntry(val name : String, val author : String?, val icon : Bitmap?, val format : RomFormat, val uri : Uri, val loaderResult : LoaderResult) : Serializable {
+class AppEntry(var name : String, var author : String?, var icon : Bitmap?, var format : RomFormat, var uri : Uri, var loaderResult : LoaderResult) : Serializable {
constructor(context : Context, format : RomFormat, uri : Uri, loaderResult : LoaderResult) : this(context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex : Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
cursor.getString(nameIndex)
}!!.dropLast(format.name.length + 1), null, null, format, uri, loaderResult)
+
+ private fun writeObject(output : ObjectOutputStream) {
+ output.writeUTF(name)
+ output.writeObject(format)
+ output.writeUTF(uri.toString())
+ output.writeBoolean(author != null)
+ if (author != null)
+ output.writeUTF(author)
+ output.writeInt(loaderResult.value)
+ output.writeBoolean(icon != null)
+ icon?.let {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+ it.compress(Bitmap.CompressFormat.WEBP_LOSSY, 100, output)
+ else
+ it.compress(Bitmap.CompressFormat.WEBP, 100, output)
+ }
+ }
+
+ private fun readObject(input : ObjectInputStream) {
+ name = input.readUTF()
+ format = input.readObject() as RomFormat
+ uri = Uri.parse(input.readUTF())
+ if (input.readBoolean())
+ author = input.readUTF()
+ loaderResult = LoaderResult.get(input.readInt())
+ if (input.readBoolean())
+ icon = BitmapFactory.decodeStream(input)
+ }
}
/**
diff --git a/app/src/main/res/layout/controller_checkbox_item.xml b/app/src/main/res/layout/controller_checkbox_item.xml
new file mode 100644
index 00000000..ecff3d0c
--- /dev/null
+++ b/app/src/main/res/layout/controller_checkbox_item.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/controller_item.xml b/app/src/main/res/layout/controller_item.xml
index 1abb8dc6..47ffb3a6 100644
--- a/app/src/main/res/layout/controller_item.xml
+++ b/app/src/main/res/layout/controller_item.xml
@@ -8,7 +8,7 @@
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
- android:padding="15dp">
+ android:padding="16dp">
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d34e3526..7c775bb3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -52,9 +52,11 @@
Failed to import keys
Input
- Show On-Screen Controls
+ On-Screen Controls
+ Enable On-Screen Controls
On-Screen Controls won\'t be shown
On-Screen Controls will be shown
+ Edit On-Screen Controls layout
Controller
Configure Controller
Controller Type