Use activity contracts for callbacks

This commit is contained in:
Willi Ye 2021-02-26 12:41:57 +01:00 committed by ◱ Mark
parent 8dd4858612
commit 8f3390f073
20 changed files with 156 additions and 336 deletions

View File

@ -28,11 +28,6 @@ android {
sourceCompatibility = javaVersion sourceCompatibility = javaVersion
targetCompatibility = javaVersion targetCompatibility = javaVersion
} }
kotlinOptions {
jvmTarget = javaVersion.toString()
}
/* Build Options */
buildTypes { buildTypes {
release { release {
debuggable true debuggable true
@ -53,9 +48,6 @@ android {
shrinkResources false shrinkResources false
} }
} }
buildFeatures {
viewBinding true
}
/* Linting */ /* Linting */
lintOptions { lintOptions {
@ -75,9 +67,18 @@ android {
aaptOptions { aaptOptions {
ignoreAssetsPattern "*.md" ignoreAssetsPattern "*.md"
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 kotlinOptions {
targetCompatibility JavaVersion.VERSION_1_8 jvmTarget = javaVersion.toString()
useIR = true
}
buildFeatures {
viewBinding true
prefab true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
} }
} }
@ -97,9 +98,13 @@ 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.fragment:fragment-ktx:1.2.5' implementation 'androidx.fragment:fragment-ktx:1.3.0'
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"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation 'androidx.activity:activity-compose:1.3.0-alpha03'
implementation 'com.google.android:flexbox:2.0.1' implementation 'com.google.android:flexbox:2.0.1'
/* Kotlin */ /* Kotlin */

View File

@ -68,11 +68,8 @@ class AppDialog : BottomSheetDialogFragment() {
} }
} }
/** override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
* This fills all the dialog with the information from [item] if it is valid and setup all user interaction super.onViewCreated(view, savedInstanceState)
*/
override fun onActivityCreated(savedInstanceState : Bundle?) {
super.onActivityCreated(savedInstanceState)
val missingIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.default_icon)!!.toBitmap(256, 256) val missingIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.default_icon)!!.toBitmap(256, 256)

View File

@ -11,6 +11,7 @@ import android.graphics.Rect
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
@ -20,7 +21,6 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.size import androidx.core.view.size
import androidx.lifecycle.observe
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -75,6 +75,19 @@ class MainActivity : AppCompatActivity() {
} }
} }
private val documentPicker = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
it?.let { uri ->
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
settings.searchLocation = uri.toString()
loadRoms(false)
}
}
private val settingsCallback = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (settings.refreshRequired) loadRoms(false)
}
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?) {
@ -117,12 +130,12 @@ class MainActivity : AppCompatActivity() {
} }
binding.chipGroup.check(binding.chipGroup.getChildAt(settings.filter).id) binding.chipGroup.check(binding.chipGroup.getChildAt(settings.filter).id)
viewModel.stateData.observe(owner = this, onChanged = ::handleState) viewModel.stateData.observe(this, ::handleState)
loadRoms(!settings.refreshRequired) loadRoms(!settings.refreshRequired)
binding.searchBar.apply { binding.searchBar.apply {
binding.logIcon.setOnClickListener { startActivity(Intent(context, LogActivity::class.java)) } binding.logIcon.setOnClickListener { startActivity(Intent(context, LogActivity::class.java)) }
binding.settingsIcon.setOnClickListener { startActivityForResult(Intent(context, SettingsActivity::class.java), 3) } binding.settingsIcon.setOnClickListener { settingsCallback.launch(Intent(context, SettingsActivity::class.java)) }
binding.refreshIcon.setOnClickListener { loadRoms(false) } binding.refreshIcon.setOnClickListener { loadRoms(false) }
addTextChangedListener(afterTextChanged = { editable -> addTextChangedListener(afterTextChanged = { editable ->
editable?.let { text -> adapter.filter.filter(text.toString()) } editable?.let { text -> adapter.filter.filter(text.toString()) }
@ -222,11 +235,7 @@ class MainActivity : AppCompatActivity() {
binding.appList.layoutManager = CustomLayoutManager(gridSpan) binding.appList.layoutManager = CustomLayoutManager(gridSpan)
setAppListDecoration() setAppListDecoration()
if (settings.searchLocation.isEmpty()) startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { if (settings.searchLocation.isEmpty()) documentPicker.launch(null)
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION
}, 1)
} }
private fun getDataItems() = mutableListOf<DataItem>().apply { private fun getDataItems() = mutableListOf<DataItem>().apply {
@ -288,41 +297,6 @@ class MainActivity : AppCompatActivity() {
if (items.isEmpty()) adapter.setItems(listOf(HeaderViewItem(getString(R.string.no_rom)))) if (items.isEmpty()) adapter.setItems(listOf(HeaderViewItem(getString(R.string.no_rom))))
} }
/**
* This handles receiving activity result from [Intent.ACTION_OPEN_DOCUMENT_TREE], [Intent.ACTION_OPEN_DOCUMENT] and [SettingsActivity]
*/
override fun onActivityResult(requestCode : Int, resultCode : Int, intent : Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
if (resultCode == RESULT_OK) {
when (requestCode) {
1 -> {
val uri = intent!!.data!!
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
settings.searchLocation = uri.toString()
loadRoms(!settings.refreshRequired)
}
2 -> {
try {
val intentGame = Intent(this, EmulationActivity::class.java)
intentGame.data = intent!!.data!!
if (resultCode != 0)
startActivityForResult(intentGame, resultCode)
else
startActivity(intentGame)
} catch (e : Exception) {
Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_SHORT).show()
}
}
3 -> if (settings.refreshRequired) loadRoms(false)
}
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()

View File

@ -5,15 +5,11 @@
package emu.skyline package emu.skyline
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent import android.view.KeyEvent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceGroup
import emu.skyline.databinding.SettingsActivityBinding import emu.skyline.databinding.SettingsActivityBinding
import emu.skyline.preference.ActivityResultPreference
import emu.skyline.preference.DocumentActivity
class SettingsActivity : AppCompatActivity() { class SettingsActivity : AppCompatActivity() {
val binding by lazy { SettingsActivityBinding.inflate(layoutInflater) } val binding by lazy { SettingsActivityBinding.inflate(layoutInflater) }
@ -35,57 +31,21 @@ class SettingsActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
.replace(R.id.settings, preferenceFragment) .replace(R.id.settings, preferenceFragment)
.commit() .commit()
}
/**
* This is used to refresh the preferences after [DocumentActivity] or [emu.skyline.input.ControllerActivity] has returned
*/
public override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
super.onActivityResult(requestCode, resultCode, data)
preferenceFragment.delegateActivityResult(requestCode, resultCode, data)
} }
/** /**
* This fragment is used to display all of the preferences * This fragment is used to display all of the preferences
*/ */
class PreferenceFragment : PreferenceFragmentCompat() { class PreferenceFragment : PreferenceFragmentCompat() {
private var requestCodeCounter = 0
/**
* Delegates activity result to all preferences which implement [ActivityResultPreference]
*/
fun delegateActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
preferenceScreen.delegateActivityResult(requestCode, resultCode, data)
}
/** /**
* This constructs the preferences from [R.xml.preferences] * This constructs the preferences from [R.xml.preferences]
*/ */
override fun onCreatePreferences(savedInstanceState : Bundle?, rootKey : String?) { override fun onCreatePreferences(savedInstanceState : Bundle?, rootKey : String?) {
setPreferencesFromResource(R.xml.preferences, rootKey) setPreferencesFromResource(R.xml.preferences, rootKey)
preferenceScreen.assignActivityRequestCode()
}
private fun PreferenceGroup.assignActivityRequestCode() {
for (i in 0 until preferenceCount) {
when (val pref = getPreference(i)) {
is PreferenceGroup -> pref.assignActivityRequestCode()
is ActivityResultPreference -> pref.requestCode = requestCodeCounter++
}
}
}
private fun PreferenceGroup.delegateActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
for (i in 0 until preferenceCount) {
when (val pref = getPreference(i)) {
is PreferenceGroup -> pref.delegateActivityResult(requestCode, resultCode, data)
is ActivityResultPreference -> pref.onActivityResult(requestCode, resultCode, data)
}
}
} }
} }

View File

@ -6,6 +6,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import emu.skyline.input.InputManager import emu.skyline.input.InputManager
import emu.skyline.utils.Settings
@EntryPoint @EntryPoint
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
@ -14,3 +15,11 @@ interface InputManagerProviderEntryPoint {
} }
fun Context.getInputManager() = EntryPointAccessors.fromApplication(this, InputManagerProviderEntryPoint::class.java).inputManager() fun Context.getInputManager() = EntryPointAccessors.fromApplication(this, InputManagerProviderEntryPoint::class.java).inputManager()
@EntryPoint
@InstallIn(SingletonComponent::class)
interface SettingsProviderEntryPoint {
fun settings() : Settings
}
fun Context.getSettings() = EntryPointAccessors.fromApplication(this, SettingsProviderEntryPoint::class.java).settings()

View File

@ -45,19 +45,16 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.state = BottomSheetBehavior.STATE_EXPANDED
} }
/** override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
* This sets up all user interaction with this dialog super.onViewCreated(view, savedInstanceState)
*/
override fun onActivityCreated(savedInstanceState : Bundle?) {
super.onActivityCreated(savedInstanceState)
if (item != null && context is ControllerActivity) { if (item != null && context is ControllerActivity) {
val context = requireContext() as ControllerActivity val context = requireContext() as ControllerActivity
val controller = inputManager.controllers[context.id]!! val controller = inputManager.controllers[context.id]!!
// View focus handling so all input is always directed to this view // View focus handling so all input is always directed to this view
view?.requestFocus() view.requestFocus()
view?.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() } view.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
// Write the text for the button's icon // Write the text for the button's icon
binding.buttonText.text = item.button.short ?: item.button.toString() binding.buttonText.text = item.button.short ?: item.button.toString()
@ -145,7 +142,7 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
val axesHistory = arrayOfNulls<Float>(axes.size) // The last recorded value of an axis, this is used to eliminate any stagnant axes val axesHistory = arrayOfNulls<Float>(axes.size) // The last recorded value of an axis, this is used to eliminate any stagnant axes
view?.setOnGenericMotionListener { _, event -> view.setOnGenericMotionListener { _, event ->
// We retrieve the value of the HAT axes so that we can check for change and ignore any input from them so it'll be passed onto the [KeyEvent] handler // We retrieve the value of the HAT axes so that we can check for change and ignore any input from them so it'll be passed onto the [KeyEvent] handler
val dpadX = event.getAxisValue(MotionEvent.AXIS_HAT_X) val dpadX = event.getAxisValue(MotionEvent.AXIS_HAT_X)
val dpadY = event.getAxisValue(MotionEvent.AXIS_HAT_Y) val dpadY = event.getAxisValue(MotionEvent.AXIS_HAT_Y)

View File

@ -45,11 +45,8 @@ class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralViewIte
behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.state = BottomSheetBehavior.STATE_EXPANDED
} }
/** override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
* This sets up all user interaction with this dialog super.onViewCreated(view, savedInstanceState)
*/
override fun onActivityCreated(savedInstanceState : Bundle?) {
super.onActivityCreated(savedInstanceState)
if (item != null && context is ControllerActivity) { if (item != null && context is ControllerActivity) {
val context = requireContext() as ControllerActivity val context = requireContext() as ControllerActivity

View File

@ -14,7 +14,6 @@ import android.view.*
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.R import emu.skyline.R
import emu.skyline.adapter.controller.ControllerStickViewItem import emu.skyline.adapter.controller.ControllerStickViewItem
import emu.skyline.databinding.StickDialogBinding import emu.skyline.databinding.StickDialogBinding
@ -22,7 +21,6 @@ import emu.skyline.di.getInputManager
import emu.skyline.input.* import emu.skyline.input.*
import emu.skyline.input.MotionHostEvent.Companion.axes import emu.skyline.input.MotionHostEvent.Companion.axes
import java.util.* import java.util.*
import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
@ -224,19 +222,16 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
} }
} }
/** override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
* This sets up all user interaction with this dialog super.onViewCreated(view, savedInstanceState)
*/
override fun onActivityCreated(savedInstanceState : Bundle?) {
super.onActivityCreated(savedInstanceState)
if (item != null && context is ControllerActivity) { if (item != null && context is ControllerActivity) {
val context = requireContext() as ControllerActivity val context = requireContext() as ControllerActivity
val controller = inputManager.controllers[context.id]!! val controller = inputManager.controllers[context.id]!!
// View focus handling so all input is always directed to this view // View focus handling so all input is always directed to this view
view?.requestFocus() view.requestFocus()
view?.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() } view.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
// Write the text for the stick's icon // Write the text for the stick's icon
binding.stickName.text = item.stick.button.short ?: item.stick.button.toString() binding.stickName.text = item.stick.button.short ?: item.stick.button.toString()
@ -286,7 +281,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
axisRunnable?.let { handler.removeCallbacks(it) } axisRunnable?.let { handler.removeCallbacks(it) }
} }
view?.setOnKeyListener { _, _, event -> view.setOnKeyListener { _, _, event ->
when { when {
// We want all input events from Joysticks and Buttons except for [KeyEvent.KEYCODE_BACK] as that will should be processed elsewhere // We want all input events from Joysticks and Buttons except for [KeyEvent.KEYCODE_BACK] as that will should be processed elsewhere
((event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON) && event.keyCode != KeyEvent.KEYCODE_BACK) || event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) && event.repeatCount == 0 -> { ((event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON) && event.keyCode != KeyEvent.KEYCODE_BACK) || event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) && event.repeatCount == 0 -> {
@ -415,7 +410,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
var oldHat = Pair(0.0f, 0.0f) // The last values of the HAT axes so that they can be ignored in [View.OnGenericMotionListener] so they are passed onto [DialogInterface.OnKeyListener] as [KeyEvent]s var oldHat = Pair(0.0f, 0.0f) // The last values of the HAT axes so that they can be ignored in [View.OnGenericMotionListener] so they are passed onto [DialogInterface.OnKeyListener] as [KeyEvent]s
view?.setOnGenericMotionListener { _, event -> view.setOnGenericMotionListener { _, event ->
// We retrieve the value of the HAT axes so that we can check for change and ignore any input from them so it'll be passed onto the [KeyEvent] handler // We retrieve the value of the HAT axes so that we can check for change and ignore any input from them so it'll be passed onto the [KeyEvent] handler
val hat = Pair(event.getAxisValue(MotionEvent.AXIS_HAT_X), event.getAxisValue(MotionEvent.AXIS_HAT_Y)) val hat = Pair(event.getAxisValue(MotionEvent.AXIS_HAT_X), event.getAxisValue(MotionEvent.AXIS_HAT_Y))

View File

@ -1,20 +0,0 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.content.Context
import android.content.Intent
import android.util.AttributeSet
import androidx.preference.Preference
/**
* Some preferences need results from activities, this delegates the results to them
*/
abstract class ActivityResultPreference @JvmOverloads constructor(context : Context?, attrs : AttributeSet? = null, defStyleAttr : Int = 0) : Preference(context, attrs, defStyleAttr) {
var requestCode = 0
abstract fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?)
}

View File

@ -5,10 +5,12 @@
package emu.skyline.preference package emu.skyline.preference
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.AttributeSet import android.util.AttributeSet
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.Preference
import androidx.preference.Preference.SummaryProvider import androidx.preference.Preference.SummaryProvider
import emu.skyline.R import emu.skyline.R
import emu.skyline.di.getInputManager import emu.skyline.di.getInputManager
@ -17,7 +19,12 @@ import emu.skyline.input.ControllerActivity
/** /**
* This preference is used to launch [ControllerActivity] using a preference * This preference is used to launch [ControllerActivity] using a preference
*/ */
class ControllerPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = R.attr.preferenceStyle) : ActivityResultPreference(context, attrs, defStyleAttr) { class ControllerPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) {
private val controllerCallback = (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
inputManager.syncObjects()
notifyChanged()
}
/** /**
* The index of the controller this preference manages * The index of the controller this preference manages
*/ */
@ -48,14 +55,5 @@ class ControllerPreference @JvmOverloads constructor(context : Context, attrs :
/** /**
* This launches [ControllerActivity] on click to configure the controller * This launches [ControllerActivity] on click to configure the controller
*/ */
override fun onClick() { override fun onClick() = controllerCallback.launch(Intent(context, ControllerActivity::class.java))
(context as Activity).startActivityForResult(Intent(context, ControllerActivity::class.java).apply { putExtra("index", index) }, requestCode)
}
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
if (this.requestCode == requestCode) {
inputManager.syncObjects()
notifyChanged()
}
}
} }

View File

@ -1,63 +0,0 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.app.Activity
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"
}
private lateinit var keyName : String
protected abstract val actionIntent : Intent
@Inject
lateinit var settings : Settings
/**
* This launches the [Intent.ACTION_OPEN_DOCUMENT_TREE] intent on creation
*/
override fun onCreate(state : Bundle?) {
super.onCreate(state)
keyName = intent.getStringExtra(KEY_NAME)!!
this.startActivityForResult(actionIntent, 1)
}
/**
* This changes the search location preference if the [Intent.ACTION_OPEN_DOCUMENT_TREE] has returned and [finish]es the activity
*/
public override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == 1) {
val uri = data!!.data!!
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
settings.refreshRequired = true
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(keyName, uri.toString())
.apply()
}
setResult(resultCode)
finish()
}
}

View File

@ -1,18 +0,0 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.content.Intent
/**
* Launches document picker to select one file
*/
class FileActivity : DocumentActivity() {
override val actionIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
}

View File

@ -1,35 +0,0 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.AttributeSet
import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import emu.skyline.KeyReader
import emu.skyline.R
import emu.skyline.SettingsActivity
/**
* Launches [FileActivity] and process the selected file for key import
*/
class FilePreference @JvmOverloads constructor(context : Context?, attrs : AttributeSet? = null, defStyleAttr : Int = androidx.preference.R.attr.preferenceStyle) : ActivityResultPreference(context, attrs, defStyleAttr) {
override fun onClick() = (context as Activity).startActivityForResult(Intent(context, FileActivity::class.java).apply { putExtra(DocumentActivity.KEY_NAME, key) }, requestCode)
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
if (this.requestCode == requestCode && resultCode == Activity.RESULT_OK && (key == KeyReader.KeyType.Prod.keyName || key == KeyReader.KeyType.Title.keyName)) {
val success = KeyReader.import(
context,
Uri.parse(PreferenceManager.getDefaultSharedPreferences(context).getString(key, "")),
KeyReader.KeyType.parse(key)
)
Snackbar.make((context as SettingsActivity).binding.root, if (success) R.string.import_keys_success else R.string.import_keys_failed, Snackbar.LENGTH_LONG).show()
}
}
}

View File

@ -1,15 +0,0 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.content.Intent
/**
* Launches document picker to select a folder
*/
class FolderActivity : DocumentActivity() {
override val actionIntent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
}

View File

@ -0,0 +1,38 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.AttributeSet
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.Preference
import androidx.preference.Preference.SummaryProvider
import androidx.preference.PreferenceManager
import androidx.preference.R
import emu.skyline.di.getSettings
class FolderPickerPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) {
private val documentPicker = (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
it?.let { uri ->
context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.getSettings().refreshRequired = true
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(key, uri.toString()).apply()
notifyChanged()
}
}
init {
summaryProvider = SummaryProvider<FolderPickerPreference> { preference ->
Uri.decode(preference.getPersistedString(""))
}
}
override fun onClick() = documentPicker.launch(null)
}

View File

@ -1,36 +0,0 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.AttributeSet
import androidx.preference.Preference.SummaryProvider
import androidx.preference.R
/**
* This preference shows the decoded URI of it's preference and launches [DocumentActivity]
*/
class FolderPreference @JvmOverloads constructor(context : Context?, attrs : AttributeSet? = null, defStyleAttr : Int = R.attr.preferenceStyle) : ActivityResultPreference(context, attrs, defStyleAttr) {
init {
summaryProvider = SummaryProvider<FolderPreference> { preference ->
Uri.decode(preference.getPersistedString(""))
}
}
/**
* This launches [DocumentActivity] on click to change the directory
*/
override fun onClick() {
(context as Activity).startActivityForResult(Intent(context, FolderActivity::class.java).apply { putExtra(DocumentActivity.KEY_NAME, key) }, requestCode)
}
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
if (requestCode == requestCode) notifyChanged()
}
}

View File

@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.content.Context
import android.content.Intent
import android.util.AttributeSet
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import emu.skyline.KeyReader
import emu.skyline.R
import emu.skyline.SettingsActivity
import emu.skyline.di.getSettings
class KeyPickerPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = androidx.preference.R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) {
private val documentPicker = (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.OpenDocument()) {
it?.let { uri ->
context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.getSettings().refreshRequired = true
val success = KeyReader.import(context, uri, KeyReader.KeyType.parse(key))
Snackbar.make((context as SettingsActivity).binding.root, if (success) R.string.import_keys_success else R.string.import_keys_failed, Snackbar.LENGTH_LONG).show()
}
}
override fun onClick() = documentPicker.launch(null)
}

View File

@ -32,8 +32,8 @@ class LicenseDialog : DialogFragment() {
}.root }.root
} }
override fun onActivityCreated(savedInstanceState : Bundle?) { override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
super.onActivityCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.licenseUrl.text = requireArguments().getString("libraryUrl") binding.licenseUrl.text = requireArguments().getString("libraryUrl")
binding.licenseContent.text = getString(requireArguments().getInt("libraryLicense")) binding.licenseContent.text = getString(requireArguments().getInt("libraryLicense"))

View File

@ -3,7 +3,7 @@
<PreferenceCategory <PreferenceCategory
android:key="category_emulator" android:key="category_emulator"
android:title="@string/emulator"> android:title="@string/emulator">
<emu.skyline.preference.FolderPreference <emu.skyline.preference.FolderPickerPreference
app:key="search_location" app:key="search_location"
app:title="@string/search_location" /> app:title="@string/search_location" />
<emu.skyline.preference.ThemePreference <emu.skyline.preference.ThemePreference
@ -54,11 +54,11 @@
<PreferenceCategory <PreferenceCategory
android:key="category_keys" android:key="category_keys"
android:title="@string/keys"> android:title="@string/keys">
<emu.skyline.preference.FilePreference <emu.skyline.preference.KeyPickerPreference
app:key="prod_keys" app:key="prod_keys"
app:title="@string/prod_keys" app:title="@string/prod_keys"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<emu.skyline.preference.FilePreference <emu.skyline.preference.KeyPickerPreference
app:key="title_keys" app:key="title_keys"
app:title="@string/title_keys" app:title="@string/title_keys"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />

View File

@ -1,17 +1,19 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.4.30' ext {
ext.lifecycle_version = '2.2.0' kotlin_version = '1.4.31'
ext.hilt_version = '2.31.2-alpha' lifecycle_version = '2.3.0'
hilt_version = '2.32-alpha'
compose_version = '1.0.0-beta01'
}
repositories { repositories {
google() google()
jcenter()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.2' classpath 'com.android.tools.build:gradle:7.0.0-alpha08'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
@ -23,6 +25,8 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
mavenCentral()
maven { url "https://google.bintray.com/flexbox-layout" }
jcenter() jcenter()
} }
} }