Rework search

* Call search once for all formats
This commit is contained in:
Willi Ye 2021-02-07 20:42:03 +01:00 committed by ◱ Mark
parent 571e189ecd
commit 479209886b
11 changed files with 108 additions and 79 deletions

View File

@ -97,7 +97,6 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation "com.google.dagger:hilt-android:$hilt_version" implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version" kapt "com.google.dagger:hilt-android-compiler:$hilt_version"

View File

@ -39,7 +39,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
private val binding by lazy { EmuActivityBinding.inflate(layoutInflater) } private val binding by lazy { EmuActivityBinding.inflate(layoutInflater) }
/** /**
* A map of [Vibrator]s that correspond to [inputManager.controllers] * A map of [Vibrator]s that correspond to [InputManager.controllers]
*/ */
private var vibrators = HashMap<Int, Vibrator>() private var vibrators = HashMap<Int, Vibrator>()
@ -54,7 +54,8 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
*/ */
private lateinit var emulationThread : Thread private lateinit var emulationThread : Thread
private val settings by lazy { Settings(this) } @Inject
lateinit var settings : Settings
@Inject @Inject
lateinit var inputManager : InputManager lateinit var inputManager : InputManager

View File

@ -17,6 +17,7 @@ import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.adapter.GenericAdapter import emu.skyline.adapter.GenericAdapter
import emu.skyline.adapter.HeaderViewItem import emu.skyline.adapter.HeaderViewItem
import emu.skyline.adapter.LogViewItem import emu.skyline.adapter.LogViewItem
@ -27,8 +28,10 @@ import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
@AndroidEntryPoint
class LogActivity : AppCompatActivity() { class LogActivity : AppCompatActivity() {
private val binding by lazy { LogActivityBinding.inflate(layoutInflater) } private val binding by lazy { LogActivityBinding.inflate(layoutInflater) }
@ -39,6 +42,9 @@ class LogActivity : AppCompatActivity() {
private val adapter = GenericAdapter() private val adapter = GenericAdapter()
@Inject
lateinit var settings : Settings
override fun onCreate(savedInstanceState : Bundle?) { override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -47,8 +53,6 @@ class LogActivity : AppCompatActivity() {
setSupportActionBar(binding.titlebar.toolbar) setSupportActionBar(binding.titlebar.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
val settings = Settings(this)
val compact = settings.logCompact val compact = settings.logCompact
val logLevel = settings.logLevel.toInt() val logLevel = settings.logLevel.toInt()
val logLevels = resources.getStringArray(R.array.log_level) val logLevels = resources.getStringArray(R.array.log_level)

View File

@ -24,6 +24,7 @@ import androidx.recyclerview.widget.DividerItemDecoration
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.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.adapter.AppViewItem import emu.skyline.adapter.AppViewItem
import emu.skyline.adapter.GenericAdapter import emu.skyline.adapter.GenericAdapter
import emu.skyline.adapter.HeaderViewItem import emu.skyline.adapter.HeaderViewItem
@ -33,13 +34,17 @@ import emu.skyline.data.DataItem
import emu.skyline.data.HeaderItem import emu.skyline.data.HeaderItem
import emu.skyline.databinding.MainActivityBinding import emu.skyline.databinding.MainActivityBinding
import emu.skyline.loader.LoaderResult import emu.skyline.loader.LoaderResult
import emu.skyline.loader.RomFormat
import emu.skyline.utils.Settings import emu.skyline.utils.Settings
import javax.inject.Inject
import kotlin.math.ceil import kotlin.math.ceil
@AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val binding by lazy { MainActivityBinding.inflate(layoutInflater) } private val binding by lazy { MainActivityBinding.inflate(layoutInflater) }
private val settings by lazy { Settings(this) } @Inject
lateinit var settings : Settings
private val adapter = GenericAdapter() private val adapter = GenericAdapter()
@ -52,16 +57,15 @@ class MainActivity : AppCompatActivity() {
private fun AppItem.toViewItem() = AppViewItem(layoutType, this, missingIcon, ::selectStartGame, ::selectShowGameDialog) private fun AppItem.toViewItem() = AppViewItem(layoutType, this, missingIcon, ::selectStartGame, ::selectShowGameDialog)
override fun onCreate(savedInstanceState : Bundle?) { override fun onCreate(savedInstanceState : Bundle?) {
AppCompatDelegate.setDefaultNightMode( // Need to create new instance of settings, dependency injection happens
when ((settings.appTheme.toInt())) { AppCompatDelegate.setDefaultNightMode(when ((Settings(this).appTheme.toInt())) {
0 -> AppCompatDelegate.MODE_NIGHT_NO 0 -> AppCompatDelegate.MODE_NIGHT_NO
1 -> AppCompatDelegate.MODE_NIGHT_YES 1 -> AppCompatDelegate.MODE_NIGHT_YES
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM 2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
} })
)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(binding.root) setContentView(binding.root)
PreferenceManager.setDefaultValues(this, R.xml.preferences, false) PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
@ -195,7 +199,16 @@ class MainActivity : AppCompatActivity() {
} }
is MainState.Loaded -> { is MainState.Loaded -> {
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
populateAdapter(state.items)
val formatOrder = arrayOf(RomFormat.NSP, RomFormat.NRO, RomFormat.NSO, RomFormat.NCA)
val items = mutableListOf<DataItem>()
for (format in formatOrder) {
state.items[format]?.let {
items.add(HeaderItem(format.name))
it.forEach { entry -> items.add(AppItem(entry)) }
}
}
populateAdapter(items)
} }
is MainState.Error -> Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${state.ex.localizedMessage}", Snackbar.LENGTH_SHORT).show() is MainState.Error -> Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${state.ex.localizedMessage}", Snackbar.LENGTH_SHORT).show()
} }

View File

@ -3,30 +3,29 @@ package emu.skyline
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import emu.skyline.data.AppItem import dagger.hilt.android.lifecycle.HiltViewModel
import emu.skyline.data.DataItem import emu.skyline.loader.AppEntry
import emu.skyline.data.HeaderItem
import emu.skyline.loader.RomFile
import emu.skyline.loader.RomFormat import emu.skyline.loader.RomFormat
import emu.skyline.utils.loadSerializedList import emu.skyline.utils.fromFile
import emu.skyline.utils.serialize import emu.skyline.utils.toFile
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import javax.inject.Inject
sealed class MainState { sealed class MainState {
object Loading : MainState() object Loading : MainState()
class Loaded(val items : List<DataItem>) : MainState() class Loaded(val items : HashMap<RomFormat, ArrayList<AppEntry>>) : MainState()
class Error(val ex : Exception) : MainState() class Error(val ex : Exception) : MainState()
} }
class MainViewModel : ViewModel() { @HiltViewModel
class MainViewModel @Inject constructor(private val romProvider : RomProvider) : ViewModel() {
companion object { companion object {
private val TAG = MainViewModel::class.java.simpleName private val TAG = MainViewModel::class.java.simpleName
} }
@ -39,30 +38,6 @@ class MainViewModel : ViewModel() {
var searchBarAnimated = false var searchBarAnimated = false
/**
* This adds all files in [directory] with [extension] as an entry using [RomFile] to load metadata
*/
private fun addEntries(context : Context, extension : String, romFormat : RomFormat, directory : DocumentFile, romElements : ArrayList<DataItem>, found : Boolean = false) : Boolean {
var foundCurrent = found
directory.listFiles().forEach { file ->
if (file.isDirectory) {
foundCurrent = addEntries(context, extension, romFormat, file, romElements, foundCurrent)
} else {
if (extension.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
RomFile(context, romFormat, file.uri).let { romFile ->
if (!foundCurrent) romElements.add(HeaderItem(romFormat.name))
romElements.add(AppItem(romFile.appEntry))
foundCurrent = true
}
}
}
}
return foundCurrent
}
/** /**
* This refreshes the contents of the adapter by either trying to load cached adapter data or searches for them to recreate a list * This refreshes the contents of the adapter by either trying to load cached adapter data or searches for them to recreate a list
* *
@ -77,33 +52,21 @@ class MainViewModel : ViewModel() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
if (loadFromFile) { if (loadFromFile) {
try { try {
state = MainState.Loaded(loadSerializedList(romsFile)) state = MainState.Loaded(fromFile(romsFile))
return@launch return@launch
} catch (e : Exception) { } catch (e : Exception) {
Log.w(TAG, "Ran into exception while loading: ${e.message}") Log.w(TAG, "Ran into exception while loading: ${e.message}")
} }
} }
val romElements = romProvider.loadRoms(searchLocation)
try { try {
val searchDocument = DocumentFile.fromTreeUri(context, searchLocation)!! romElements.toFile(romsFile)
val romElements = ArrayList<DataItem>()
addEntries(context, "nsp", RomFormat.NSP, searchDocument, romElements)
addEntries(context, "xci", RomFormat.XCI, searchDocument, romElements)
addEntries(context, "nro", RomFormat.NRO, searchDocument, romElements)
addEntries(context, "nso", RomFormat.NSO, searchDocument, romElements)
addEntries(context, "nca", RomFormat.NCA, searchDocument, romElements)
try {
romElements.serialize(romsFile)
} catch (e : IOException) { } catch (e : IOException) {
Log.w(TAG, "Ran into exception while saving: ${e.message}") Log.w(TAG, "Ran into exception while saving: ${e.message}")
} }
state = MainState.Loaded(romElements) state = MainState.Loaded(romElements)
} catch (e : Exception) {
state = MainState.Error(e)
}
} }
} }
} }

View File

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

View File

@ -53,7 +53,8 @@ class ControllerActivity : AppCompatActivity() {
*/ */
val axisMap = mutableMapOf<AxisId, ControllerStickViewItem>() val axisMap = mutableMapOf<AxisId, ControllerStickViewItem>()
private val settings by lazy { Settings(this) } @Inject
lateinit var settings : Settings
@Inject @Inject
lateinit var inputManager : InputManager lateinit var inputManager : InputManager

View File

@ -15,16 +15,22 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.R import emu.skyline.R
import emu.skyline.databinding.OnScreenEditActivityBinding import emu.skyline.databinding.OnScreenEditActivityBinding
import emu.skyline.utils.Settings import emu.skyline.utils.Settings
import javax.inject.Inject
@AndroidEntryPoint
class OnScreenEditActivity : AppCompatActivity() { class OnScreenEditActivity : AppCompatActivity() {
private val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) } private val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) }
private var fullEditVisible = true private var fullEditVisible = true
private var editMode = false private var editMode = false
@Inject
lateinit var settings : Settings
private val closeAction : () -> Unit = { private val closeAction : () -> Unit = {
if (editMode) { if (editMode) {
toggleFabVisibility(true) toggleFabVisibility(true)
@ -85,7 +91,7 @@ class OnScreenEditActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState : Bundle?) { override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(binding.root) setContentView(binding.root)
binding.onScreenControllerView.recenterSticks = Settings(this).onScreenControlRecenterSticks binding.onScreenControllerView.recenterSticks = settings.onScreenControlRecenterSticks
actions.forEach { pair -> actions.forEach { pair ->
binding.fabParent.addView(LayoutInflater.from(this).inflate(R.layout.on_screen_edit_mini_fab, binding.fabParent, false).apply { binding.fabParent.addView(LayoutInflater.from(this).inflate(R.layout.on_screen_edit_mini_fab, binding.fabParent, false).apply {

View File

@ -10,11 +10,14 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.utils.Settings import emu.skyline.utils.Settings
import javax.inject.Inject
/** /**
* This activity is used to launch a document picker and saves the result to preferences * This activity is used to launch a document picker and saves the result to preferences
*/ */
@AndroidEntryPoint
abstract class DocumentActivity : AppCompatActivity() { abstract class DocumentActivity : AppCompatActivity() {
companion object { companion object {
const val KEY_NAME = "key_name" const val KEY_NAME = "key_name"
@ -24,6 +27,9 @@ abstract class DocumentActivity : AppCompatActivity() {
protected abstract val actionIntent : Intent protected abstract val actionIntent : Intent
@Inject
lateinit var settings : Settings
/** /**
* This launches the [Intent.ACTION_OPEN_DOCUMENT_TREE] intent on creation * This launches the [Intent.ACTION_OPEN_DOCUMENT_TREE] intent on creation
*/ */
@ -46,7 +52,7 @@ abstract class DocumentActivity : AppCompatActivity() {
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
Settings(this).refreshRequired = true settings.refreshRequired = true
PreferenceManager.getDefaultSharedPreferences(this).edit() PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(keyName, uri.toString()) .putString(keyName, uri.toString())
.apply() .apply()

View File

@ -10,13 +10,9 @@ import java.io.ObjectInputStream
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
import java.io.Serializable import java.io.Serializable
fun <T : Serializable> ArrayList<T>.serialize(file : File) { fun <T : Serializable> T.toFile(file : File) {
ObjectOutputStream(file.outputStream()).use { ObjectOutputStream(file.outputStream()).use { it.writeObject(this) }
it.writeObject(this)
}
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : Serializable> loadSerializedList(file : File) = ObjectInputStream(file.inputStream()).use { fun <T : Serializable> fromFile(file : File) = ObjectInputStream(file.inputStream()).use { it.readObject() } as T
it.readObject()
} as ArrayList<T>

View File

@ -6,8 +6,12 @@
package emu.skyline.utils package emu.skyline.utils
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
class Settings(context : Context) { @Singleton
class Settings @Inject constructor(@ApplicationContext private val context : Context) {
var layoutType by sharedPreferences(context, "1") var layoutType by sharedPreferences(context, "1")
var searchLocation by sharedPreferences(context, "") var searchLocation by sharedPreferences(context, "")