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.lifecycle:lifecycle-viewmodel-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 "com.google.dagger:hilt-android:$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) }
/**
* 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>()
@ -54,7 +54,8 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
*/
private lateinit var emulationThread : Thread
private val settings by lazy { Settings(this) }
@Inject
lateinit var settings : Settings
@Inject
lateinit var inputManager : InputManager

View File

@ -17,6 +17,7 @@ import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.adapter.GenericAdapter
import emu.skyline.adapter.HeaderViewItem
import emu.skyline.adapter.LogViewItem
@ -27,8 +28,10 @@ import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.net.URL
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@AndroidEntryPoint
class LogActivity : AppCompatActivity() {
private val binding by lazy { LogActivityBinding.inflate(layoutInflater) }
@ -39,6 +42,9 @@ class LogActivity : AppCompatActivity() {
private val adapter = GenericAdapter()
@Inject
lateinit var settings : Settings
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
@ -47,8 +53,6 @@ class LogActivity : AppCompatActivity() {
setSupportActionBar(binding.titlebar.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val settings = Settings(this)
val compact = settings.logCompact
val logLevel = settings.logLevel.toInt()
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.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.adapter.AppViewItem
import emu.skyline.adapter.GenericAdapter
import emu.skyline.adapter.HeaderViewItem
@ -33,13 +34,17 @@ import emu.skyline.data.DataItem
import emu.skyline.data.HeaderItem
import emu.skyline.databinding.MainActivityBinding
import emu.skyline.loader.LoaderResult
import emu.skyline.loader.RomFormat
import emu.skyline.utils.Settings
import javax.inject.Inject
import kotlin.math.ceil
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val binding by lazy { MainActivityBinding.inflate(layoutInflater) }
private val settings by lazy { Settings(this) }
@Inject
lateinit var settings : Settings
private val adapter = GenericAdapter()
@ -52,16 +57,15 @@ class MainActivity : AppCompatActivity() {
private fun AppItem.toViewItem() = AppViewItem(layoutType, this, missingIcon, ::selectStartGame, ::selectShowGameDialog)
override fun onCreate(savedInstanceState : Bundle?) {
AppCompatDelegate.setDefaultNightMode(
when ((settings.appTheme.toInt())) {
0 -> AppCompatDelegate.MODE_NIGHT_NO
1 -> AppCompatDelegate.MODE_NIGHT_YES
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
}
)
// Need to create new instance of settings, dependency injection happens
AppCompatDelegate.setDefaultNightMode(when ((Settings(this).appTheme.toInt())) {
0 -> AppCompatDelegate.MODE_NIGHT_NO
1 -> AppCompatDelegate.MODE_NIGHT_YES
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
})
super.onCreate(savedInstanceState)
setContentView(binding.root)
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
@ -195,7 +199,16 @@ class MainActivity : AppCompatActivity() {
}
is MainState.Loaded -> {
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()
}

View File

@ -3,30 +3,29 @@ package emu.skyline
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import emu.skyline.data.AppItem
import emu.skyline.data.DataItem
import emu.skyline.data.HeaderItem
import emu.skyline.loader.RomFile
import dagger.hilt.android.lifecycle.HiltViewModel
import emu.skyline.loader.AppEntry
import emu.skyline.loader.RomFormat
import emu.skyline.utils.loadSerializedList
import emu.skyline.utils.serialize
import emu.skyline.utils.fromFile
import emu.skyline.utils.toFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.io.IOException
import javax.inject.Inject
sealed class 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 MainViewModel : ViewModel() {
@HiltViewModel
class MainViewModel @Inject constructor(private val romProvider : RomProvider) : ViewModel() {
companion object {
private val TAG = MainViewModel::class.java.simpleName
}
@ -39,30 +38,6 @@ class MainViewModel : ViewModel() {
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
*
@ -77,33 +52,21 @@ class MainViewModel : ViewModel() {
viewModelScope.launch(Dispatchers.IO) {
if (loadFromFile) {
try {
state = MainState.Loaded(loadSerializedList(romsFile))
state = MainState.Loaded(fromFile(romsFile))
return@launch
} catch (e : Exception) {
Log.w(TAG, "Ran into exception while loading: ${e.message}")
}
}
val romElements = romProvider.loadRoms(searchLocation)
try {
val searchDocument = DocumentFile.fromTreeUri(context, searchLocation)!!
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) {
Log.w(TAG, "Ran into exception while saving: ${e.message}")
}
state = MainState.Loaded(romElements)
} catch (e : Exception) {
state = MainState.Error(e)
romElements.toFile(romsFile)
} catch (e : IOException) {
Log.w(TAG, "Ran into exception while saving: ${e.message}")
}
state = MainState.Loaded(romElements)
}
}
}

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>()
private val settings by lazy { Settings(this) }
@Inject
lateinit var settings : Settings
@Inject
lateinit var inputManager : InputManager

View File

@ -15,16 +15,22 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.R
import emu.skyline.databinding.OnScreenEditActivityBinding
import emu.skyline.utils.Settings
import javax.inject.Inject
@AndroidEntryPoint
class OnScreenEditActivity : AppCompatActivity() {
private val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) }
private var fullEditVisible = true
private var editMode = false
@Inject
lateinit var settings : Settings
private val closeAction : () -> Unit = {
if (editMode) {
toggleFabVisibility(true)
@ -85,7 +91,7 @@ class OnScreenEditActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.onScreenControllerView.recenterSticks = Settings(this).onScreenControlRecenterSticks
binding.onScreenControllerView.recenterSticks = settings.onScreenControlRecenterSticks
actions.forEach { pair ->
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 androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.utils.Settings
import javax.inject.Inject
/**
* This activity is used to launch a document picker and saves the result to preferences
*/
@AndroidEntryPoint
abstract class DocumentActivity : AppCompatActivity() {
companion object {
const val KEY_NAME = "key_name"
@ -24,6 +27,9 @@ abstract class DocumentActivity : AppCompatActivity() {
protected abstract val actionIntent : Intent
@Inject
lateinit var settings : Settings
/**
* 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)
Settings(this).refreshRequired = true
settings.refreshRequired = true
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(keyName, uri.toString())
.apply()

View File

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

View File

@ -6,8 +6,12 @@
package emu.skyline.utils
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 searchLocation by sharedPreferences(context, "")