mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-23 04:09:18 +01:00
Switch to viewbinding and di with hilt
This commit is contained in:
parent
ff1b9cb510
commit
f5d5caf939
@ -110,6 +110,10 @@
|
|||||||
</indentOptions>
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="XML">
|
<codeStyleSettings language="XML">
|
||||||
|
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
<arrangement>
|
<arrangement>
|
||||||
<rules>
|
<rules>
|
||||||
<section>
|
<section>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
apply plugin: 'kotlin-android'
|
id 'com.android.application'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
id 'kotlin-android'
|
||||||
|
id 'kotlin-kapt'
|
||||||
|
id 'dagger.hilt.android.plugin'
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
@ -51,14 +54,7 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
prefab true
|
viewBinding true
|
||||||
viewBinding false
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Android Extensions */
|
|
||||||
androidExtensions {
|
|
||||||
/* TODO: Remove this after migrating to View Bindings */
|
|
||||||
experimental = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
@ -79,6 +75,10 @@ android {
|
|||||||
aaptOptions {
|
aaptOptions {
|
||||||
ignoreAssetsPattern "*.md"
|
ignoreAssetsPattern "*.md"
|
||||||
}
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -99,6 +99,8 @@ dependencies {
|
|||||||
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.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"
|
||||||
|
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
|
||||||
|
|
||||||
/* Kotlin */
|
/* Kotlin */
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
|
@ -20,8 +20,8 @@ import androidx.core.graphics.drawable.toBitmap
|
|||||||
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 emu.skyline.data.AppItem
|
import emu.skyline.data.AppItem
|
||||||
|
import emu.skyline.databinding.AppDialogBinding
|
||||||
import emu.skyline.loader.LoaderResult
|
import emu.skyline.loader.LoaderResult
|
||||||
import kotlinx.android.synthetic.main.app_dialog.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This dialog is used to show extra game metadata and provide extra options such as pinning the game to the home screen
|
* This dialog is used to show extra game metadata and provide extra options such as pinning the game to the home screen
|
||||||
@ -41,19 +41,15 @@ class AppDialog : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var item : AppItem
|
private lateinit var binding : AppDialogBinding
|
||||||
|
|
||||||
|
private val item by lazy { requireArguments().getSerializable("item") as AppItem }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This inflates the layout of the dialog after initial view creation
|
* This inflates the layout of the dialog after initial view creation
|
||||||
*/
|
*/
|
||||||
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) : View? {
|
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) : View? {
|
||||||
return requireActivity().layoutInflater.inflate(R.layout.app_dialog, container)
|
return AppDialogBinding.inflate(inflater).also { binding = it }.root
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState : Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
item = requireArguments().getSerializable("item") as AppItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,19 +78,19 @@ class AppDialog : BottomSheetDialogFragment() {
|
|||||||
|
|
||||||
val missingIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.default_icon)!!.toBitmap(256, 256)
|
val missingIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.default_icon)!!.toBitmap(256, 256)
|
||||||
|
|
||||||
game_icon.setImageBitmap(item.icon ?: missingIcon)
|
binding.gameIcon.setImageBitmap(item.icon ?: missingIcon)
|
||||||
game_title.text = item.title
|
binding.gameTitle.text = item.title
|
||||||
game_subtitle.text = item.subTitle ?: item.loaderResultString(requireContext())
|
binding.gameSubtitle.text = item.subTitle ?: item.loaderResultString(requireContext())
|
||||||
|
|
||||||
game_play.isEnabled = item.loaderResult == LoaderResult.Success
|
binding.gamePlay.isEnabled = item.loaderResult == LoaderResult.Success
|
||||||
game_play.setOnClickListener {
|
binding.gamePlay.setOnClickListener {
|
||||||
startActivity(Intent(activity, EmulationActivity::class.java).apply { data = item.uri })
|
startActivity(Intent(activity, EmulationActivity::class.java).apply { data = item.uri })
|
||||||
}
|
}
|
||||||
|
|
||||||
val shortcutManager = requireActivity().getSystemService(ShortcutManager::class.java)
|
val shortcutManager = requireActivity().getSystemService(ShortcutManager::class.java)
|
||||||
game_pin.isEnabled = shortcutManager.isRequestPinShortcutSupported
|
binding.gamePin.isEnabled = shortcutManager.isRequestPinShortcutSupported
|
||||||
|
|
||||||
game_pin.setOnClickListener {
|
binding.gamePin.setOnClickListener {
|
||||||
val info = ShortcutInfo.Builder(context, item.title)
|
val info = ShortcutInfo.Builder(context, item.title)
|
||||||
info.setShortLabel(item.title)
|
info.setShortLabel(item.title)
|
||||||
info.setActivity(ComponentName(requireContext(), EmulationActivity::class.java))
|
info.setActivity(ComponentName(requireContext(), EmulationActivity::class.java))
|
||||||
|
@ -17,13 +17,16 @@ import android.view.*
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import emu.skyline.databinding.EmuActivityBinding
|
||||||
import emu.skyline.input.*
|
import emu.skyline.input.*
|
||||||
import emu.skyline.loader.getRomFormat
|
import emu.skyline.loader.getRomFormat
|
||||||
import emu.skyline.utils.Settings
|
import emu.skyline.utils.Settings
|
||||||
import kotlinx.android.synthetic.main.emu_activity.*
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTouchListener {
|
class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTouchListener {
|
||||||
companion object {
|
companion object {
|
||||||
private val Tag = EmulationActivity::class.java.simpleName
|
private val Tag = EmulationActivity::class.java.simpleName
|
||||||
@ -33,8 +36,10 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
System.loadLibrary("skyline") // libskyline.so
|
System.loadLibrary("skyline") // libskyline.so
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>()
|
||||||
|
|
||||||
@ -51,6 +56,9 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
|
|
||||||
private val settings by lazy { Settings(this) }
|
private val settings by lazy { Settings(this) }
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var inputManager : InputManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the entry point into the emulation code for libskyline
|
* This is the entry point into the emulation code for libskyline
|
||||||
*
|
*
|
||||||
@ -131,7 +139,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
private fun initializeControllers() {
|
private fun initializeControllers() {
|
||||||
for (controller in InputManager.controllers.values) {
|
for (controller in inputManager.controllers.values) {
|
||||||
if (controller.type != ControllerType.None) {
|
if (controller.type != ControllerType.None) {
|
||||||
val type = when (controller.type) {
|
val type = when (controller.type) {
|
||||||
ControllerType.None -> throw IllegalArgumentException()
|
ControllerType.None -> throw IllegalArgumentException()
|
||||||
@ -177,11 +185,11 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
/**
|
/**
|
||||||
* This makes the window fullscreen, sets up the performance statistics and finally calls [executeApplication] for executing the application
|
* This makes the window fullscreen, sets up the performance statistics and finally calls [executeApplication] for executing the application
|
||||||
*/
|
*/
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||||
override fun onCreate(savedInstanceState : Bundle?) {
|
override fun onCreate(savedInstanceState : Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.emu_activity)
|
setContentView(binding.root)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
window.insetsController?.hide(WindowInsets.Type.navigationBars() or WindowInsets.Type.systemBars() or WindowInsets.Type.systemGestures() or WindowInsets.Type.statusBars())
|
window.insetsController?.hide(WindowInsets.Type.navigationBars() or WindowInsets.Type.systemBars() or WindowInsets.Type.systemGestures() or WindowInsets.Type.statusBars())
|
||||||
@ -196,32 +204,36 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
game_view.holder.addCallback(this)
|
binding.gameView.holder.addCallback(this)
|
||||||
|
|
||||||
if (settings.perfStats) {
|
if (settings.perfStats) {
|
||||||
perf_stats.postDelayed(object : Runnable {
|
binding.perfStats.apply {
|
||||||
override fun run() {
|
postDelayed(object : Runnable {
|
||||||
updatePerformanceStatistics()
|
override fun run() {
|
||||||
perf_stats.text = "$fps FPS\n${frametime}ms"
|
updatePerformanceStatistics()
|
||||||
perf_stats.postDelayed(this, 250)
|
text = "$fps FPS\n${frametime}ms"
|
||||||
}
|
postDelayed(this, 250)
|
||||||
}, 250)
|
}
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION") val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) display!! else windowManager.defaultDisplay
|
@Suppress("DEPRECATION") val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) display!! else windowManager.defaultDisplay
|
||||||
display?.supportedModes?.maxByOrNull { it.refreshRate + (it.physicalHeight * it.physicalWidth) }?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
display?.supportedModes?.maxByOrNull { it.refreshRate + (it.physicalHeight * it.physicalWidth) }?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
||||||
|
|
||||||
game_view.setOnTouchListener(this)
|
binding.gameView.setOnTouchListener(this)
|
||||||
|
|
||||||
// Hide on screen controls when first controller is not set
|
// Hide on screen controls when first controller is not set
|
||||||
on_screen_controller_view.isGone = InputManager.controllers[0]!!.type == ControllerType.None || !settings.onScreenControl
|
binding.onScreenControllerView.apply {
|
||||||
on_screen_controller_view.setOnButtonStateChangedListener(::onButtonStateChanged)
|
isGone = inputManager.controllers[0]!!.type == ControllerType.None || !settings.onScreenControl
|
||||||
on_screen_controller_view.setOnStickStateChangedListener(::onStickStateChanged)
|
setOnButtonStateChangedListener(::onButtonStateChanged)
|
||||||
on_screen_controller_view.recenterSticks = settings.onScreenControlRecenterSticks
|
setOnStickStateChangedListener(::onStickStateChanged)
|
||||||
|
recenterSticks = settings.onScreenControlRecenterSticks
|
||||||
|
}
|
||||||
|
|
||||||
on_screen_controller_toggle.isGone = on_screen_controller_view.isGone
|
binding.onScreenControllerToggle.apply {
|
||||||
on_screen_controller_toggle.setOnClickListener {
|
isGone = binding.onScreenControllerView.isGone
|
||||||
on_screen_controller_view.isInvisible = !on_screen_controller_view.isInvisible
|
setOnClickListener { binding.onScreenControllerView.isInvisible = !binding.onScreenControllerView.isInvisible }
|
||||||
}
|
}
|
||||||
|
|
||||||
executeApplication(intent.data!!)
|
executeApplication(intent.data!!)
|
||||||
@ -291,7 +303,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
else -> return super.dispatchKeyEvent(event)
|
else -> return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (val guestEvent = InputManager.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) {
|
return when (val guestEvent = inputManager.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) {
|
||||||
is ButtonGuestEvent -> {
|
is ButtonGuestEvent -> {
|
||||||
if (guestEvent.button != ButtonId.Menu)
|
if (guestEvent.button != ButtonId.Menu)
|
||||||
setButtonState(guestEvent.id, guestEvent.button.value(), action.state)
|
setButtonState(guestEvent.id, guestEvent.button.value(), action.state)
|
||||||
@ -333,9 +345,9 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
var polarity = value >= 0
|
var polarity = value >= 0
|
||||||
|
|
||||||
val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent ->
|
val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent ->
|
||||||
InputManager.eventMap[hostEvent] ?: if (value == 0f) {
|
inputManager.eventMap[hostEvent] ?: if (value == 0f) {
|
||||||
polarity = false
|
polarity = false
|
||||||
InputManager.eventMap[hostEvent.copy(polarity = false)]
|
inputManager.eventMap[hostEvent.copy(polarity = false)]
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -413,7 +425,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
val vibrator = if (vibrators[index] != null) {
|
val vibrator = if (vibrators[index] != null) {
|
||||||
vibrators[index]!!
|
vibrators[index]!!
|
||||||
} else {
|
} else {
|
||||||
InputManager.controllers[index]!!.rumbleDeviceDescriptor?.let {
|
inputManager.controllers[index]!!.rumbleDeviceDescriptor?.let {
|
||||||
if (it == "builtin") {
|
if (it == "builtin") {
|
||||||
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
vibrators[index] = vibrator
|
vibrators[index] = vibrator
|
||||||
@ -421,7 +433,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
} else {
|
} else {
|
||||||
for (id in InputDevice.getDeviceIds()) {
|
for (id in InputDevice.getDeviceIds()) {
|
||||||
val device = InputDevice.getDevice(id)
|
val device = InputDevice.getDevice(id)
|
||||||
if (device.descriptor == InputManager.controllers[index]!!.rumbleDeviceDescriptor) {
|
if (device.descriptor == inputManager.controllers[index]!!.rumbleDeviceDescriptor) {
|
||||||
vibrators[index] = device.vibrator
|
vibrators[index] = device.vibrator
|
||||||
device.vibrator
|
device.vibrator
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,8 @@ import com.google.android.material.snackbar.Snackbar
|
|||||||
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
|
||||||
|
import emu.skyline.databinding.LogActivityBinding
|
||||||
import emu.skyline.utils.Settings
|
import emu.skyline.utils.Settings
|
||||||
import kotlinx.android.synthetic.main.log_activity.*
|
|
||||||
import kotlinx.android.synthetic.main.titlebar.*
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
@ -31,25 +30,21 @@ import java.net.URL
|
|||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
class LogActivity : AppCompatActivity() {
|
class LogActivity : AppCompatActivity() {
|
||||||
|
private val binding by lazy { LogActivityBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The log file is used to read log entries from or to clear all entries
|
* The log file is used to read log entries from or to clear all entries
|
||||||
*/
|
*/
|
||||||
private lateinit var logFile : File
|
private lateinit var logFile : File
|
||||||
|
|
||||||
/**
|
|
||||||
* The adapter used for adding elements from the log to [log_list]
|
|
||||||
*/
|
|
||||||
private val adapter = GenericAdapter()
|
private val adapter = GenericAdapter()
|
||||||
|
|
||||||
/**
|
|
||||||
* This initializes [toolbar] and fills [log_list] with data from the logs
|
|
||||||
*/
|
|
||||||
override fun onCreate(savedInstanceState : Bundle?) {
|
override fun onCreate(savedInstanceState : Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.log_activity)
|
setContentView(binding.root)
|
||||||
|
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(binding.titlebar.toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
val settings = Settings(this)
|
val settings = Settings(this)
|
||||||
@ -58,10 +53,9 @@ class LogActivity : AppCompatActivity() {
|
|||||||
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)
|
||||||
|
|
||||||
log_list.adapter = adapter
|
binding.logList.adapter = adapter
|
||||||
|
|
||||||
if (!compact)
|
if (!compact) binding.logList.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
|
||||||
log_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logFile = File(applicationContext.filesDir.canonicalPath + "/skyline.log")
|
logFile = File(applicationContext.filesDir.canonicalPath + "/skyline.log")
|
||||||
|
@ -31,12 +31,14 @@ import emu.skyline.adapter.LayoutType
|
|||||||
import emu.skyline.data.AppItem
|
import emu.skyline.data.AppItem
|
||||||
import emu.skyline.data.DataItem
|
import emu.skyline.data.DataItem
|
||||||
import emu.skyline.data.HeaderItem
|
import emu.skyline.data.HeaderItem
|
||||||
|
import emu.skyline.databinding.MainActivityBinding
|
||||||
import emu.skyline.loader.LoaderResult
|
import emu.skyline.loader.LoaderResult
|
||||||
import emu.skyline.utils.Settings
|
import emu.skyline.utils.Settings
|
||||||
import kotlinx.android.synthetic.main.main_activity.*
|
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
private val binding by lazy { MainActivityBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
private val settings by lazy { Settings(this) }
|
private val settings by lazy { Settings(this) }
|
||||||
|
|
||||||
private val adapter = GenericAdapter()
|
private val adapter = GenericAdapter()
|
||||||
@ -60,24 +62,23 @@ class MainActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(binding.root)
|
||||||
setContentView(R.layout.main_activity)
|
|
||||||
|
|
||||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
|
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
|
||||||
|
|
||||||
setupAppList()
|
setupAppList()
|
||||||
|
|
||||||
swipe_refresh_layout.apply {
|
binding.swipeRefreshLayout.apply {
|
||||||
setProgressBackgroundColorSchemeColor(obtainStyledAttributes(intArrayOf(R.attr.colorPrimary)).use { it.getColor(0, Color.BLACK) })
|
setProgressBackgroundColorSchemeColor(obtainStyledAttributes(intArrayOf(R.attr.colorPrimary)).use { it.getColor(0, Color.BLACK) })
|
||||||
setColorSchemeColors(obtainStyledAttributes(intArrayOf(R.attr.colorAccent)).use { it.getColor(0, Color.BLACK) })
|
setColorSchemeColors(obtainStyledAttributes(intArrayOf(R.attr.colorAccent)).use { it.getColor(0, Color.BLACK) })
|
||||||
post { setDistanceToTriggerSync(swipe_refresh_layout.height / 3) }
|
post { setDistanceToTriggerSync(binding.swipeRefreshLayout.height / 3) }
|
||||||
setOnRefreshListener { loadRoms(false) }
|
setOnRefreshListener { loadRoms(false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.state.observe(owner = this, onChanged = ::handleState)
|
viewModel.stateData.observe(owner = this, onChanged = ::handleState)
|
||||||
loadRoms(!settings.refreshRequired)
|
loadRoms(!settings.refreshRequired)
|
||||||
|
|
||||||
search_bar.apply {
|
binding.searchBar.apply {
|
||||||
setLogIconListener { startActivity(Intent(context, LogActivity::class.java)) }
|
setLogIconListener { startActivity(Intent(context, LogActivity::class.java)) }
|
||||||
setSettingsIconListener { startActivityForResult(Intent(context, SettingsActivity::class.java), 3) }
|
setSettingsIconListener { startActivityForResult(Intent(context, SettingsActivity::class.java), 3) }
|
||||||
setRefreshIconListener { loadRoms(false) }
|
setRefreshIconListener { loadRoms(false) }
|
||||||
@ -90,7 +91,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.decorView.findViewById<View>(android.R.id.content).viewTreeObserver.addOnTouchModeChangeListener { isInTouchMode ->
|
window.decorView.findViewById<View>(android.R.id.content).viewTreeObserver.addOnTouchModeChangeListener { isInTouchMode ->
|
||||||
search_bar.refreshIconVisible = !isInTouchMode
|
binding.searchBar.refreshIconVisible = !isInTouchMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,11 +119,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setAppListDecoration() {
|
private fun setAppListDecoration() {
|
||||||
while (app_list.itemDecorationCount > 0) app_list.removeItemDecorationAt(0)
|
binding.appList.apply {
|
||||||
when (layoutType) {
|
while (itemDecorationCount > 0) removeItemDecorationAt(0)
|
||||||
LayoutType.List -> app_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
|
when (layoutType) {
|
||||||
|
LayoutType.List -> addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
|
||||||
|
|
||||||
LayoutType.Grid, LayoutType.GridCompact -> app_list.addItemDecoration(GridSpacingItemDecoration())
|
LayoutType.Grid, LayoutType.GridCompact -> addItemDecoration(GridSpacingItemDecoration())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +135,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private inner class CustomLayoutManager(gridSpan : Int) : GridLayoutManager(this, gridSpan) {
|
private inner class CustomLayoutManager(gridSpan : Int) : GridLayoutManager(this, gridSpan) {
|
||||||
init {
|
init {
|
||||||
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||||
override fun getSpanSize(position : Int) = if (layoutType == LayoutType.List || adapter.currentItems[position] is HeaderViewItem) gridSpan else 1
|
override fun getSpanSize(position : Int) = if (layoutType == LayoutType.List || adapter.currentItems[position].fullSpan) gridSpan else 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,23 +144,24 @@ class MainActivity : AppCompatActivity() {
|
|||||||
when (focusDirection) {
|
when (focusDirection) {
|
||||||
View.FOCUS_DOWN -> {
|
View.FOCUS_DOWN -> {
|
||||||
findContainingItemView(focused)?.let { focusedChild ->
|
findContainingItemView(focused)?.let { focusedChild ->
|
||||||
val current = app_list.indexOfChild(focusedChild)
|
val current = binding.appList.indexOfChild(focusedChild)
|
||||||
val currentSpanIndex = (focusedChild.layoutParams as LayoutParams).spanIndex
|
val currentSpanIndex = (focusedChild.layoutParams as LayoutParams).spanIndex
|
||||||
for (i in current + 1 until app_list.size) {
|
for (i in current + 1 until binding.appList.size) {
|
||||||
val candidate = getChildAt(i)!!
|
val candidate = getChildAt(i)!!
|
||||||
// Return candidate when span index matches
|
// Return candidate when span index matches
|
||||||
if (currentSpanIndex == (candidate.layoutParams as LayoutParams).spanIndex) return candidate
|
if (currentSpanIndex == (candidate.layoutParams as LayoutParams).spanIndex) return candidate
|
||||||
}
|
}
|
||||||
if (nextFocus == null) {
|
if (nextFocus == null) {
|
||||||
app_bar_layout.setExpanded(false) // End of list, hide app bar, so bottom row is fully visible
|
binding.appBarLayout.setExpanded(false) // End of list, hide app bar, so bottom row is fully visible
|
||||||
app_list.smoothScrollToPosition(adapter.itemCount)
|
binding.appList.smoothScrollToPosition(adapter.itemCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
View.FOCUS_UP -> {
|
View.FOCUS_UP -> {
|
||||||
if (nextFocus?.isFocusable != true) {
|
if (nextFocus?.isFocusable != true) {
|
||||||
search_bar.requestFocus()
|
binding.searchBar.requestFocus()
|
||||||
app_bar_layout.setExpanded(true)
|
binding.appBarLayout.setExpanded(true)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,13 +171,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupAppList() {
|
private fun setupAppList() {
|
||||||
app_list.adapter = adapter
|
binding.appList.adapter = adapter
|
||||||
|
|
||||||
val itemWidth = 225
|
val itemWidth = 225
|
||||||
val metrics = resources.displayMetrics
|
val metrics = resources.displayMetrics
|
||||||
val gridSpan = ceil((metrics.widthPixels / metrics.density) / itemWidth).toInt()
|
val gridSpan = ceil((metrics.widthPixels / metrics.density) / itemWidth).toInt()
|
||||||
|
|
||||||
app_list.layoutManager = CustomLayoutManager(gridSpan)
|
binding.appList.layoutManager = CustomLayoutManager(gridSpan)
|
||||||
setAppListDecoration()
|
setAppListDecoration()
|
||||||
|
|
||||||
if (settings.searchLocation.isEmpty()) {
|
if (settings.searchLocation.isEmpty()) {
|
||||||
@ -186,18 +190,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun handleState(state : MainState) = when (state) {
|
private fun handleState(state : MainState) = when (state) {
|
||||||
MainState.Loading -> {
|
MainState.Loading -> {
|
||||||
search_bar.animateRefreshIcon()
|
binding.searchBar.animateRefreshIcon()
|
||||||
swipe_refresh_layout.isRefreshing = true
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
}
|
}
|
||||||
is MainState.Loaded -> {
|
is MainState.Loaded -> {
|
||||||
swipe_refresh_layout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
populateAdapter(state.items)
|
populateAdapter(state.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()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectStartGame(appItem : AppItem) {
|
private fun selectStartGame(appItem : AppItem) {
|
||||||
if (swipe_refresh_layout.isRefreshing) return
|
if (binding.swipeRefreshLayout.isRefreshing) return
|
||||||
|
|
||||||
if (settings.selectAction)
|
if (settings.selectAction)
|
||||||
AppDialog.newInstance(appItem).show(supportFragmentManager, "game")
|
AppDialog.newInstance(appItem).show(supportFragmentManager, "game")
|
||||||
@ -206,7 +210,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun selectShowGameDialog(appItem : AppItem) {
|
private fun selectShowGameDialog(appItem : AppItem) {
|
||||||
if (swipe_refresh_layout.isRefreshing) return
|
if (binding.swipeRefreshLayout.isRefreshing) return
|
||||||
|
|
||||||
AppDialog.newInstance(appItem).show(supportFragmentManager, "game")
|
AppDialog.newInstance(appItem).show(supportFragmentManager, "game")
|
||||||
}
|
}
|
||||||
@ -281,7 +285,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
search_bar.apply {
|
binding.searchBar.apply {
|
||||||
if (hasFocus() && text.isNotEmpty()) {
|
if (hasFocus() && text.isNotEmpty()) {
|
||||||
text = ""
|
text = ""
|
||||||
clearFocus()
|
clearFocus()
|
||||||
|
@ -31,8 +31,11 @@ class MainViewModel : ViewModel() {
|
|||||||
private val TAG = MainViewModel::class.java.simpleName
|
private val TAG = MainViewModel::class.java.simpleName
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mutableState = MutableLiveData<MainState>()
|
private var state
|
||||||
val state : LiveData<MainState> = mutableState
|
get() = _stateData.value
|
||||||
|
set(value) = _stateData.postValue(value)
|
||||||
|
private val _stateData = MutableLiveData<MainState>()
|
||||||
|
val stateData : LiveData<MainState> = _stateData
|
||||||
|
|
||||||
var searchBarAnimated = false
|
var searchBarAnimated = false
|
||||||
|
|
||||||
@ -66,16 +69,15 @@ class MainViewModel : ViewModel() {
|
|||||||
* @param loadFromFile 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
|
||||||
*/
|
*/
|
||||||
fun loadRoms(context : Context, loadFromFile : Boolean, searchLocation : Uri) {
|
fun loadRoms(context : Context, loadFromFile : Boolean, searchLocation : Uri) {
|
||||||
if (mutableState.value == MainState.Loading) return
|
if (state == MainState.Loading) return
|
||||||
|
state = MainState.Loading
|
||||||
mutableState.postValue(MainState.Loading)
|
|
||||||
|
|
||||||
val romsFile = File(context.filesDir.canonicalPath + "/roms.bin")
|
val romsFile = File(context.filesDir.canonicalPath + "/roms.bin")
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
if (loadFromFile) {
|
if (loadFromFile) {
|
||||||
try {
|
try {
|
||||||
mutableState.postValue(MainState.Loaded(loadSerializedList(romsFile)))
|
state = MainState.Loaded(loadSerializedList(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}")
|
||||||
@ -98,9 +100,9 @@ class MainViewModel : ViewModel() {
|
|||||||
Log.w(TAG, "Ran into exception while saving: ${e.message}")
|
Log.w(TAG, "Ran into exception while saving: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
mutableState.postValue(MainState.Loaded(romElements))
|
state = MainState.Loaded(romElements)
|
||||||
} catch (e : Exception) {
|
} catch (e : Exception) {
|
||||||
mutableState.postValue(MainState.Error(e))
|
state = MainState.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,13 @@ 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 androidx.preference.PreferenceGroup
|
||||||
|
import emu.skyline.databinding.SettingsActivityBinding
|
||||||
import emu.skyline.preference.ActivityResultDelegate
|
import emu.skyline.preference.ActivityResultDelegate
|
||||||
import emu.skyline.preference.DocumentActivity
|
import emu.skyline.preference.DocumentActivity
|
||||||
import kotlinx.android.synthetic.main.titlebar.*
|
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
val binding by lazy { SettingsActivityBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the instance of [PreferenceFragment] that is shown inside [R.id.settings]
|
* This is the instance of [PreferenceFragment] that is shown inside [R.id.settings]
|
||||||
*/
|
*/
|
||||||
@ -27,9 +29,9 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState : Bundle?) {
|
override fun onCreate(savedInstanceState : Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.settings_activity)
|
setContentView(binding.root)
|
||||||
|
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(binding.titlebar.toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
|
@ -6,14 +6,7 @@
|
|||||||
package emu.skyline
|
package emu.skyline
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import emu.skyline.input.InputManager
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
/**
|
@HiltAndroidApp
|
||||||
* Custom application class to initialize [InputManager]
|
class SkylineApplication : Application()
|
||||||
*/
|
|
||||||
class SkylineApplication : Application() {
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
InputManager.init(applicationContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -10,42 +10,88 @@ import android.content.Context
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.data.AppItem
|
import emu.skyline.data.AppItem
|
||||||
import kotlinx.android.synthetic.main.app_item_grid_compact.*
|
import emu.skyline.databinding.AppItemGridBinding
|
||||||
|
import emu.skyline.databinding.AppItemGridCompactBinding
|
||||||
|
import emu.skyline.databinding.AppItemLinearBinding
|
||||||
|
|
||||||
/**
|
sealed class LayoutType(val builder : (parent : ViewGroup) -> ViewBinding) {
|
||||||
* This enumerates the type of layouts the menu can be in
|
object List : LayoutType({ ListBinding(it) })
|
||||||
*/
|
object Grid : LayoutType({ GridBinding(it) })
|
||||||
enum class LayoutType(val layoutRes : Int) {
|
object GridCompact : LayoutType({ GridCompatBinding(it) })
|
||||||
List(R.layout.app_item_linear),
|
|
||||||
Grid(R.layout.app_item_grid),
|
companion object {
|
||||||
GridCompact(R.layout.app_item_grid_compact)
|
fun values() = arrayListOf(List, Grid, GridCompact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LayoutBindingFactory(private val layoutType : LayoutType) : ViewBindingFactory {
|
||||||
|
override fun createBinding(parent : ViewGroup) = layoutType.builder(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LayoutBinding<V : ViewBinding> : ViewBinding {
|
||||||
|
val binding : V
|
||||||
|
|
||||||
|
override fun getRoot() = binding.root
|
||||||
|
|
||||||
|
val textTitle : TextView
|
||||||
|
|
||||||
|
val textSubtitle : TextView
|
||||||
|
|
||||||
|
val icon : ImageView
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListBinding(parent : ViewGroup) : LayoutBinding<AppItemLinearBinding> {
|
||||||
|
override val binding = AppItemLinearBinding.inflate(parent.inflater(), parent, false)
|
||||||
|
|
||||||
|
override val textTitle = binding.textTitle
|
||||||
|
|
||||||
|
override val textSubtitle = binding.textSubtitle
|
||||||
|
|
||||||
|
override val icon = binding.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridBinding(parent : ViewGroup) : LayoutBinding<AppItemGridBinding> {
|
||||||
|
override val binding = AppItemGridBinding.inflate(parent.inflater(), parent, false)
|
||||||
|
|
||||||
|
override val textTitle = binding.textTitle
|
||||||
|
|
||||||
|
override val textSubtitle = binding.textSubtitle
|
||||||
|
|
||||||
|
override val icon = binding.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCompatBinding(parent : ViewGroup) : LayoutBinding<AppItemGridCompactBinding> {
|
||||||
|
override val binding = AppItemGridCompactBinding.inflate(parent.inflater(), parent, false)
|
||||||
|
|
||||||
|
override val textTitle = binding.textTitle
|
||||||
|
|
||||||
|
override val textSubtitle = binding.textSubtitle
|
||||||
|
|
||||||
|
override val icon = binding.icon
|
||||||
}
|
}
|
||||||
|
|
||||||
private typealias InteractionFunction = (appItem : AppItem) -> Unit
|
private typealias InteractionFunction = (appItem : AppItem) -> Unit
|
||||||
|
|
||||||
private data class AppLayoutFactory(private val layoutType : LayoutType) : GenericLayoutFactory {
|
class AppViewItem(var layoutType : LayoutType, private val item : AppItem, private val missingIcon : Bitmap, private val onClick : InteractionFunction, private val onLongClick : InteractionFunction) : GenericListItem<LayoutBinding<*>>() {
|
||||||
override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(layoutType.layoutRes, parent, false)
|
override fun getViewBindingFactory() = LayoutBindingFactory(layoutType)
|
||||||
}
|
|
||||||
|
|
||||||
class AppViewItem(var layoutType : LayoutType, private val item : AppItem, private val missingIcon : Bitmap, private val onClick : InteractionFunction, private val onLongClick : InteractionFunction) : GenericListItem() {
|
override fun bind(holder : GenericViewHolder<LayoutBinding<*>>, position : Int) {
|
||||||
override fun getLayoutFactory() : GenericLayoutFactory = AppLayoutFactory(layoutType)
|
holder.binding.textTitle.text = item.title
|
||||||
|
holder.binding.textSubtitle.text = item.subTitle ?: item.loaderResultString(holder.binding.root.context)
|
||||||
|
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
holder.binding.icon.setImageBitmap(item.icon ?: missingIcon)
|
||||||
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) {
|
if (layoutType == LayoutType.List) {
|
||||||
holder.icon.setOnClickListener { showIconDialog(holder.icon.context, item) }
|
holder.binding.icon.setOnClickListener { showIconDialog(it.context, item) }
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.itemView.findViewById<View>(R.id.item_click_layout).apply {
|
holder.itemView.findViewById<View>(R.id.item_click_layout).apply {
|
||||||
@ -68,7 +114,7 @@ class AppViewItem(var layoutType : LayoutType, private val item : AppItem, priva
|
|||||||
|
|
||||||
override fun key() = item.key()
|
override fun key() = item.key()
|
||||||
|
|
||||||
override fun areItemsTheSame(other : GenericListItem) = key() == other.key()
|
override fun areItemsTheSame(other : GenericListItem<LayoutBinding<*>>) = key() == other.key()
|
||||||
|
|
||||||
override fun areContentsTheSame(other : GenericListItem) = other is AppViewItem && layoutType == other.layoutType && item == other.item
|
override fun areContentsTheSame(other : GenericListItem<LayoutBinding<*>>) = other is AppViewItem && layoutType == other.layoutType && item == other.item
|
||||||
}
|
}
|
||||||
|
@ -5,39 +5,41 @@
|
|||||||
|
|
||||||
package emu.skyline.adapter
|
package emu.skyline.adapter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Filter
|
import android.widget.Filter
|
||||||
import android.widget.Filterable
|
import android.widget.Filterable
|
||||||
import androidx.recyclerview.widget.AsyncListDiffer
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import info.debatty.java.stringsimilarity.Cosine
|
import info.debatty.java.stringsimilarity.Cosine
|
||||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can handle any view types with [GenericListItem] implemented, [GenericListItem] are differentiated by the return value of [GenericListItem.getLayoutFactory]
|
* Can handle any view types with [GenericListItem] implemented, [GenericListItem] are differentiated by the return value of [GenericListItem.getViewBindingFactory]
|
||||||
*/
|
*/
|
||||||
class GenericAdapter : RecyclerView.Adapter<GenericViewHolder>(), Filterable {
|
class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>(), Filterable {
|
||||||
companion object {
|
companion object {
|
||||||
private val DIFFER = object : DiffUtil.ItemCallback<GenericListItem>() {
|
private val DIFFER = object : DiffUtil.ItemCallback<GenericListItem<ViewBinding>>() {
|
||||||
override fun areItemsTheSame(oldItem : GenericListItem, newItem : GenericListItem) = oldItem.areItemsTheSame(newItem)
|
override fun areItemsTheSame(oldItem : GenericListItem<ViewBinding>, newItem : GenericListItem<ViewBinding>) = oldItem.areItemsTheSame(newItem)
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem : GenericListItem, newItem : GenericListItem) = oldItem.areContentsTheSame(newItem)
|
override fun areContentsTheSame(oldItem : GenericListItem<ViewBinding>, newItem : GenericListItem<ViewBinding>) = oldItem.areContentsTheSame(newItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val asyncListDiffer = AsyncListDiffer(this, DIFFER)
|
private val asyncListDiffer = AsyncListDiffer(this, DIFFER)
|
||||||
private val allItems = mutableListOf<GenericListItem>()
|
private val allItems = mutableListOf<GenericListItem<out ViewBinding>>()
|
||||||
val currentItems : List<GenericListItem> get() = asyncListDiffer.currentList
|
val currentItems : List<GenericListItem<in ViewBinding>> get() = asyncListDiffer.currentList
|
||||||
|
|
||||||
var currentSearchTerm = ""
|
var currentSearchTerm = ""
|
||||||
|
|
||||||
private val viewTypesMapping = mutableMapOf<GenericLayoutFactory, Int>()
|
private val viewTypesMapping = mutableMapOf<ViewBindingFactory, Int>()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) = GenericViewHolder(viewTypesMapping.filterValues { it == viewType }.keys.single().createLayout(parent))
|
override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) = GenericViewHolder(viewTypesMapping.filterValues { it == viewType }.keys.single().createBinding(parent))
|
||||||
|
|
||||||
override fun onBindViewHolder(holder : GenericViewHolder, position : Int) {
|
override fun onBindViewHolder(holder : GenericViewHolder<ViewBinding>, position : Int) {
|
||||||
currentItems[position].apply {
|
currentItems[position].apply {
|
||||||
adapter = this@GenericAdapter
|
adapter = this@GenericAdapter
|
||||||
bind(holder, position)
|
bind(holder, position)
|
||||||
@ -46,9 +48,9 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder>(), Filterable {
|
|||||||
|
|
||||||
override fun getItemCount() = currentItems.size
|
override fun getItemCount() = currentItems.size
|
||||||
|
|
||||||
override fun getItemViewType(position : Int) = viewTypesMapping.getOrPut(currentItems[position].getLayoutFactory(), { viewTypesMapping.size })
|
override fun getItemViewType(position : Int) = viewTypesMapping.getOrPut(currentItems[position].getViewBindingFactory()) { viewTypesMapping.size }
|
||||||
|
|
||||||
fun setItems(items : List<GenericListItem>) {
|
fun setItems(items : List<GenericListItem<*>>) {
|
||||||
allItems.clear()
|
allItems.clear()
|
||||||
allItems.addAll(items)
|
allItems.addAll(items)
|
||||||
filter.filter(currentSearchTerm)
|
filter.filter(currentSearchTerm)
|
||||||
@ -68,7 +70,7 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder>(), Filterable {
|
|||||||
*/
|
*/
|
||||||
private val cos = Cosine()
|
private val cos = Cosine()
|
||||||
|
|
||||||
inner class ScoredItem(val score : Double, val item : GenericListItem)
|
inner class ScoredItem(val score : Double, val item : GenericListItem<*>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This sorts the items in [allItems] in relation to how similar they are to [currentSearchTerm]
|
* This sorts the items in [allItems] in relation to how similar they are to [currentSearchTerm]
|
||||||
@ -93,7 +95,7 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder>(), Filterable {
|
|||||||
results.values = allItems.toMutableList()
|
results.values = allItems.toMutableList()
|
||||||
results.count = allItems.size
|
results.count = allItems.size
|
||||||
} else {
|
} else {
|
||||||
val filterData = mutableListOf<GenericListItem>()
|
val filterData = mutableListOf<GenericListItem<*>>()
|
||||||
|
|
||||||
val topResults = extractSorted()
|
val topResults = extractSorted()
|
||||||
val avgScore = topResults.sumByDouble { it.score } / topResults.size
|
val avgScore = topResults.sumByDouble { it.score } / topResults.size
|
||||||
@ -112,7 +114,7 @@ class GenericAdapter : RecyclerView.Adapter<GenericViewHolder>(), Filterable {
|
|||||||
*/
|
*/
|
||||||
override fun publishResults(charSequence : CharSequence, results : FilterResults) {
|
override fun publishResults(charSequence : CharSequence, results : FilterResults) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
asyncListDiffer.submitList(results.values as List<GenericListItem>)
|
asyncListDiffer.submitList(results.values as List<GenericListItem<ViewBinding>>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,33 +5,38 @@
|
|||||||
|
|
||||||
package emu.skyline.adapter
|
package emu.skyline.adapter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
import androidx.viewbinding.ViewBinding
|
||||||
|
|
||||||
class GenericViewHolder(override val containerView : View) : RecyclerView.ViewHolder(containerView), LayoutContainer
|
class GenericViewHolder<out V : ViewBinding>(val binding : V) : RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
interface GenericLayoutFactory {
|
fun View.inflater() = LayoutInflater.from(context)!!
|
||||||
fun createLayout(parent : ViewGroup) : View
|
|
||||||
|
interface ViewBindingFactory {
|
||||||
|
fun createBinding(parent : ViewGroup) : ViewBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class GenericListItem {
|
abstract class GenericListItem<V : ViewBinding> {
|
||||||
var adapter : GenericAdapter? = null
|
var adapter : GenericAdapter? = null
|
||||||
|
|
||||||
abstract fun getLayoutFactory() : GenericLayoutFactory
|
abstract fun getViewBindingFactory() : ViewBindingFactory
|
||||||
|
|
||||||
abstract fun bind(holder : GenericViewHolder, position : Int)
|
abstract fun bind(holder : GenericViewHolder<V>, position : Int)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for filtering
|
* Used for filtering
|
||||||
*/
|
*/
|
||||||
open fun key() : String = ""
|
open fun key() : String = ""
|
||||||
|
|
||||||
open fun areItemsTheSame(other : GenericListItem) = this == other
|
open fun areItemsTheSame(other : GenericListItem<V>) = this == other
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will only be called when [areItemsTheSame] returns true, thus returning true by default
|
* Will only be called when [areItemsTheSame] returns true, thus returning true by default
|
||||||
*/
|
*/
|
||||||
open fun areContentsTheSame(other : GenericListItem) = true
|
open fun areContentsTheSame(other : GenericListItem<V>) = true
|
||||||
|
|
||||||
|
open val fullSpan : Boolean = false
|
||||||
}
|
}
|
||||||
|
@ -5,24 +5,23 @@
|
|||||||
|
|
||||||
package emu.skyline.adapter
|
package emu.skyline.adapter
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import emu.skyline.R
|
import emu.skyline.databinding.SectionItemBinding
|
||||||
import kotlinx.android.synthetic.main.section_item.*
|
|
||||||
|
|
||||||
private object HeaderLayoutFactory : GenericLayoutFactory {
|
object HeaderBindingFactory : ViewBindingFactory {
|
||||||
override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(R.layout.section_item, parent, false)
|
override fun createBinding(parent : ViewGroup) = SectionItemBinding.inflate(parent.inflater(), parent, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
class HeaderViewItem(private val text : String) : GenericListItem() {
|
class HeaderViewItem(private val text : String) : GenericListItem<SectionItemBinding>() {
|
||||||
override fun getLayoutFactory() : GenericLayoutFactory = HeaderLayoutFactory
|
override fun getViewBindingFactory() = HeaderBindingFactory
|
||||||
|
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
override fun bind(holder : GenericViewHolder<SectionItemBinding>, position : Int) {
|
||||||
holder.text_title.text = text
|
holder.binding.textTitle.text = text
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = ""
|
override fun toString() = ""
|
||||||
|
|
||||||
override fun areItemsTheSame(other : GenericListItem) = other is HeaderViewItem && text == other.text
|
override fun areItemsTheSame(other : GenericListItem<SectionItemBinding>) = other is HeaderViewItem && text == other.text
|
||||||
|
|
||||||
|
override val fullSpan = true
|
||||||
}
|
}
|
||||||
|
@ -7,25 +7,51 @@ package emu.skyline.adapter
|
|||||||
|
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import emu.skyline.R
|
import androidx.viewbinding.ViewBinding
|
||||||
import kotlinx.android.synthetic.main.log_item.*
|
import emu.skyline.databinding.LogItemBinding
|
||||||
|
import emu.skyline.databinding.LogItemCompactBinding
|
||||||
|
|
||||||
private data class LogLayoutFactory(private val compact : Boolean) : GenericLayoutFactory {
|
data class LogBindingFactory(private val compact : Boolean) : ViewBindingFactory {
|
||||||
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)
|
override fun createBinding(parent : ViewGroup) = if (compact) LogCompactBinding(parent) else LogBinding(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class LogViewItem(private val compact : Boolean, private val message : String, private val level : String) : GenericListItem() {
|
interface ILogBinding : ViewBinding {
|
||||||
override fun getLayoutFactory() : GenericLayoutFactory = LogLayoutFactory(compact)
|
val binding : ViewBinding
|
||||||
|
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
override fun getRoot() = binding.root
|
||||||
holder.text_title.text = message
|
|
||||||
holder.text_subtitle?.text = level
|
|
||||||
|
|
||||||
holder.itemView.setOnClickListener {
|
val textTitle : TextView
|
||||||
|
|
||||||
|
val textSubTitle : TextView?
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogCompactBinding(parent : ViewGroup) : ILogBinding {
|
||||||
|
override val binding = LogItemCompactBinding.inflate(parent.inflater(), parent, false)
|
||||||
|
|
||||||
|
override val textTitle = binding.textTitle
|
||||||
|
|
||||||
|
override val textSubTitle : Nothing? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogBinding(parent : ViewGroup) : ILogBinding {
|
||||||
|
override val binding = LogItemBinding.inflate(parent.inflater(), parent, false)
|
||||||
|
|
||||||
|
override val textTitle = binding.textTitle
|
||||||
|
|
||||||
|
override val textSubTitle = binding.textSubtitle
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LogViewItem(private val compact : Boolean, private val message : String, private val level : String) : GenericListItem<ILogBinding>() {
|
||||||
|
override fun getViewBindingFactory() = LogBindingFactory(compact)
|
||||||
|
|
||||||
|
override fun bind(holder : GenericViewHolder<ILogBinding>, position : Int) {
|
||||||
|
holder.binding.textTitle.text = message
|
||||||
|
holder.binding.textSubTitle?.text = level
|
||||||
|
|
||||||
|
holder.binding.root.setOnClickListener {
|
||||||
it.context.getSystemService(ClipboardManager::class.java).setPrimaryClip(ClipData.newPlainText("Log Message", "$message ($level)"))
|
it.context.getSystemService(ClipboardManager::class.java).setPrimaryClip(ClipData.newPlainText("Log Message", "$message ($level)"))
|
||||||
Toast.makeText(it.context, "Copied to clipboard", Toast.LENGTH_LONG).show()
|
Toast.makeText(it.context, "Copied to clipboard", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
@ -7,25 +7,26 @@ package emu.skyline.adapter.controller
|
|||||||
|
|
||||||
import emu.skyline.adapter.GenericListItem
|
import emu.skyline.adapter.GenericListItem
|
||||||
import emu.skyline.adapter.GenericViewHolder
|
import emu.skyline.adapter.GenericViewHolder
|
||||||
|
import emu.skyline.databinding.ControllerItemBinding
|
||||||
|
import emu.skyline.di.InputManagerProviderEntryPoint
|
||||||
import emu.skyline.input.ButtonGuestEvent
|
import emu.skyline.input.ButtonGuestEvent
|
||||||
import emu.skyline.input.ButtonId
|
import emu.skyline.input.ButtonId
|
||||||
import emu.skyline.input.InputManager
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This item is used to display a particular [button] mapping for the controller
|
* This item is used to display a particular [button] mapping for the controller
|
||||||
*/
|
*/
|
||||||
class ControllerButtonViewItem(private val controllerId : Int, val button : ButtonId, private val onClick : (item : ControllerButtonViewItem, position : Int) -> Unit) : ControllerViewItem() {
|
class ControllerButtonViewItem(private val controllerId : Int, val button : ButtonId, private val onClick : (item : ControllerButtonViewItem, position : Int) -> Unit) : ControllerViewItem() {
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
|
||||||
content = button.long?.let { holder.itemView.context.getString(it) } ?: button.toString()
|
content = button.long?.let { holder.itemView.context.getString(it) } ?: button.toString()
|
||||||
val guestEvent = ButtonGuestEvent(controllerId, button)
|
val guestEvent = ButtonGuestEvent(controllerId, button)
|
||||||
subContent = InputManager.eventMap.filter { it.value is ButtonGuestEvent && it.value == guestEvent }.keys.firstOrNull()?.toString() ?: ""
|
subContent = InputManagerProviderEntryPoint.getInputManager(holder.binding.root.context).eventMap.filter { it.value is ButtonGuestEvent && it.value == guestEvent }.keys.firstOrNull()?.toString() ?: ""
|
||||||
|
|
||||||
super.bind(holder, position)
|
super.bind(holder, position)
|
||||||
|
|
||||||
holder.itemView.setOnClickListener { onClick.invoke(this, position) }
|
holder.binding.root.setOnClickListener { onClick.invoke(this, position) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areItemsTheSame(other : GenericListItem) = other is ControllerButtonViewItem && controllerId == other.controllerId
|
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerButtonViewItem && controllerId == other.controllerId
|
||||||
|
|
||||||
override fun areContentsTheSame(other : GenericListItem) = other is ControllerButtonViewItem && button == other.button
|
override fun areContentsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerButtonViewItem && button == other.button
|
||||||
}
|
}
|
||||||
|
@ -5,36 +5,34 @@
|
|||||||
|
|
||||||
package emu.skyline.adapter.controller
|
package emu.skyline.adapter.controller
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import emu.skyline.R
|
|
||||||
import emu.skyline.adapter.GenericLayoutFactory
|
|
||||||
import emu.skyline.adapter.GenericListItem
|
import emu.skyline.adapter.GenericListItem
|
||||||
import emu.skyline.adapter.GenericViewHolder
|
import emu.skyline.adapter.GenericViewHolder
|
||||||
import kotlinx.android.synthetic.main.controller_checkbox_item.*
|
import emu.skyline.adapter.ViewBindingFactory
|
||||||
|
import emu.skyline.adapter.inflater
|
||||||
|
import emu.skyline.databinding.ControllerCheckboxItemBinding
|
||||||
|
|
||||||
private object ControllerCheckBoxLayoutFactory : GenericLayoutFactory {
|
object ControllerCheckBoxBindingFactory : ViewBindingFactory {
|
||||||
override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(R.layout.controller_checkbox_item, parent, false)
|
override fun createBinding(parent : ViewGroup) = ControllerCheckboxItemBinding.inflate(parent.inflater(), parent, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ControllerCheckBoxViewItem(var title : String, var summary : String, var checked : Boolean, private val onCheckedChange : (item : ControllerCheckBoxViewItem, position : Int) -> Unit) : GenericListItem() {
|
class ControllerCheckBoxViewItem(var title : String, var summary : String, var checked : Boolean, private val onCheckedChange : (item : ControllerCheckBoxViewItem, position : Int) -> Unit) : GenericListItem<ControllerCheckboxItemBinding>() {
|
||||||
override fun getLayoutFactory() : GenericLayoutFactory = ControllerCheckBoxLayoutFactory
|
override fun getViewBindingFactory() = ControllerCheckBoxBindingFactory
|
||||||
|
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
override fun bind(holder : GenericViewHolder<ControllerCheckboxItemBinding>, position : Int) {
|
||||||
holder.text_title.isGone = title.isEmpty()
|
holder.binding.textTitle.isGone = title.isEmpty()
|
||||||
holder.text_title.text = title
|
holder.binding.textTitle.text = title
|
||||||
holder.text_subtitle.isGone = summary.isEmpty()
|
holder.binding.textSubtitle.isGone = summary.isEmpty()
|
||||||
holder.text_subtitle.text = summary
|
holder.binding.textSubtitle.text = summary
|
||||||
holder.checkbox.isChecked = checked
|
holder.binding.checkbox.isChecked = checked
|
||||||
holder.itemView.setOnClickListener {
|
holder.itemView.setOnClickListener {
|
||||||
checked = !checked
|
checked = !checked
|
||||||
onCheckedChange.invoke(this, position)
|
onCheckedChange.invoke(this, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areItemsTheSame(other : GenericListItem) = other is ControllerCheckBoxViewItem
|
override fun areItemsTheSame(other : GenericListItem<ControllerCheckboxItemBinding>) = other is ControllerCheckBoxViewItem
|
||||||
|
|
||||||
override fun areContentsTheSame(other : GenericListItem) = other is ControllerCheckBoxViewItem && title == other.title && summary == other.summary && checked == other.checked
|
override fun areContentsTheSame(other : GenericListItem<ControllerCheckboxItemBinding>) = other is ControllerCheckBoxViewItem && title == other.title && summary == other.summary && checked == other.checked
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,9 @@ package emu.skyline.adapter.controller
|
|||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.adapter.GenericListItem
|
import emu.skyline.adapter.GenericListItem
|
||||||
import emu.skyline.adapter.GenericViewHolder
|
import emu.skyline.adapter.GenericViewHolder
|
||||||
|
import emu.skyline.databinding.ControllerItemBinding
|
||||||
|
import emu.skyline.di.InputManagerProviderEntryPoint
|
||||||
import emu.skyline.input.GeneralType
|
import emu.skyline.input.GeneralType
|
||||||
import emu.skyline.input.InputManager
|
|
||||||
import emu.skyline.input.JoyConLeftController
|
import emu.skyline.input.JoyConLeftController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,9 +19,9 @@ import emu.skyline.input.JoyConLeftController
|
|||||||
* @param type The type of controller setting this item is displaying
|
* @param type The type of controller setting this item is displaying
|
||||||
*/
|
*/
|
||||||
class ControllerGeneralViewItem(private val controllerId : Int, val type : GeneralType, private val onClick : (item : ControllerGeneralViewItem, position : Int) -> Unit) : ControllerViewItem() {
|
class ControllerGeneralViewItem(private val controllerId : Int, val type : GeneralType, private val onClick : (item : ControllerGeneralViewItem, position : Int) -> Unit) : ControllerViewItem() {
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
|
||||||
val context = holder.itemView.context
|
val context = holder.itemView.context
|
||||||
val controller = InputManager.controllers[controllerId]!!
|
val controller = InputManagerProviderEntryPoint.getInputManager(context).controllers[controllerId]!!
|
||||||
|
|
||||||
content = context.getString(type.stringRes)
|
content = context.getString(type.stringRes)
|
||||||
subContent = when (type) {
|
subContent = when (type) {
|
||||||
@ -40,7 +41,7 @@ class ControllerGeneralViewItem(private val controllerId : Int, val type : Gener
|
|||||||
holder.itemView.setOnClickListener { onClick.invoke(this, position) }
|
holder.itemView.setOnClickListener { onClick.invoke(this, position) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areItemsTheSame(other : GenericListItem) = other is ControllerGeneralViewItem && controllerId == other.controllerId
|
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerGeneralViewItem && controllerId == other.controllerId
|
||||||
|
|
||||||
override fun areContentsTheSame(other : GenericListItem) = other is ControllerGeneralViewItem && type == other.type
|
override fun areContentsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerGeneralViewItem && type == other.type
|
||||||
}
|
}
|
||||||
|
@ -8,32 +8,34 @@ package emu.skyline.adapter.controller
|
|||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.adapter.GenericListItem
|
import emu.skyline.adapter.GenericListItem
|
||||||
import emu.skyline.adapter.GenericViewHolder
|
import emu.skyline.adapter.GenericViewHolder
|
||||||
|
import emu.skyline.databinding.ControllerItemBinding
|
||||||
|
import emu.skyline.di.InputManagerProviderEntryPoint
|
||||||
import emu.skyline.input.AxisGuestEvent
|
import emu.skyline.input.AxisGuestEvent
|
||||||
import emu.skyline.input.ButtonGuestEvent
|
import emu.skyline.input.ButtonGuestEvent
|
||||||
import emu.skyline.input.InputManager
|
|
||||||
import emu.skyline.input.StickId
|
import emu.skyline.input.StickId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This item is used to display all information regarding a [stick] and it's mappings for the controller
|
* This item is used to display all information regarding a [stick] and it's mappings for the controller
|
||||||
*/
|
*/
|
||||||
class ControllerStickViewItem(private val controllerId : Int, val stick : StickId, private val onClick : (item : ControllerStickViewItem, position : Int) -> Unit) : ControllerViewItem(stick.toString()) {
|
class ControllerStickViewItem(private val controllerId : Int, val stick : StickId, private val onClick : (item : ControllerStickViewItem, position : Int) -> Unit) : ControllerViewItem(stick.toString()) {
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
|
||||||
val context = holder.itemView.context
|
val context = holder.itemView.context
|
||||||
|
val inputManager = InputManagerProviderEntryPoint.getInputManager(context)
|
||||||
|
|
||||||
val buttonGuestEvent = ButtonGuestEvent(controllerId, stick.button)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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"
|
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"
|
||||||
|
|
||||||
@ -42,7 +44,7 @@ class ControllerStickViewItem(private val controllerId : Int, val stick : StickI
|
|||||||
holder.itemView.setOnClickListener { onClick.invoke(this, position) }
|
holder.itemView.setOnClickListener { onClick.invoke(this, position) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areItemsTheSame(other : GenericListItem) = other is ControllerStickViewItem && controllerId == other.controllerId
|
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerStickViewItem && controllerId == other.controllerId
|
||||||
|
|
||||||
override fun areContentsTheSame(other : GenericListItem) = other is ControllerStickViewItem && stick == other.stick
|
override fun areContentsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerStickViewItem && stick == other.stick
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,14 @@ package emu.skyline.adapter.controller
|
|||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.adapter.GenericListItem
|
import emu.skyline.adapter.GenericListItem
|
||||||
import emu.skyline.adapter.GenericViewHolder
|
import emu.skyline.adapter.GenericViewHolder
|
||||||
|
import emu.skyline.databinding.ControllerItemBinding
|
||||||
import emu.skyline.input.ControllerType
|
import emu.skyline.input.ControllerType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This item is used to display the [type] of the currently active controller
|
* This item is used to display the [type] of the currently active controller
|
||||||
*/
|
*/
|
||||||
class ControllerTypeViewItem(private val type : ControllerType, private val onClick : (item : ControllerTypeViewItem, position : Int) -> Unit) : ControllerViewItem() {
|
class ControllerTypeViewItem(private val type : ControllerType, private val onClick : (item : ControllerTypeViewItem, position : Int) -> Unit) : ControllerViewItem() {
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
|
||||||
val context = holder.itemView.context
|
val context = holder.itemView.context
|
||||||
|
|
||||||
content = context.getString(R.string.controller_type)
|
content = context.getString(R.string.controller_type)
|
||||||
@ -25,7 +26,7 @@ class ControllerTypeViewItem(private val type : ControllerType, private val onCl
|
|||||||
holder.itemView.setOnClickListener { onClick.invoke(this, position) }
|
holder.itemView.setOnClickListener { onClick.invoke(this, position) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areItemsTheSame(other : GenericListItem) = other is ControllerTypeViewItem
|
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerTypeViewItem
|
||||||
|
|
||||||
override fun areContentsTheSame(other : GenericListItem) = other is ControllerTypeViewItem && type == other.type
|
override fun areContentsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerTypeViewItem && type == other.type
|
||||||
}
|
}
|
||||||
|
@ -5,32 +5,31 @@
|
|||||||
|
|
||||||
package emu.skyline.adapter.controller
|
package emu.skyline.adapter.controller
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import emu.skyline.R
|
|
||||||
import emu.skyline.adapter.GenericLayoutFactory
|
|
||||||
import emu.skyline.adapter.GenericListItem
|
import emu.skyline.adapter.GenericListItem
|
||||||
import emu.skyline.adapter.GenericViewHolder
|
import emu.skyline.adapter.GenericViewHolder
|
||||||
import kotlinx.android.synthetic.main.controller_item.*
|
import emu.skyline.adapter.ViewBindingFactory
|
||||||
|
import emu.skyline.adapter.inflater
|
||||||
|
import emu.skyline.databinding.ControllerItemBinding
|
||||||
|
import emu.skyline.input.InputManager
|
||||||
|
|
||||||
private object ControllerLayoutFactory : GenericLayoutFactory {
|
object ControllerBindingFactory : ViewBindingFactory {
|
||||||
override fun createLayout(parent : ViewGroup) : View = LayoutInflater.from(parent.context).inflate(R.layout.controller_item, parent, false)
|
override fun createBinding(parent : ViewGroup) = ControllerItemBinding.inflate(parent.inflater(), parent, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
open class ControllerViewItem(var content : String = "", var subContent : String = "", private val onClick : (() -> Unit)? = null) : GenericListItem() {
|
open class ControllerViewItem(var content : String = "", var subContent : String = "", private val onClick : (() -> Unit)? = null) : GenericListItem<ControllerItemBinding>() {
|
||||||
private var position = -1
|
private var position = -1
|
||||||
|
|
||||||
override fun getLayoutFactory() : GenericLayoutFactory = ControllerLayoutFactory
|
override fun getViewBindingFactory() = ControllerBindingFactory
|
||||||
|
|
||||||
override fun bind(holder : GenericViewHolder, position : Int) {
|
override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
|
||||||
this.position = position
|
this.position = position
|
||||||
holder.text_title.apply {
|
holder.binding.textTitle.apply {
|
||||||
isGone = content.isEmpty()
|
isGone = content.isEmpty()
|
||||||
text = content
|
text = content
|
||||||
}
|
}
|
||||||
holder.text_subtitle.apply {
|
holder.binding.textSubtitle.apply {
|
||||||
isGone = subContent.isEmpty()
|
isGone = subContent.isEmpty()
|
||||||
text = subContent
|
text = subContent
|
||||||
}
|
}
|
||||||
@ -39,7 +38,7 @@ open class ControllerViewItem(var content : String = "", var subContent : String
|
|||||||
|
|
||||||
fun update() = adapter?.notifyItemChanged(position)
|
fun update() = adapter?.notifyItemChanged(position)
|
||||||
|
|
||||||
override fun areItemsTheSame(other : GenericListItem) = other is ControllerViewItem
|
override fun areItemsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerViewItem
|
||||||
|
|
||||||
override fun areContentsTheSame(other : GenericListItem) = other is ControllerViewItem && content == other.content && subContent == other.subContent
|
override fun areContentsTheSame(other : GenericListItem<ControllerItemBinding>) = other is ControllerViewItem && content == other.content && subContent == other.subContent
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package emu.skyline.di
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.hilt.EntryPoint
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.EntryPointAccessors
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import emu.skyline.input.InputManager
|
||||||
|
|
||||||
|
@EntryPoint
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
interface InputManagerProviderEntryPoint {
|
||||||
|
fun inputManager() : InputManager
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getInputManager(context : Context) = EntryPointAccessors.fromApplication(context, InputManagerProviderEntryPoint::class.java).inputManager()
|
||||||
|
}
|
||||||
|
}
|
@ -12,23 +12,27 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.adapter.GenericAdapter
|
import emu.skyline.adapter.GenericAdapter
|
||||||
import emu.skyline.adapter.GenericListItem
|
import emu.skyline.adapter.GenericListItem
|
||||||
import emu.skyline.adapter.HeaderViewItem
|
import emu.skyline.adapter.HeaderViewItem
|
||||||
import emu.skyline.adapter.controller.*
|
import emu.skyline.adapter.controller.*
|
||||||
|
import emu.skyline.databinding.ControllerActivityBinding
|
||||||
import emu.skyline.input.dialog.ButtonDialog
|
import emu.skyline.input.dialog.ButtonDialog
|
||||||
import emu.skyline.input.dialog.RumbleDialog
|
import emu.skyline.input.dialog.RumbleDialog
|
||||||
import emu.skyline.input.dialog.StickDialog
|
import emu.skyline.input.dialog.StickDialog
|
||||||
import emu.skyline.input.onscreen.OnScreenEditActivity
|
import emu.skyline.input.onscreen.OnScreenEditActivity
|
||||||
import emu.skyline.utils.Settings
|
import emu.skyline.utils.Settings
|
||||||
import kotlinx.android.synthetic.main.controller_activity.*
|
import javax.inject.Inject
|
||||||
import kotlinx.android.synthetic.main.titlebar.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This activity is used to change the settings for a specific controller
|
* This activity is used to change the settings for a specific controller
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class ControllerActivity : AppCompatActivity() {
|
class ControllerActivity : AppCompatActivity() {
|
||||||
|
private val binding by lazy { ControllerActivityBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The index of the controller this activity manages
|
* The index of the controller this activity manages
|
||||||
*/
|
*/
|
||||||
@ -51,14 +55,17 @@ class ControllerActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private val settings by lazy { Settings(this) }
|
private val settings by lazy { Settings(this) }
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var inputManager : InputManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function updates the [adapter] based on information from [InputManager]
|
* This function updates the [adapter] based on information from [InputManager]
|
||||||
*/
|
*/
|
||||||
private fun update() {
|
private fun update() {
|
||||||
val items = mutableListOf<GenericListItem>()
|
val items = mutableListOf<GenericListItem<*>>()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val controller = InputManager.controllers[id]!!
|
val controller = inputManager.controllers[id]!!
|
||||||
|
|
||||||
items.add(ControllerTypeViewItem(controller.type, onControllerTypeClick))
|
items.add(ControllerTypeViewItem(controller.type, onControllerTypeClick))
|
||||||
|
|
||||||
@ -166,20 +173,20 @@ class ControllerActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
title = "${getString(R.string.config_controller)} #${id + 1}"
|
title = "${getString(R.string.config_controller)} #${id + 1}"
|
||||||
|
|
||||||
setContentView(R.layout.controller_activity)
|
setContentView(binding.root)
|
||||||
|
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(binding.titlebar.toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
val layoutManager = LinearLayoutManager(this)
|
val layoutManager = LinearLayoutManager(this)
|
||||||
controller_list.layoutManager = layoutManager
|
binding.controllerList.layoutManager = layoutManager
|
||||||
controller_list.adapter = adapter
|
binding.controllerList.adapter = adapter
|
||||||
|
|
||||||
controller_list.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
binding.controllerList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView : RecyclerView, dx : Int, dy : Int) {
|
override fun onScrolled(recyclerView : RecyclerView, dx : Int, dy : Int) {
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
if (layoutManager.findLastCompletelyVisibleItemPosition() == adapter.itemCount - 1) app_bar_layout.setExpanded(false)
|
if (layoutManager.findLastCompletelyVisibleItemPosition() == adapter.itemCount - 1) binding.titlebar.appBarLayout.setExpanded(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -190,12 +197,12 @@ class ControllerActivity : AppCompatActivity() {
|
|||||||
* This causes the input file to be synced when the activity has been paused
|
* This causes the input file to be synced when the activity has been paused
|
||||||
*/
|
*/
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
InputManager.syncFile()
|
inputManager.syncFile()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onControllerTypeClick = { item : ControllerTypeViewItem, _ : Int ->
|
private val onControllerTypeClick = { item : ControllerTypeViewItem, _ : Int ->
|
||||||
val controller = InputManager.controllers[id]!!
|
val controller = inputManager.controllers[id]!!
|
||||||
|
|
||||||
val types = ControllerType.values().apply { if (id != 0) filter { !it.firstController } }
|
val types = ControllerType.values().apply { if (id != 0) filter { !it.firstController } }
|
||||||
val typeNames = types.map { getString(it.stringRes) }.toTypedArray()
|
val typeNames = types.map { getString(it.stringRes) }.toTypedArray()
|
||||||
@ -206,11 +213,11 @@ class ControllerActivity : AppCompatActivity() {
|
|||||||
val selectedType = types[typeIndex]
|
val selectedType = types[typeIndex]
|
||||||
if (controller.type != selectedType) {
|
if (controller.type != selectedType) {
|
||||||
if (controller is JoyConLeftController)
|
if (controller is JoyConLeftController)
|
||||||
controller.partnerId?.let { (InputManager.controllers[it] as JoyConRightController).partnerId = null }
|
controller.partnerId?.let { (inputManager.controllers[it] as JoyConRightController).partnerId = null }
|
||||||
else if (controller is JoyConRightController)
|
else if (controller is JoyConRightController)
|
||||||
controller.partnerId?.let { (InputManager.controllers[it] as JoyConLeftController).partnerId = null }
|
controller.partnerId?.let { (inputManager.controllers[it] as JoyConLeftController).partnerId = null }
|
||||||
|
|
||||||
InputManager.controllers[id] = when (selectedType) {
|
inputManager.controllers[id] = when (selectedType) {
|
||||||
ControllerType.None -> Controller(id, ControllerType.None)
|
ControllerType.None -> Controller(id, ControllerType.None)
|
||||||
ControllerType.HandheldProController -> HandheldController(id)
|
ControllerType.HandheldProController -> HandheldController(id)
|
||||||
ControllerType.ProController -> ProController(id)
|
ControllerType.ProController -> ProController(id)
|
||||||
@ -230,9 +237,9 @@ class ControllerActivity : AppCompatActivity() {
|
|||||||
private val onControllerGeneralClick = { item : ControllerGeneralViewItem, _ : Int ->
|
private val onControllerGeneralClick = { item : ControllerGeneralViewItem, _ : Int ->
|
||||||
when (item.type) {
|
when (item.type) {
|
||||||
GeneralType.PartnerJoyCon -> {
|
GeneralType.PartnerJoyCon -> {
|
||||||
val controller = InputManager.controllers[id] as JoyConLeftController
|
val controller = inputManager.controllers[id] as JoyConLeftController
|
||||||
|
|
||||||
val rJoyCons = InputManager.controllers.values.filter { it.type == ControllerType.JoyConRight }
|
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 rJoyConNames = (listOf(getString(R.string.none)) + rJoyCons.map { "${getString(R.string.controller)} #${it.id + 1}" }).toTypedArray()
|
||||||
|
|
||||||
val partnerNameIndex = controller.partnerId?.let { partnerId ->
|
val partnerNameIndex = controller.partnerId?.let { partnerId ->
|
||||||
@ -242,12 +249,12 @@ class ControllerActivity : AppCompatActivity() {
|
|||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(item.content)
|
.setTitle(item.content)
|
||||||
.setSingleChoiceItems(rJoyConNames, partnerNameIndex) { dialog, index ->
|
.setSingleChoiceItems(rJoyConNames, partnerNameIndex) { dialog, index ->
|
||||||
(InputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = null
|
(inputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = null
|
||||||
|
|
||||||
controller.partnerId = if (index == 0) null else rJoyCons[index - 1].id
|
controller.partnerId = if (index == 0) null else rJoyCons[index - 1].id
|
||||||
|
|
||||||
if (controller.partnerId != null)
|
if (controller.partnerId != null)
|
||||||
(InputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = controller.id
|
(inputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = controller.id
|
||||||
|
|
||||||
item.update()
|
item.update()
|
||||||
|
|
||||||
|
@ -7,16 +7,20 @@ package emu.skyline.input
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This object is used to manage all transactions with storing/retrieving data in relation to input
|
* This object is used to manage all transactions with storing/retrieving data in relation to input
|
||||||
*/
|
*/
|
||||||
object InputManager {
|
@Singleton
|
||||||
|
class InputManager @Inject constructor(@ApplicationContext context : Context) {
|
||||||
/**
|
/**
|
||||||
* The underlying [File] object with the input data
|
* The underlying [File] object with the input data
|
||||||
*/
|
*/
|
||||||
private lateinit var file : File
|
private val file = File("${context.applicationInfo.dataDir}/input.bin")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HashMap] of all the controllers that contains their metadata
|
* A [HashMap] of all the controllers that contains their metadata
|
||||||
@ -28,31 +32,31 @@ object InputManager {
|
|||||||
*/
|
*/
|
||||||
lateinit var eventMap : HashMap<HostEvent?, GuestEvent?>
|
lateinit var eventMap : HashMap<HostEvent?, GuestEvent?>
|
||||||
|
|
||||||
fun init(context : Context) {
|
init {
|
||||||
file = File("${context.applicationInfo.dataDir}/input.bin")
|
run {
|
||||||
|
try {
|
||||||
try {
|
if (file.exists() && file.length() != 0L) {
|
||||||
if (file.exists() && file.length() != 0L) {
|
syncObjects()
|
||||||
syncObjects()
|
return@run
|
||||||
return
|
}
|
||||||
|
} catch (e : Exception) {
|
||||||
|
Log.e(this.toString(), e.localizedMessage ?: "InputManager cannot read \"${file.absolutePath}\"")
|
||||||
}
|
}
|
||||||
} catch (e : Exception) {
|
|
||||||
Log.e(this.toString(), e.localizedMessage ?: "InputManager cannot read \"${file.absolutePath}\"")
|
controllers = hashMapOf(
|
||||||
|
0 to Controller(0, ControllerType.HandheldProController),
|
||||||
|
1 to Controller(1, ControllerType.None),
|
||||||
|
2 to Controller(2, ControllerType.None),
|
||||||
|
3 to Controller(3, ControllerType.None),
|
||||||
|
4 to Controller(4, ControllerType.None),
|
||||||
|
5 to Controller(5, ControllerType.None),
|
||||||
|
6 to Controller(6, ControllerType.None),
|
||||||
|
7 to Controller(7, ControllerType.None))
|
||||||
|
|
||||||
|
eventMap = hashMapOf()
|
||||||
|
|
||||||
|
syncFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
controllers = hashMapOf(
|
|
||||||
0 to Controller(0, ControllerType.HandheldProController),
|
|
||||||
1 to Controller(1, ControllerType.None),
|
|
||||||
2 to Controller(2, ControllerType.None),
|
|
||||||
3 to Controller(3, ControllerType.None),
|
|
||||||
4 to Controller(4, ControllerType.None),
|
|
||||||
5 to Controller(5, ControllerType.None),
|
|
||||||
6 to Controller(6, ControllerType.None),
|
|
||||||
7 to Controller(7, ControllerType.None))
|
|
||||||
|
|
||||||
eventMap = hashMapOf()
|
|
||||||
|
|
||||||
syncFile()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,10 +13,12 @@ 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.ControllerButtonViewItem
|
import emu.skyline.adapter.controller.ControllerButtonViewItem
|
||||||
|
import emu.skyline.databinding.ButtonDialogBinding
|
||||||
import emu.skyline.input.*
|
import emu.skyline.input.*
|
||||||
import kotlinx.android.synthetic.main.button_dialog.*
|
import javax.inject.Inject
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,11 +26,17 @@ import kotlin.math.abs
|
|||||||
*
|
*
|
||||||
* @param item This is used to hold the [ControllerButtonViewItem] between instances
|
* @param item This is used to hold the [ControllerButtonViewItem] between instances
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class ButtonDialog @JvmOverloads constructor(private val item : ControllerButtonViewItem? = null) : BottomSheetDialogFragment() {
|
class ButtonDialog @JvmOverloads constructor(private val item : ControllerButtonViewItem? = null) : BottomSheetDialogFragment() {
|
||||||
|
private lateinit var binding : ButtonDialogBinding
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var inputManager : InputManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This inflates the layout of the dialog after initial view creation
|
* This inflates the layout of the dialog after initial view creation
|
||||||
*/
|
*/
|
||||||
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) : View? = inflater.inflate(R.layout.button_dialog, container)
|
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) = ButtonDialogBinding.inflate(inflater).also { binding = it }.root
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This expands the bottom sheet so that it's fully visible
|
* This expands the bottom sheet so that it's fully visible
|
||||||
@ -48,20 +56,20 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
|
|
||||||
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
|
||||||
button_text.text = item.button.short ?: item.button.toString()
|
binding.buttonText.text = item.button.short ?: item.button.toString()
|
||||||
|
|
||||||
// Set up the reset button to clear out all entries corresponding to this button from [InputManager.eventMap]
|
// Set up the reset button to clear out all entries corresponding to this button from [inputManager.eventMap]
|
||||||
button_reset.setOnClickListener {
|
binding.buttonReset.setOnClickListener {
|
||||||
val guestEvent = ButtonGuestEvent(context.id, item.button)
|
val guestEvent = ButtonGuestEvent(context.id, item.button)
|
||||||
|
|
||||||
InputManager.eventMap.filterValues { it is ButtonGuestEvent && it == guestEvent }.keys.forEach { InputManager.eventMap.remove(it) }
|
inputManager.eventMap.filterValues { it is ButtonGuestEvent && it == guestEvent }.keys.forEach { inputManager.eventMap.remove(it) }
|
||||||
|
|
||||||
item.update()
|
item.update()
|
||||||
|
|
||||||
@ -69,11 +77,11 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that layout animations are proper
|
// Ensure that layout animations are proper
|
||||||
button_layout.layoutTransition.setAnimateParentHierarchy(false)
|
binding.buttonLayout.layoutTransition.setAnimateParentHierarchy(false)
|
||||||
button_layout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
binding.buttonLayout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||||
|
|
||||||
// We want the secondary progress bar to be visible through the first one
|
// We want the secondary progress bar to be visible through the first one
|
||||||
button_seekbar.progressDrawable.alpha = 128
|
binding.buttonSeekbar.progressDrawable.alpha = 128
|
||||||
|
|
||||||
var deviceId : Int? = null // The ID of the currently selected device
|
var deviceId : Int? = null // The ID of the currently selected device
|
||||||
var inputId : Int? = null // The key code/axis ID of the currently selected event
|
var inputId : Int? = null // The key code/axis ID of the currently selected event
|
||||||
@ -99,19 +107,19 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
axisRunnable = null
|
axisRunnable = null
|
||||||
}
|
}
|
||||||
|
|
||||||
button_icon.animate().alpha(0.75f).setDuration(50).start()
|
binding.buttonIcon.animate().alpha(0.75f).setDuration(50).start()
|
||||||
button_text.animate().alpha(0.9f).setDuration(50).start()
|
binding.buttonText.animate().alpha(0.9f).setDuration(50).start()
|
||||||
|
|
||||||
button_title.text = getString(R.string.release_confirm)
|
binding.buttonTitle.text = getString(R.string.release_confirm)
|
||||||
button_seekbar.visibility = View.GONE
|
binding.buttonSeekbar.visibility = View.GONE
|
||||||
} else if (deviceId == event.deviceId && inputId == event.keyCode && event.action == KeyEvent.ACTION_UP) {
|
} else if (deviceId == event.deviceId && inputId == event.keyCode && event.action == KeyEvent.ACTION_UP) {
|
||||||
// We serialize the current [deviceId] and [inputId] into a [KeyHostEvent] and map it to a corresponding [GuestEvent] on [KeyEvent.ACTION_UP]
|
// We serialize the current [deviceId] and [inputId] into a [KeyHostEvent] and map it to a corresponding [GuestEvent] on [KeyEvent.ACTION_UP]
|
||||||
val hostEvent = KeyHostEvent(event.device.descriptor, event.keyCode)
|
val hostEvent = KeyHostEvent(event.device.descriptor, event.keyCode)
|
||||||
|
|
||||||
var guestEvent = InputManager.eventMap[hostEvent]
|
var guestEvent = inputManager.eventMap[hostEvent]
|
||||||
|
|
||||||
if (guestEvent is GuestEvent) {
|
if (guestEvent is GuestEvent) {
|
||||||
InputManager.eventMap.remove(hostEvent)
|
inputManager.eventMap.remove(hostEvent)
|
||||||
|
|
||||||
if (guestEvent is ButtonGuestEvent)
|
if (guestEvent is ButtonGuestEvent)
|
||||||
context.buttonMap[guestEvent.button]?.update()
|
context.buttonMap[guestEvent.button]?.update()
|
||||||
@ -121,9 +129,9 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
|
|
||||||
guestEvent = ButtonGuestEvent(context.id, item.button)
|
guestEvent = ButtonGuestEvent(context.id, item.button)
|
||||||
|
|
||||||
InputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { InputManager.eventMap.remove(it) }
|
inputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { inputManager.eventMap.remove(it) }
|
||||||
|
|
||||||
InputManager.eventMap[hostEvent] = guestEvent
|
inputManager.eventMap[hostEvent] = guestEvent
|
||||||
|
|
||||||
item.update()
|
item.update()
|
||||||
|
|
||||||
@ -165,8 +173,8 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
inputId = axis
|
inputId = axis
|
||||||
axisPolarity = value >= 0
|
axisPolarity = value >= 0
|
||||||
|
|
||||||
button_title.text = getString(R.string.hold_confirm)
|
binding.buttonTitle.text = getString(R.string.hold_confirm)
|
||||||
button_seekbar.visibility = View.VISIBLE
|
binding.buttonSeekbar.visibility = View.VISIBLE
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -175,10 +183,10 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
// If the currently active input is a valid axis
|
// If the currently active input is a valid axis
|
||||||
if (axes.contains(inputId)) {
|
if (axes.contains(inputId)) {
|
||||||
val value = event.getAxisValue(inputId!!)
|
val value = event.getAxisValue(inputId!!)
|
||||||
val threshold = button_seekbar.progress / 100f
|
val threshold = binding.buttonSeekbar.progress / 100f
|
||||||
|
|
||||||
// Update the secondary progress bar in [button_seekbar] based on the axis's value
|
// Update the secondary progress bar in [button_seekbar] based on the axis's value
|
||||||
button_seekbar.secondaryProgress = (abs(value) * 100).toInt()
|
binding.buttonSeekbar.secondaryProgress = (abs(value) * 100).toInt()
|
||||||
|
|
||||||
// If the axis value crosses the threshold then post [axisRunnable] with a delay and animate the views accordingly
|
// If the axis value crosses the threshold then post [axisRunnable] with a delay and animate the views accordingly
|
||||||
if (abs(value) >= threshold) {
|
if (abs(value) >= threshold) {
|
||||||
@ -186,10 +194,10 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
axisRunnable = Runnable {
|
axisRunnable = Runnable {
|
||||||
val hostEvent = MotionHostEvent(event.device.descriptor, inputId!!, axisPolarity)
|
val hostEvent = MotionHostEvent(event.device.descriptor, inputId!!, axisPolarity)
|
||||||
|
|
||||||
var guestEvent = InputManager.eventMap[hostEvent]
|
var guestEvent = inputManager.eventMap[hostEvent]
|
||||||
|
|
||||||
if (guestEvent is GuestEvent) {
|
if (guestEvent is GuestEvent) {
|
||||||
InputManager.eventMap.remove(hostEvent)
|
inputManager.eventMap.remove(hostEvent)
|
||||||
|
|
||||||
if (guestEvent is ButtonGuestEvent)
|
if (guestEvent is ButtonGuestEvent)
|
||||||
context.buttonMap[(guestEvent as ButtonGuestEvent).button]?.update()
|
context.buttonMap[(guestEvent as ButtonGuestEvent).button]?.update()
|
||||||
@ -199,9 +207,9 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
|
|
||||||
guestEvent = ButtonGuestEvent(controller.id, item.button, threshold)
|
guestEvent = ButtonGuestEvent(controller.id, item.button, threshold)
|
||||||
|
|
||||||
InputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { InputManager.eventMap.remove(it) }
|
inputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { inputManager.eventMap.remove(it) }
|
||||||
|
|
||||||
InputManager.eventMap[hostEvent] = guestEvent
|
inputManager.eventMap[hostEvent] = guestEvent
|
||||||
|
|
||||||
item.update()
|
item.update()
|
||||||
|
|
||||||
@ -211,8 +219,8 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
axisHandler.postDelayed(axisRunnable!!, 1000)
|
axisHandler.postDelayed(axisRunnable!!, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
button_icon.animate().alpha(0.85f).setInterpolator(LinearInterpolator(context, null)).setDuration(1000).start()
|
binding.buttonIcon.animate().alpha(0.85f).setInterpolator(LinearInterpolator(context, null)).setDuration(1000).start()
|
||||||
button_text.animate().alpha(0.95f).setInterpolator(LinearInterpolator(context, null)).setDuration(1000).start()
|
binding.buttonText.animate().alpha(0.95f).setInterpolator(LinearInterpolator(context, null)).setDuration(1000).start()
|
||||||
} else {
|
} else {
|
||||||
// If the axis value is below the threshold, remove [axisRunnable] from it being posted and animate the views accordingly
|
// If the axis value is below the threshold, remove [axisRunnable] from it being posted and animate the views accordingly
|
||||||
if (axisRunnable != null) {
|
if (axisRunnable != null) {
|
||||||
@ -220,8 +228,8 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
|
|||||||
axisRunnable = null
|
axisRunnable = null
|
||||||
}
|
}
|
||||||
|
|
||||||
button_icon.animate().alpha(0.25f).setDuration(50).start()
|
binding.buttonIcon.animate().alpha(0.25f).setDuration(50).start()
|
||||||
button_text.animate().alpha(0.35f).setDuration(50).start()
|
binding.buttonText.animate().alpha(0.35f).setDuration(50).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,22 +14,30 @@ 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.ControllerGeneralViewItem
|
import emu.skyline.adapter.controller.ControllerGeneralViewItem
|
||||||
|
import emu.skyline.databinding.RumbleDialogBinding
|
||||||
import emu.skyline.input.ControllerActivity
|
import emu.skyline.input.ControllerActivity
|
||||||
import emu.skyline.input.InputManager
|
import emu.skyline.input.InputManager
|
||||||
import kotlinx.android.synthetic.main.rumble_dialog.*
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This dialog is used to set a device to pass on any rumble/force feedback data onto
|
* 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 [ControllerGeneralViewItem] between instances
|
* @param item This is used to hold the [ControllerGeneralViewItem] between instances
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralViewItem? = null) : BottomSheetDialogFragment() {
|
class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralViewItem? = null) : BottomSheetDialogFragment() {
|
||||||
|
private lateinit var binding : RumbleDialogBinding
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var inputManager : InputManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This inflates the layout of the dialog after initial view creation
|
* This inflates the layout of the dialog after initial view creation
|
||||||
*/
|
*/
|
||||||
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) : View? = inflater.inflate(R.layout.rumble_dialog, container)
|
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) = RumbleDialogBinding.inflate(inflater).also { binding = it }.root
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This expands the bottom sheet so that it's fully visible
|
* This expands the bottom sheet so that it's fully visible
|
||||||
@ -49,10 +57,10 @@ class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralViewIte
|
|||||||
|
|
||||||
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]!!
|
||||||
|
|
||||||
// Set up the reset button to clear out [Controller.rumbleDevice] when pressed
|
// Set up the reset button to clear out [Controller.rumbleDevice] when pressed
|
||||||
rumble_reset.setOnClickListener {
|
binding.rumbleReset.setOnClickListener {
|
||||||
controller.rumbleDeviceDescriptor = null
|
controller.rumbleDeviceDescriptor = null
|
||||||
controller.rumbleDeviceName = null
|
controller.rumbleDeviceName = null
|
||||||
item.update()
|
item.update()
|
||||||
@ -61,10 +69,10 @@ class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralViewIte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.id == 0) {
|
if (context.id == 0) {
|
||||||
rumble_builtin.visibility = View.VISIBLE
|
binding.rumbleBuiltin.visibility = View.VISIBLE
|
||||||
if (!(context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator).hasVibrator())
|
if (!(context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator).hasVibrator())
|
||||||
rumble_builtin.isEnabled = false
|
binding.rumbleBuiltin.isEnabled = false
|
||||||
rumble_builtin.setOnClickListener {
|
binding.rumbleBuiltin.setOnClickListener {
|
||||||
controller.rumbleDeviceDescriptor = "builtin"
|
controller.rumbleDeviceDescriptor = "builtin"
|
||||||
controller.rumbleDeviceName = getString(R.string.builtin_vibrator)
|
controller.rumbleDeviceName = getString(R.string.builtin_vibrator)
|
||||||
item.update()
|
item.update()
|
||||||
@ -74,8 +82,8 @@ class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralViewIte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that layout animations are proper
|
// Ensure that layout animations are proper
|
||||||
rumble_layout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
binding.rumbleLayout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||||
rumble_controller.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
binding.rumbleController.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||||
|
|
||||||
var deviceId : Int? = null // The ID of the currently selected device
|
var deviceId : Int? = null // The ID of the currently selected device
|
||||||
|
|
||||||
@ -88,20 +96,20 @@ class RumbleDialog @JvmOverloads constructor(val item : ControllerGeneralViewIte
|
|||||||
when {
|
when {
|
||||||
// If the device doesn't match the currently selected device then update the UI accordingly and set [deviceId] to the current device
|
// If the device doesn't match the currently selected device then update the UI accordingly and set [deviceId] to the current device
|
||||||
deviceId != event.deviceId -> {
|
deviceId != event.deviceId -> {
|
||||||
rumble_controller_name.text = event.device.name
|
binding.rumbleControllerName.text = event.device.name
|
||||||
|
|
||||||
if (vibrator.hasVibrator()) {
|
if (vibrator.hasVibrator()) {
|
||||||
rumble_controller_supported.text = getString(R.string.supported)
|
binding.rumbleControllerSupported.text = getString(R.string.supported)
|
||||||
rumble_title.text = getString(R.string.confirm_button_again)
|
binding.rumbleTitle.text = getString(R.string.confirm_button_again)
|
||||||
|
|
||||||
vibrator.vibrate(VibrationEffect.createOneShot(250, VibrationEffect.DEFAULT_AMPLITUDE))
|
vibrator.vibrate(VibrationEffect.createOneShot(250, VibrationEffect.DEFAULT_AMPLITUDE))
|
||||||
} else {
|
} else {
|
||||||
rumble_controller_supported.text = getString(R.string.not_supported)
|
binding.rumbleControllerSupported.text = getString(R.string.not_supported)
|
||||||
dialog?.setOnKeyListener { _, _, _ -> false }
|
dialog?.setOnKeyListener { _, _, _ -> false }
|
||||||
rumble_reset.requestFocus()
|
binding.rumbleReset.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
rumble_controller_icon.animate().apply {
|
binding.rumbleControllerIcon.animate().apply {
|
||||||
interpolator = LinearInterpolator()
|
interpolator = LinearInterpolator()
|
||||||
duration = 100
|
duration = 100
|
||||||
alpha(if (vibrator.hasVibrator()) 0.75f else 0.5f)
|
alpha(if (vibrator.hasVibrator()) 0.75f else 0.5f)
|
||||||
|
@ -14,12 +14,14 @@ 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.input.*
|
import emu.skyline.input.*
|
||||||
import emu.skyline.input.MotionHostEvent.Companion.axes
|
import emu.skyline.input.MotionHostEvent.Companion.axes
|
||||||
import kotlinx.android.synthetic.main.stick_dialog.*
|
|
||||||
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
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ import kotlin.math.max
|
|||||||
*
|
*
|
||||||
* @param item This is used to hold the [ControllerStickViewItem] between instances
|
* @param item This is used to hold the [ControllerStickViewItem] between instances
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem? = null) : BottomSheetDialogFragment() {
|
class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem? = null) : BottomSheetDialogFragment() {
|
||||||
/**
|
/**
|
||||||
* This enumerates all of the stages this dialog can be in
|
* This enumerates all of the stages this dialog can be in
|
||||||
@ -41,6 +44,8 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
Stick(R.string.stick_preview);
|
Stick(R.string.stick_preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var binding : StickDialogBinding
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the current stage of the dialog
|
* This is the current stage of the dialog
|
||||||
*/
|
*/
|
||||||
@ -61,12 +66,13 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
*/
|
*/
|
||||||
private var animationStop = false
|
private var animationStop = false
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var inputManager : InputManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This inflates the layout of the dialog after initial view creation
|
* This inflates the layout of the dialog after initial view creation
|
||||||
*/
|
*/
|
||||||
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) : View? {
|
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) = StickDialogBinding.inflate(inflater).also { binding = it }.root
|
||||||
return requireActivity().layoutInflater.inflate(R.layout.stick_dialog, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This expands the bottom sheet so that it's fully visible
|
* This expands the bottom sheet so that it's fully visible
|
||||||
@ -91,7 +97,13 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
animationStop = false
|
animationStop = false
|
||||||
stageAnimation?.let { handler.removeCallbacks(it) }
|
stageAnimation?.let { handler.removeCallbacks(it) }
|
||||||
|
|
||||||
stick_container?.animate()?.scaleX(1f)?.scaleY(1f)?.alpha(1f)?.translationY(0f)?.translationX(0f)?.rotationX(0f)?.rotationY(0f)?.start()
|
binding.stickContainer.animate()
|
||||||
|
.scaleX(1f).scaleY(1f)
|
||||||
|
.alpha(1f)
|
||||||
|
.translationY(0f).translationX(0f)
|
||||||
|
.rotationX(0f)
|
||||||
|
.rotationY(0f)
|
||||||
|
.start()
|
||||||
|
|
||||||
when (stage) {
|
when (stage) {
|
||||||
DialogStage.Button -> {
|
DialogStage.Button -> {
|
||||||
@ -99,7 +111,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
if (stage != DialogStage.Button || animationStop)
|
if (stage != DialogStage.Button || animationStop)
|
||||||
return@Runnable
|
return@Runnable
|
||||||
|
|
||||||
stick_container?.animate()?.scaleX(0.85f)?.scaleY(0.85f)?.alpha(1f)?.withEndAction {
|
binding.stickContainer.animate().scaleX(0.85f).scaleY(0.85f).alpha(1f).withEndAction {
|
||||||
if (stage != DialogStage.Button || animationStop)
|
if (stage != DialogStage.Button || animationStop)
|
||||||
return@withEndAction
|
return@withEndAction
|
||||||
|
|
||||||
@ -107,7 +119,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
if (stage != DialogStage.Button || animationStop)
|
if (stage != DialogStage.Button || animationStop)
|
||||||
return@Runnable
|
return@Runnable
|
||||||
|
|
||||||
stick_container?.animate()?.scaleX(1f)?.scaleY(1f)?.alpha(0.85f)?.withEndAction {
|
binding.stickContainer.animate().scaleX(1f).scaleY(1f).alpha(0.85f).withEndAction {
|
||||||
if (stage != DialogStage.Button || animationStop)
|
if (stage != DialogStage.Button || animationStop)
|
||||||
return@withEndAction
|
return@withEndAction
|
||||||
|
|
||||||
@ -129,7 +141,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
if ((stage != DialogStage.YPlus && stage != DialogStage.YMinus) || animationStop)
|
if ((stage != DialogStage.YPlus && stage != DialogStage.YMinus) || animationStop)
|
||||||
return@Runnable
|
return@Runnable
|
||||||
|
|
||||||
stick_container?.animate()?.setDuration(300)?.translationY(dipToPixels(15f) * polarity)?.rotationX(27f * polarity)?.alpha(1f)?.withEndAction {
|
binding.stickContainer.animate().setDuration(300).translationY(dipToPixels(15f) * polarity).rotationX(27f * polarity).alpha(1f).withEndAction {
|
||||||
if ((stage != DialogStage.YPlus && stage != DialogStage.YMinus) || animationStop)
|
if ((stage != DialogStage.YPlus && stage != DialogStage.YMinus) || animationStop)
|
||||||
return@withEndAction
|
return@withEndAction
|
||||||
|
|
||||||
@ -137,7 +149,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
if ((stage != DialogStage.YPlus && stage != DialogStage.YMinus) || animationStop)
|
if ((stage != DialogStage.YPlus && stage != DialogStage.YMinus) || animationStop)
|
||||||
return@Runnable
|
return@Runnable
|
||||||
|
|
||||||
stick_container?.animate()?.setDuration(250)?.translationY(0f)?.rotationX(0f)?.alpha(0.85f)?.withEndAction {
|
binding.stickContainer.animate().setDuration(250).translationY(0f).rotationX(0f).alpha(0.85f).withEndAction {
|
||||||
if ((stage != DialogStage.YPlus && stage != DialogStage.YMinus) || animationStop)
|
if ((stage != DialogStage.YPlus && stage != DialogStage.YMinus) || animationStop)
|
||||||
return@withEndAction
|
return@withEndAction
|
||||||
|
|
||||||
@ -159,7 +171,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
if ((stage != DialogStage.XPlus && stage != DialogStage.XMinus) || animationStop)
|
if ((stage != DialogStage.XPlus && stage != DialogStage.XMinus) || animationStop)
|
||||||
return@Runnable
|
return@Runnable
|
||||||
|
|
||||||
stick_container?.animate()?.setDuration(300)?.translationX(dipToPixels(16.5f) * polarity)?.rotationY(27f * polarity)?.alpha(1f)?.withEndAction {
|
binding.stickContainer.animate().setDuration(300).translationX(dipToPixels(16.5f) * polarity).rotationY(27f * polarity).alpha(1f).withEndAction {
|
||||||
if ((stage != DialogStage.XPlus && stage != DialogStage.XMinus) || animationStop)
|
if ((stage != DialogStage.XPlus && stage != DialogStage.XMinus) || animationStop)
|
||||||
return@withEndAction
|
return@withEndAction
|
||||||
|
|
||||||
@ -167,14 +179,14 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
if ((stage != DialogStage.XPlus && stage != DialogStage.XMinus) || animationStop)
|
if ((stage != DialogStage.XPlus && stage != DialogStage.XMinus) || animationStop)
|
||||||
return@Runnable
|
return@Runnable
|
||||||
|
|
||||||
stick_container?.animate()?.setDuration(250)?.translationX(0f)?.rotationY(0f)?.alpha(0.85f)?.withEndAction {
|
binding.stickContainer.animate().setDuration(250).translationX(0f).rotationY(0f).alpha(0.85f).withEndAction {
|
||||||
if ((stage != DialogStage.XPlus && stage != DialogStage.XMinus) || animationStop)
|
if ((stage != DialogStage.XPlus && stage != DialogStage.XMinus) || animationStop)
|
||||||
return@withEndAction
|
return@withEndAction
|
||||||
|
|
||||||
stageAnimation?.let {
|
stageAnimation?.let {
|
||||||
handler.postDelayed(it, 750)
|
handler.postDelayed(it, 750)
|
||||||
}
|
}
|
||||||
}?.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.postDelayed(runnable, 300)
|
handler.postDelayed(runnable, 300)
|
||||||
@ -199,13 +211,13 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
if (ordinal in 0 until size) {
|
if (ordinal in 0 until size) {
|
||||||
stage = DialogStage.values()[ordinal]
|
stage = DialogStage.values()[ordinal]
|
||||||
|
|
||||||
stick_title.text = getString(stage.string)
|
binding.stickTitle.text = getString(stage.string)
|
||||||
stick_subtitle.text = if (stage != DialogStage.Stick) getString(R.string.use_button_axis) else getString(R.string.use_non_stick)
|
binding.stickSubtitle.text = getString(if (stage != DialogStage.Stick) R.string.use_button_axis else R.string.use_non_stick)
|
||||||
stick_icon.animate().alpha(0.25f).setDuration(50).start()
|
binding.stickIcon.animate().alpha(0.25f).setDuration(50).start()
|
||||||
stick_name.animate().alpha(0.35f).setDuration(50).start()
|
binding.stickName.animate().alpha(0.35f).setDuration(50).start()
|
||||||
stick_seekbar.visibility = View.GONE
|
binding.stickSeekbar.visibility = View.GONE
|
||||||
|
|
||||||
stick_next.text = if (ordinal + 1 == size) getString(R.string.done) else getString(R.string.next)
|
binding.stickNext.text = getString(if (ordinal + 1 == size) R.string.done else R.string.next)
|
||||||
|
|
||||||
updateAnimation()
|
updateAnimation()
|
||||||
} else {
|
} else {
|
||||||
@ -221,28 +233,28 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
|
|
||||||
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
|
||||||
stick_name.text = item.stick.button.short ?: item.stick.button.toString()
|
binding.stickName.text = item.stick.button.short ?: item.stick.button.toString()
|
||||||
|
|
||||||
// Set up the reset button to clear out all entries corresponding to this stick from [InputManager.eventMap]
|
// Set up the reset button to clear out all entries corresponding to this stick from [inputManager.eventMap]
|
||||||
stick_reset.setOnClickListener {
|
binding.stickReset.setOnClickListener {
|
||||||
for (axis in arrayOf(item.stick.xAxis, item.stick.yAxis)) {
|
for (axis in arrayOf(item.stick.xAxis, item.stick.yAxis)) {
|
||||||
for (polarity in booleanArrayOf(true, false)) {
|
for (polarity in booleanArrayOf(true, false)) {
|
||||||
val guestEvent = AxisGuestEvent(context.id, axis, polarity)
|
val guestEvent = AxisGuestEvent(context.id, axis, polarity)
|
||||||
|
|
||||||
InputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { InputManager.eventMap.remove(it) }
|
inputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { inputManager.eventMap.remove(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val guestEvent = ButtonGuestEvent(context.id, item.stick.button)
|
val guestEvent = ButtonGuestEvent(context.id, item.stick.button)
|
||||||
|
|
||||||
InputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { InputManager.eventMap.remove(it) }
|
inputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { inputManager.eventMap.remove(it) }
|
||||||
|
|
||||||
item.update()
|
item.update()
|
||||||
|
|
||||||
@ -250,11 +262,11 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that layout animations are proper
|
// Ensure that layout animations are proper
|
||||||
stick_layout.layoutTransition.setAnimateParentHierarchy(false)
|
binding.stickLayout.layoutTransition.setAnimateParentHierarchy(false)
|
||||||
stick_layout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
binding.stickLayout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||||
|
|
||||||
// We want the secondary progress bar to be visible through the first one
|
// We want the secondary progress bar to be visible through the first one
|
||||||
stick_seekbar.progressDrawable.alpha = 128
|
binding.stickSeekbar.progressDrawable.alpha = 128
|
||||||
|
|
||||||
updateAnimation()
|
updateAnimation()
|
||||||
|
|
||||||
@ -266,7 +278,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
var axisPolarity = false // The polarity of the axis for the currently selected event
|
var axisPolarity = false // The polarity of the axis for the currently selected event
|
||||||
var axisRunnable : Runnable? = null // The Runnable that is used for counting down till an axis is selected
|
var axisRunnable : Runnable? = null // The Runnable that is used for counting down till an axis is selected
|
||||||
|
|
||||||
stick_next.setOnClickListener {
|
binding.stickNext.setOnClickListener {
|
||||||
gotoStage(1)
|
gotoStage(1)
|
||||||
|
|
||||||
deviceId = null
|
deviceId = null
|
||||||
@ -280,39 +292,41 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
// 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 -> {
|
||||||
if (stage == DialogStage.Stick) {
|
if (stage == DialogStage.Stick) {
|
||||||
// When the stick is being previewed after everything is mapped we do a lookup into [InputManager.eventMap] to find a corresponding [GuestEvent] and animate the stick correspondingly
|
// When the stick is being previewed after everything is mapped we do a lookup into [inputManager.eventMap] to find a corresponding [GuestEvent] and animate the stick correspondingly
|
||||||
when (val guestEvent = InputManager.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) {
|
when (val guestEvent = inputManager.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) {
|
||||||
is ButtonGuestEvent -> {
|
is ButtonGuestEvent -> {
|
||||||
if (guestEvent.button == item.stick.button) {
|
if (guestEvent.button == item.stick.button) {
|
||||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||||
stick_container?.animate()?.setStartDelay(0)?.setDuration(50)?.scaleX(0.85f)?.scaleY(0.85f)?.start()
|
binding.stickContainer.animate().setStartDelay(0).setDuration(50).scaleX(0.85f).scaleY(0.85f).start()
|
||||||
|
|
||||||
stick_icon.animate().alpha(0.85f).setDuration(50).start()
|
binding.stickIcon.animate().alpha(0.85f).setDuration(50).start()
|
||||||
stick_name.animate().alpha(0.95f).setDuration(50).start()
|
binding.stickName.animate().alpha(0.95f).setDuration(50).start()
|
||||||
} else {
|
} else {
|
||||||
stick_container?.animate()?.setStartDelay(0)?.setDuration(25)?.scaleX(1f)?.scaleY(1f)?.start()
|
binding.stickContainer.animate().setStartDelay(0).setDuration(25).scaleX(1f).scaleY(1f).start()
|
||||||
|
|
||||||
stick_icon.animate().alpha(0.25f).setDuration(25).start()
|
binding.stickIcon.animate().alpha(0.25f).setDuration(25).start()
|
||||||
stick_name.animate().alpha(0.35f).setDuration(25).start()
|
binding.stickName.animate().alpha(0.35f).setDuration(25).start()
|
||||||
}
|
}
|
||||||
} else if (event.action == KeyEvent.ACTION_UP) {
|
} else if (event.action == KeyEvent.ACTION_UP) {
|
||||||
stick_next?.callOnClick()
|
binding.stickNext.callOnClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is AxisGuestEvent -> {
|
is AxisGuestEvent -> {
|
||||||
val coefficient = if (event.action == KeyEvent.ACTION_DOWN) if (guestEvent.polarity) 1 else -1 else 0
|
val coefficient = if (event.action == KeyEvent.ACTION_DOWN) if (guestEvent.polarity) 1 else -1 else 0
|
||||||
|
|
||||||
if (guestEvent.axis == item.stick.xAxis) {
|
binding.stickContainer.apply {
|
||||||
stick_container?.translationX = dipToPixels(16.5f) * coefficient
|
if (guestEvent.axis == item.stick.xAxis) {
|
||||||
stick_container?.rotationY = 27f * coefficient
|
translationX = dipToPixels(16.5f) * coefficient
|
||||||
} else if (guestEvent.axis == item.stick.yAxis) {
|
rotationY = 27f * coefficient
|
||||||
stick_container?.translationY = dipToPixels(16.5f) * -coefficient
|
} else if (guestEvent.axis == item.stick.yAxis) {
|
||||||
stick_container?.rotationX = 27f * coefficient
|
translationY = dipToPixels(16.5f) * -coefficient
|
||||||
|
rotationX = 27f * coefficient
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
null -> if (event.action == KeyEvent.ACTION_UP) stick_next?.callOnClick()
|
null -> if (event.action == KeyEvent.ACTION_UP) binding.stickNext.callOnClick()
|
||||||
}
|
}
|
||||||
} else if (stage != DialogStage.Stick) {
|
} else if (stage != DialogStage.Stick) {
|
||||||
if ((deviceId != event.deviceId || inputId != event.keyCode) && event.action == KeyEvent.ACTION_DOWN && !ignoredEvents.any { it == Objects.hash(event.deviceId, event.keyCode) }) {
|
if ((deviceId != event.deviceId || inputId != event.keyCode) && event.action == KeyEvent.ACTION_DOWN && !ignoredEvents.any { it == Objects.hash(event.deviceId, event.keyCode) }) {
|
||||||
@ -330,26 +344,28 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
val coefficient = if (stage == DialogStage.YMinus || stage == DialogStage.XPlus) 1 else -1
|
val coefficient = if (stage == DialogStage.YMinus || stage == DialogStage.XPlus) 1 else -1
|
||||||
|
|
||||||
when (stage) {
|
when (stage) {
|
||||||
DialogStage.Button -> stick_container?.animate()?.setStartDelay(0)?.setDuration(25)?.scaleX(0.85f)?.scaleY(0.85f)?.alpha(1f)?.start()
|
DialogStage.Button -> binding.stickContainer.animate().setStartDelay(0).setDuration(25).scaleX(0.85f).scaleY(0.85f).alpha(1f).start()
|
||||||
DialogStage.YPlus, DialogStage.YMinus -> stick_container?.animate()?.setStartDelay(0)?.setDuration(75)?.translationY(dipToPixels(16.5f) * coefficient)?.rotationX(27f * coefficient)?.alpha(1f)?.start()
|
|
||||||
DialogStage.XPlus, DialogStage.XMinus -> stick_container?.animate()?.setStartDelay(0)?.setDuration(75)?.translationX(dipToPixels(16.5f) * coefficient)?.rotationY(27f * coefficient)?.alpha(1f)?.start()
|
DialogStage.YPlus, DialogStage.YMinus -> binding.stickContainer.animate().setStartDelay(0).setDuration(75).translationY(dipToPixels(16.5f) * coefficient).rotationX(27f * coefficient).alpha(1f).start()
|
||||||
|
|
||||||
|
DialogStage.XPlus, DialogStage.XMinus -> binding.stickContainer.animate().setStartDelay(0).setDuration(75).translationX(dipToPixels(16.5f) * coefficient).rotationY(27f * coefficient).alpha(1f).start()
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stick_icon.animate().alpha(0.85f).setDuration(50).start()
|
binding.stickIcon.animate().alpha(0.85f).setDuration(50).start()
|
||||||
stick_name.animate().alpha(0.95f).setDuration(50).start()
|
binding.stickName.animate().alpha(0.95f).setDuration(50).start()
|
||||||
|
|
||||||
stick_subtitle.text = getString(R.string.release_confirm)
|
binding.stickSubtitle.text = getString(R.string.release_confirm)
|
||||||
stick_seekbar.visibility = View.GONE
|
binding.stickSeekbar.visibility = View.GONE
|
||||||
} else if (deviceId == event.deviceId && inputId == event.keyCode && event.action == KeyEvent.ACTION_UP) {
|
} else if (deviceId == event.deviceId && inputId == event.keyCode && event.action == KeyEvent.ACTION_UP) {
|
||||||
// We serialize the current [deviceId] and [inputId] into a [KeyHostEvent] and map it to a corresponding [GuestEvent] and add it to [ignoredEvents] on [KeyEvent.ACTION_UP]
|
// We serialize the current [deviceId] and [inputId] into a [KeyHostEvent] and map it to a corresponding [GuestEvent] and add it to [ignoredEvents] on [KeyEvent.ACTION_UP]
|
||||||
val hostEvent = KeyHostEvent(event.device.descriptor, event.keyCode)
|
val hostEvent = KeyHostEvent(event.device.descriptor, event.keyCode)
|
||||||
|
|
||||||
var guestEvent = InputManager.eventMap[hostEvent]
|
var guestEvent = inputManager.eventMap[hostEvent]
|
||||||
|
|
||||||
if (guestEvent is GuestEvent) {
|
if (guestEvent is GuestEvent) {
|
||||||
InputManager.eventMap.remove(hostEvent)
|
inputManager.eventMap.remove(hostEvent)
|
||||||
|
|
||||||
if (guestEvent is ButtonGuestEvent)
|
if (guestEvent is ButtonGuestEvent)
|
||||||
context.buttonMap[guestEvent.button]?.update()
|
context.buttonMap[guestEvent.button]?.update()
|
||||||
@ -364,15 +380,15 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
InputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { InputManager.eventMap.remove(it) }
|
inputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { inputManager.eventMap.remove(it) }
|
||||||
|
|
||||||
InputManager.eventMap[hostEvent] = guestEvent
|
inputManager.eventMap[hostEvent] = guestEvent
|
||||||
|
|
||||||
ignoredEvents.add(Objects.hash(deviceId!!, inputId!!))
|
ignoredEvents.add(Objects.hash(deviceId!!, inputId!!))
|
||||||
|
|
||||||
item.update()
|
item.update()
|
||||||
|
|
||||||
stick_next?.callOnClick()
|
binding.stickNext.callOnClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +423,7 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
// We want all input events from Joysticks and Buttons that are [MotionEvent.ACTION_MOVE] and not from the D-pad
|
// We want all input events from Joysticks and Buttons that are [MotionEvent.ACTION_MOVE] and not from the D-pad
|
||||||
if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE && hat == oldHat) {
|
if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE && hat == oldHat) {
|
||||||
if (stage == DialogStage.Stick) {
|
if (stage == DialogStage.Stick) {
|
||||||
// When the stick is being previewed after everything is mapped we do a lookup into [InputManager.eventMap] to find a corresponding [GuestEvent] and animate the stick correspondingly
|
// When the stick is being previewed after everything is mapped we do a lookup into [inputManager.eventMap] to find a corresponding [GuestEvent] and animate the stick correspondingly
|
||||||
for (axisItem in axes.withIndex()) {
|
for (axisItem in axes.withIndex()) {
|
||||||
val axis = axisItem.value
|
val axis = axisItem.value
|
||||||
var value = event.getAxisValue(axis)
|
var value = event.getAxisValue(axis)
|
||||||
@ -421,9 +437,9 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
|
|
||||||
var polarity = value >= 0
|
var polarity = value >= 0
|
||||||
val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent ->
|
val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent ->
|
||||||
InputManager.eventMap[hostEvent] ?: if (value == 0f) {
|
inputManager.eventMap[hostEvent] ?: if (value == 0f) {
|
||||||
polarity = false
|
polarity = false
|
||||||
InputManager.eventMap[hostEvent.copy(polarity = false)]
|
inputManager.eventMap[hostEvent.copy(polarity = false)]
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -433,13 +449,13 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
is ButtonGuestEvent -> {
|
is ButtonGuestEvent -> {
|
||||||
if (guestEvent.button == item.stick.button) {
|
if (guestEvent.button == item.stick.button) {
|
||||||
if (abs(value) >= guestEvent.threshold) {
|
if (abs(value) >= guestEvent.threshold) {
|
||||||
stick_container?.animate()?.setStartDelay(0)?.setDuration(50)?.scaleX(0.85f)?.scaleY(0.85f)?.start()
|
binding.stickContainer.animate().setStartDelay(0).setDuration(50).scaleX(0.85f).scaleY(0.85f).start()
|
||||||
stick_icon.animate().alpha(0.85f).setDuration(50).start()
|
binding.stickIcon.animate().alpha(0.85f).setDuration(50).start()
|
||||||
stick_name.animate().alpha(0.95f).setDuration(50).start()
|
binding.stickName.animate().alpha(0.95f).setDuration(50).start()
|
||||||
} else {
|
} else {
|
||||||
stick_container?.animate()?.setStartDelay(0)?.setDuration(25)?.scaleX(1f)?.scaleY(1f)?.start()
|
binding.stickContainer.animate().setStartDelay(0).setDuration(25).scaleX(1f).scaleY(1f).start()
|
||||||
stick_icon.animate().alpha(0.25f).setDuration(25).start()
|
binding.stickIcon.animate().alpha(0.25f).setDuration(25).start()
|
||||||
stick_name.animate().alpha(0.35f).setDuration(25).start()
|
binding.stickName.animate().alpha(0.35f).setDuration(25).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -449,12 +465,14 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
|
|
||||||
val coefficient = if (polarity) abs(value) else -abs(value)
|
val coefficient = if (polarity) abs(value) else -abs(value)
|
||||||
|
|
||||||
if (guestEvent.axis == item.stick.xAxis) {
|
binding.stickContainer.apply {
|
||||||
stick_container?.translationX = dipToPixels(16.5f) * coefficient
|
if (guestEvent.axis == item.stick.xAxis) {
|
||||||
stick_container?.rotationY = 27f * coefficient
|
translationX = dipToPixels(16.5f) * coefficient
|
||||||
} else if (guestEvent.axis == item.stick.yAxis) {
|
rotationY = 27f * coefficient
|
||||||
stick_container?.translationY = dipToPixels(16.5f) * coefficient
|
} else if (guestEvent.axis == item.stick.yAxis) {
|
||||||
stick_container?.rotationX = 27f * -coefficient
|
translationY = dipToPixels(16.5f) * coefficient
|
||||||
|
rotationX = 27f * -coefficient
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -480,10 +498,10 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
inputId = axis
|
inputId = axis
|
||||||
axisPolarity = value >= 0
|
axisPolarity = value >= 0
|
||||||
|
|
||||||
stick_subtitle.text = getString(R.string.hold_confirm)
|
binding.stickSubtitle.text = getString(R.string.hold_confirm)
|
||||||
|
|
||||||
if (stage == DialogStage.Button)
|
if (stage == DialogStage.Button)
|
||||||
stick_seekbar.visibility = View.VISIBLE
|
binding.stickSeekbar.visibility = View.VISIBLE
|
||||||
|
|
||||||
animationStop = true
|
animationStop = true
|
||||||
|
|
||||||
@ -494,13 +512,13 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
// If the currently active input is a valid axis
|
// If the currently active input is a valid axis
|
||||||
if (axes.contains(inputId)) {
|
if (axes.contains(inputId)) {
|
||||||
val value = event.getAxisValue(inputId!!)
|
val value = event.getAxisValue(inputId!!)
|
||||||
val threshold = if (stage == DialogStage.Button) stick_seekbar.progress / 100f else 0.5f
|
val threshold = if (stage == DialogStage.Button) binding.stickSeekbar.progress / 100f else 0.5f
|
||||||
|
|
||||||
when (stage) {
|
when (stage) {
|
||||||
// Update the secondary progress bar in [button_seekbar] based on the axis's value
|
// Update the secondary progress bar in [button_seekbar] based on the axis's value
|
||||||
DialogStage.Button -> {
|
DialogStage.Button -> {
|
||||||
stick_container?.animate()?.setStartDelay(0)?.setDuration(25)?.scaleX(0.85f)?.scaleY(0.85f)?.alpha(1f)?.start()
|
binding.stickContainer.animate().setStartDelay(0).setDuration(25).scaleX(0.85f).scaleY(0.85f).alpha(1f).start()
|
||||||
stick_seekbar.secondaryProgress = (abs(value) * 100).toInt()
|
binding.stickSeekbar.secondaryProgress = (abs(value) * 100).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -508,16 +526,16 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
DialogStage.YPlus, DialogStage.YMinus -> {
|
DialogStage.YPlus, DialogStage.YMinus -> {
|
||||||
val coefficient = if (stage == DialogStage.YMinus) abs(value) else -abs(value)
|
val coefficient = if (stage == DialogStage.YMinus) abs(value) else -abs(value)
|
||||||
|
|
||||||
stick_container?.translationY = dipToPixels(16.5f) * coefficient
|
binding.stickContainer.translationY = dipToPixels(16.5f) * coefficient
|
||||||
stick_container?.rotationX = 27f * -coefficient
|
binding.stickContainer.rotationX = 27f * -coefficient
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the the position of the stick in the X-axis based on the axis's value
|
// Update the the position of the stick in the X-axis based on the axis's value
|
||||||
DialogStage.XPlus, DialogStage.XMinus -> {
|
DialogStage.XPlus, DialogStage.XMinus -> {
|
||||||
val coefficient = if (stage == DialogStage.XPlus) abs(value) else -abs(value)
|
val coefficient = if (stage == DialogStage.XPlus) abs(value) else -abs(value)
|
||||||
|
|
||||||
stick_container?.translationX = dipToPixels(16.5f) * coefficient
|
binding.stickContainer.translationX = dipToPixels(16.5f) * coefficient
|
||||||
stick_container?.rotationY = 27f * coefficient
|
binding.stickContainer.rotationY = 27f * coefficient
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@ -530,10 +548,10 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
axisRunnable = Runnable {
|
axisRunnable = Runnable {
|
||||||
val hostEvent = MotionHostEvent(event.device.descriptor, inputId!!, axisPolarity)
|
val hostEvent = MotionHostEvent(event.device.descriptor, inputId!!, axisPolarity)
|
||||||
|
|
||||||
var guestEvent = InputManager.eventMap[hostEvent]
|
var guestEvent = inputManager.eventMap[hostEvent]
|
||||||
|
|
||||||
if (guestEvent is GuestEvent) {
|
if (guestEvent is GuestEvent) {
|
||||||
InputManager.eventMap.remove(hostEvent)
|
inputManager.eventMap.remove(hostEvent)
|
||||||
|
|
||||||
if (guestEvent is ButtonGuestEvent)
|
if (guestEvent is ButtonGuestEvent)
|
||||||
context.buttonMap[(guestEvent as ButtonGuestEvent).button]?.update()
|
context.buttonMap[(guestEvent as ButtonGuestEvent).button]?.update()
|
||||||
@ -550,9 +568,9 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
InputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { InputManager.eventMap.remove(it) }
|
inputManager.eventMap.filterValues { it == guestEvent }.keys.forEach { inputManager.eventMap.remove(it) }
|
||||||
|
|
||||||
InputManager.eventMap[hostEvent] = guestEvent
|
inputManager.eventMap[hostEvent] = guestEvent
|
||||||
|
|
||||||
ignoredEvents.add(Objects.hash(deviceId!!, inputId!!, axisPolarity))
|
ignoredEvents.add(Objects.hash(deviceId!!, inputId!!, axisPolarity))
|
||||||
|
|
||||||
@ -560,14 +578,14 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
|
|
||||||
item.update()
|
item.update()
|
||||||
|
|
||||||
stick_next?.callOnClick()
|
binding.stickNext.callOnClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.postDelayed(axisRunnable!!, 1000)
|
handler.postDelayed(axisRunnable!!, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
stick_icon.animate().alpha(0.85f).setInterpolator(LinearInterpolator(context, null)).setDuration(1000).start()
|
binding.stickIcon.animate().alpha(0.85f).setInterpolator(LinearInterpolator(context, null)).setDuration(1000).start()
|
||||||
stick_name.animate().alpha(0.95f).setInterpolator(LinearInterpolator(context, null)).setDuration(1000).start()
|
binding.stickName.animate().alpha(0.95f).setInterpolator(LinearInterpolator(context, null)).setDuration(1000).start()
|
||||||
} else {
|
} else {
|
||||||
// If the axis value is below the threshold, remove [axisRunnable] from it being posted and animate the views accordingly
|
// If the axis value is below the threshold, remove [axisRunnable] from it being posted and animate the views accordingly
|
||||||
if (axisRunnable != null) {
|
if (axisRunnable != null) {
|
||||||
@ -576,10 +594,10 @@ class StickDialog @JvmOverloads constructor(val item : ControllerStickViewItem?
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stage == DialogStage.Button)
|
if (stage == DialogStage.Button)
|
||||||
stick_container?.animate()?.setStartDelay(0)?.setDuration(10)?.scaleX(1f)?.scaleY(1f)?.alpha(1f)?.start()
|
binding.stickContainer.animate().setStartDelay(0).setDuration(10).scaleX(1f).scaleY(1f).alpha(1f).start()
|
||||||
|
|
||||||
stick_icon.animate().alpha(0.25f).setDuration(50).start()
|
binding.stickIcon.animate().alpha(0.25f).setDuration(50).start()
|
||||||
stick_name.animate().alpha(0.35f).setDuration(50).start()
|
binding.stickName.animate().alpha(0.35f).setDuration(50).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,17 +16,19 @@ 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 emu.skyline.R
|
import emu.skyline.R
|
||||||
|
import emu.skyline.databinding.OnScreenEditActivityBinding
|
||||||
import emu.skyline.utils.Settings
|
import emu.skyline.utils.Settings
|
||||||
import kotlinx.android.synthetic.main.on_screen_edit_activity.*
|
|
||||||
|
|
||||||
class OnScreenEditActivity : AppCompatActivity() {
|
class OnScreenEditActivity : AppCompatActivity() {
|
||||||
|
private val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
private var fullEditVisible = true
|
private var fullEditVisible = true
|
||||||
private var editMode = false
|
private var editMode = false
|
||||||
|
|
||||||
private val closeAction : () -> Unit = {
|
private val closeAction : () -> Unit = {
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
toggleFabVisibility(true)
|
toggleFabVisibility(true)
|
||||||
on_screen_controller_view.setEditMode(false)
|
binding.onScreenControllerView.setEditMode(false)
|
||||||
editMode = false
|
editMode = false
|
||||||
} else {
|
} else {
|
||||||
fullEditVisible = !fullEditVisible
|
fullEditVisible = !fullEditVisible
|
||||||
@ -45,12 +47,12 @@ class OnScreenEditActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private val editAction = {
|
private val editAction = {
|
||||||
editMode = true
|
editMode = true
|
||||||
on_screen_controller_view.setEditMode(true)
|
binding.onScreenControllerView.setEditMode(true)
|
||||||
toggleFabVisibility(false)
|
toggleFabVisibility(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val toggleAction : () -> Unit = {
|
private val toggleAction : () -> Unit = {
|
||||||
val buttonProps = on_screen_controller_view.getButtonProps()
|
val buttonProps = binding.onScreenControllerView.getButtonProps()
|
||||||
val checkArray = buttonProps.map { it.second }.toBooleanArray()
|
val checkArray = buttonProps.map { it.second }.toBooleanArray()
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
@ -62,7 +64,7 @@ class OnScreenEditActivity : AppCompatActivity() {
|
|||||||
}.setPositiveButton(R.string.confirm) { _, _ ->
|
}.setPositiveButton(R.string.confirm) { _, _ ->
|
||||||
buttonProps.forEachIndexed { index, pair ->
|
buttonProps.forEachIndexed { index, pair ->
|
||||||
if (checkArray[index] != pair.second)
|
if (checkArray[index] != pair.second)
|
||||||
on_screen_controller_view.setButtonEnabled(pair.first, checkArray[index])
|
binding.onScreenControllerView.setButtonEnabled(pair.first, checkArray[index])
|
||||||
}
|
}
|
||||||
}.setNegativeButton(R.string.cancel, null)
|
}.setNegativeButton(R.string.cancel, null)
|
||||||
.setOnDismissListener { fullScreen() }
|
.setOnDismissListener { fullScreen() }
|
||||||
@ -70,11 +72,11 @@ class OnScreenEditActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val actions : List<Pair<Int, () -> Unit>> = listOf(
|
private val actions : List<Pair<Int, () -> Unit>> = listOf(
|
||||||
Pair(R.drawable.ic_restore, { on_screen_controller_view.resetControls() }),
|
Pair(R.drawable.ic_restore, { binding.onScreenControllerView.resetControls() }),
|
||||||
Pair(R.drawable.ic_toggle, toggleAction),
|
Pair(R.drawable.ic_toggle, toggleAction),
|
||||||
Pair(R.drawable.ic_edit, editAction),
|
Pair(R.drawable.ic_edit, editAction),
|
||||||
Pair(R.drawable.ic_zoom_out, { on_screen_controller_view.decreaseScale() }),
|
Pair(R.drawable.ic_zoom_out, { binding.onScreenControllerView.decreaseScale() }),
|
||||||
Pair(R.drawable.ic_zoom_in, { on_screen_controller_view.increaseScale() }),
|
Pair(R.drawable.ic_zoom_in, { binding.onScreenControllerView.increaseScale() }),
|
||||||
Pair(R.drawable.ic_close, closeAction)
|
Pair(R.drawable.ic_close, closeAction)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -82,11 +84,11 @@ class OnScreenEditActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState : Bundle?) {
|
override fun onCreate(savedInstanceState : Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.on_screen_edit_activity)
|
setContentView(binding.root)
|
||||||
on_screen_controller_view.recenterSticks = Settings(this).onScreenControlRecenterSticks
|
binding.onScreenControllerView.recenterSticks = Settings(this).onScreenControlRecenterSticks
|
||||||
|
|
||||||
actions.forEach { pair ->
|
actions.forEach { pair ->
|
||||||
fab_parent.addView(LayoutInflater.from(this).inflate(R.layout.on_screen_edit_mini_fab, fab_parent, false).apply {
|
binding.fabParent.addView(LayoutInflater.from(this).inflate(R.layout.on_screen_edit_mini_fab, binding.fabParent, false).apply {
|
||||||
(this as FloatingActionButton).setImageDrawable(ContextCompat.getDrawable(context, pair.first))
|
(this as FloatingActionButton).setImageDrawable(ContextCompat.getDrawable(context, pair.first))
|
||||||
setOnClickListener { pair.second.invoke() }
|
setOnClickListener { pair.second.invoke() }
|
||||||
fabMapping[pair.first] = this
|
fabMapping[pair.first] = this
|
||||||
|
@ -12,8 +12,8 @@ import android.util.AttributeSet
|
|||||||
import androidx.preference.Preference
|
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.InputManagerProviderEntryPoint
|
||||||
import emu.skyline.input.ControllerActivity
|
import emu.skyline.input.ControllerActivity
|
||||||
import emu.skyline.input.InputManager
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This preference is used to launch [ControllerActivity] using a preference
|
* This preference is used to launch [ControllerActivity] using a preference
|
||||||
@ -26,6 +26,8 @@ class ControllerPreference @JvmOverloads constructor(context : Context, attrs :
|
|||||||
|
|
||||||
override var requestCode = 0
|
override var requestCode = 0
|
||||||
|
|
||||||
|
private val inputManager = InputManagerProviderEntryPoint.getInputManager(context)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
for (i in 0 until attrs!!.attributeCount) {
|
for (i in 0 until attrs!!.attributeCount) {
|
||||||
val attr = attrs.getAttributeName(i)
|
val attr = attrs.getAttributeName(i)
|
||||||
@ -43,7 +45,7 @@ class ControllerPreference @JvmOverloads constructor(context : Context, attrs :
|
|||||||
key = "controller_$index"
|
key = "controller_$index"
|
||||||
|
|
||||||
title = "${context.getString(R.string.config_controller)} #${index + 1}"
|
title = "${context.getString(R.string.config_controller)} #${index + 1}"
|
||||||
summaryProvider = SummaryProvider<ControllerPreference> { InputManager.controllers[index]!!.type.stringRes.let { context.getString(it) } }
|
summaryProvider = SummaryProvider<ControllerPreference> { inputManager.controllers[index]!!.type.stringRes.let { context.getString(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,7 +57,7 @@ class ControllerPreference @JvmOverloads constructor(context : Context, attrs :
|
|||||||
|
|
||||||
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
|
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
|
||||||
if (this.requestCode == requestCode) {
|
if (this.requestCode == requestCode) {
|
||||||
InputManager.syncObjects()
|
inputManager.syncObjects()
|
||||||
notifyChanged()
|
notifyChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@ import emu.skyline.R
|
|||||||
/**
|
/**
|
||||||
* This class adapts [EditTextPreference] so that it supports setting the value as the summary automatically. Also added useful attributes.
|
* This class adapts [EditTextPreference] so that it supports setting the value as the summary automatically. Also added useful attributes.
|
||||||
*/
|
*/
|
||||||
class CustomEditTextPreference : EditTextPreference {
|
class CustomEditTextPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = androidx.preference.R.attr.editTextPreferenceStyle) : EditTextPreference(context, attrs, defStyleAttr) {
|
||||||
constructor(context : Context, attrs : AttributeSet?, defStyleAttr : Int, defStyleRes : Int) : super(context, attrs, defStyleAttr, defStyleRes) {
|
init {
|
||||||
attrs?.let {
|
attrs?.let {
|
||||||
val a = context.obtainStyledAttributes(it, R.styleable.CustomEditTextPreference, defStyleAttr, 0)
|
val a = context.obtainStyledAttributes(it, R.styleable.CustomEditTextPreference, defStyleAttr, 0)
|
||||||
val limit = a.getInt(R.styleable.CustomEditTextPreference_limit, -1)
|
val limit = a.getInt(R.styleable.CustomEditTextPreference_limit, -1)
|
||||||
@ -33,12 +33,6 @@ class CustomEditTextPreference : EditTextPreference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context : Context, attrs : AttributeSet?, defStyleAttr : Int) : this(context, attrs, defStyleAttr, 0)
|
|
||||||
|
|
||||||
constructor(context : Context, attrs : AttributeSet?) : this(context, attrs, androidx.preference.R.attr.editTextPreferenceStyle)
|
|
||||||
|
|
||||||
constructor(context : Context) : this(context, null)
|
|
||||||
|
|
||||||
override fun onAttached() {
|
override fun onAttached() {
|
||||||
super.onAttached()
|
super.onAttached()
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ import com.google.android.material.snackbar.Snackbar
|
|||||||
import emu.skyline.KeyReader
|
import emu.skyline.KeyReader
|
||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.SettingsActivity
|
import emu.skyline.SettingsActivity
|
||||||
import kotlinx.android.synthetic.main.settings_activity.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches [FileActivity] and process the selected file for key import
|
* Launches [FileActivity] and process the selected file for key import
|
||||||
@ -27,14 +26,14 @@ class FilePreference @JvmOverloads constructor(context : Context?, attrs : Attri
|
|||||||
override fun onClick() = (context as Activity).startActivityForResult(Intent(context, FileActivity::class.java).apply { putExtra(DocumentActivity.KEY_NAME, key) }, requestCode)
|
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?) {
|
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
|
||||||
if (this.requestCode == requestCode) {
|
if (this.requestCode == requestCode && requestCode == Activity.RESULT_OK) {
|
||||||
if (key == "prod_keys" || key == "title_keys") {
|
if (key == "prod_keys" || key == "title_keys") {
|
||||||
val success = KeyReader.import(
|
val success = KeyReader.import(
|
||||||
context,
|
context,
|
||||||
Uri.parse(PreferenceManager.getDefaultSharedPreferences(context).getString(key, "")),
|
Uri.parse(PreferenceManager.getDefaultSharedPreferences(context).getString(key, "")),
|
||||||
KeyReader.KeyType.parse(key)
|
KeyReader.KeyType.parse(key)
|
||||||
)
|
)
|
||||||
Snackbar.make((context as SettingsActivity).settings, if (success) R.string.import_keys_success else R.string.import_keys_failed, Snackbar.LENGTH_LONG).show()
|
Snackbar.make((context as SettingsActivity).binding.root, if (success) R.string.import_keys_success else R.string.import_keys_failed, Snackbar.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,37 +9,34 @@ import android.graphics.Rect
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import emu.skyline.R
|
import emu.skyline.databinding.LicenseDialogBinding
|
||||||
import kotlinx.android.synthetic.main.license_dialog.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This dialog is used to display the contents of a license for a particular project
|
* This dialog is used to display the contents of a license for a particular project
|
||||||
*/
|
*/
|
||||||
class LicenseDialog : DialogFragment() {
|
class LicenseDialog : DialogFragment() {
|
||||||
|
private lateinit var binding : LicenseDialogBinding
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This inflates the layout of the dialog and sets the minimum width/height to 90% of the screen size
|
* This inflates the layout of the dialog and sets the minimum width/height to 90% of the screen size
|
||||||
*/
|
*/
|
||||||
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) : View? {
|
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) : View? {
|
||||||
val layout = layoutInflater.inflate(R.layout.license_dialog, container)
|
|
||||||
|
|
||||||
val displayRectangle = Rect()
|
val displayRectangle = Rect()
|
||||||
val window : Window = requireActivity().window
|
val window : Window = requireActivity().window
|
||||||
window.decorView.getWindowVisibleDisplayFrame(displayRectangle)
|
window.decorView.getWindowVisibleDisplayFrame(displayRectangle)
|
||||||
|
|
||||||
layout.minimumWidth = ((displayRectangle.width() * 0.9f).toInt())
|
return LicenseDialogBinding.inflate(inflater).apply {
|
||||||
layout.minimumHeight = ((displayRectangle.height() * 0.9f).toInt())
|
root.minimumWidth = ((displayRectangle.width() * 0.9f).toInt())
|
||||||
|
root.minimumHeight = ((displayRectangle.height() * 0.9f).toInt())
|
||||||
return layout
|
binding = this
|
||||||
|
}.root
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This sets the [license_url] and [license_content] based on arguments passed
|
|
||||||
*/
|
|
||||||
override fun onActivityCreated(savedInstanceState : Bundle?) {
|
override fun onActivityCreated(savedInstanceState : Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
|
||||||
license_url.text = arguments?.getString("libraryUrl")!!
|
binding.licenseUrl.text = requireArguments().getString("libraryUrl")
|
||||||
license_content.text = context?.getString(arguments?.getInt("libraryLicense")!!)!!
|
binding.licenseContent.text = getString(requireArguments().getInt("libraryLicense"))
|
||||||
|
|
||||||
dialog?.setOnKeyListener { _, keyCode, event ->
|
dialog?.setOnKeyListener { _, keyCode, event ->
|
||||||
if (keyCode == KeyEvent.KEYCODE_BUTTON_B && event.action == KeyEvent.ACTION_UP) {
|
if (keyCode == KeyEvent.KEYCODE_BUTTON_B && event.action == KeyEvent.ACTION_UP) {
|
||||||
|
@ -16,63 +16,50 @@ import emu.skyline.R
|
|||||||
/**
|
/**
|
||||||
* This preference is used to show licenses and the source of a library
|
* This preference is used to show licenses and the source of a library
|
||||||
*/
|
*/
|
||||||
class LicensePreference : Preference {
|
class LicensePreference @JvmOverloads constructor(context : Context?, attrs : AttributeSet? = null, defStyleAttr : Int = R.attr.dialogPreferenceStyle) : Preference(context, attrs, defStyleAttr) {
|
||||||
/**
|
/**
|
||||||
* The [FragmentManager] is used to show the [LicenseDialog] fragment
|
* The [FragmentManager] is used to show the [LicenseDialog] fragment
|
||||||
*/
|
*/
|
||||||
private val fragmentManager : FragmentManager
|
private val fragmentManager = (context as AppCompatActivity).supportFragmentManager
|
||||||
|
|
||||||
/**
|
companion object {
|
||||||
* The tag used by this preference when launching a corresponding fragment
|
private const val LIBRARY_URL_ARG = "libraryUrl"
|
||||||
*/
|
private const val LIBRARY_LICENSE_ARG = "libraryLicense"
|
||||||
private val mDialogFragmentTag = "LicensePreference"
|
|
||||||
|
private val DIALOG_TAG = LicensePreference::class.java.simpleName
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URL of the library
|
* The URL of the library
|
||||||
*/
|
*/
|
||||||
private var libraryUrl : String? = null
|
private lateinit var libraryUrl : String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The contents of the license of this library
|
* The contents of the license of this library
|
||||||
*/
|
*/
|
||||||
private var libraryLicense : Int? = null
|
private var libraryLicense = 0
|
||||||
|
|
||||||
/**
|
|
||||||
* The constructor assigns the [fragmentManager] from the activity and finds [libraryUrl] and [libraryLicense] in the attributes
|
|
||||||
*/
|
|
||||||
constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int, defStyleRes : Int) : super(context, attrs, defStyleAttr, defStyleRes) {
|
|
||||||
fragmentManager = (context as AppCompatActivity).supportFragmentManager
|
|
||||||
|
|
||||||
|
init {
|
||||||
for (i in 0 until attrs!!.attributeCount) {
|
for (i in 0 until attrs!!.attributeCount) {
|
||||||
val attr = attrs.getAttributeName(i)
|
when (attrs.getAttributeName(i)) {
|
||||||
|
LIBRARY_URL_ARG -> libraryUrl = attrs.getAttributeValue(i)
|
||||||
|
|
||||||
if (attr.equals("libraryUrl", ignoreCase = true))
|
LIBRARY_LICENSE_ARG -> libraryLicense = attrs.getAttributeValue(i).substring(1).toInt()
|
||||||
libraryUrl = attrs.getAttributeValue(i)
|
}
|
||||||
else if (attr.equals("libraryLicense", ignoreCase = true))
|
|
||||||
libraryLicense = attrs.getAttributeValue(i).substring(1).toInt()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int) : this(context, attrs, defStyleAttr, 0)
|
|
||||||
|
|
||||||
constructor(context : Context?, attrs : AttributeSet?) : this(context, attrs, R.attr.dialogPreferenceStyle)
|
|
||||||
|
|
||||||
constructor(context : Context?) : this(context, null)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [LicenseDialog] fragment is shown using [fragmentManager] on click with [libraryUrl] and [libraryLicense] passed as arguments
|
* The [LicenseDialog] fragment is shown using [fragmentManager] on click with [libraryUrl] and [libraryLicense] passed as arguments
|
||||||
*/
|
*/
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
if (fragmentManager.findFragmentByTag(mDialogFragmentTag) != null)
|
fragmentManager.findFragmentByTag(DIALOG_TAG) ?: run {
|
||||||
return
|
LicenseDialog().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
val dialog = LicenseDialog()
|
putString(LIBRARY_URL_ARG, libraryUrl)
|
||||||
|
putInt(LIBRARY_LICENSE_ARG, libraryLicense)
|
||||||
val bundle = Bundle(2)
|
}
|
||||||
bundle.putString("libraryUrl", libraryUrl!!)
|
}.show(fragmentManager, DIALOG_TAG)
|
||||||
bundle.putInt("libraryLicense", libraryLicense!!)
|
}
|
||||||
dialog.arguments = bundle
|
|
||||||
|
|
||||||
dialog.show(fragmentManager, mDialogFragmentTag)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,17 +9,12 @@ import android.content.Context
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This preference is used to set the theme to Light/Dark mode
|
* This preference is used to set the theme to Light/Dark mode
|
||||||
*/
|
*/
|
||||||
class ThemePreference : ListPreference {
|
class ThemePreference @JvmOverloads constructor(context : Context?, attrs : AttributeSet? = null, defStyleAttr : Int = R.attr.dialogPreferenceStyle) : ListPreference(context, attrs, defStyleAttr) {
|
||||||
constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int) : super(context, attrs, defStyleAttr)
|
|
||||||
|
|
||||||
constructor(context : Context?, attrs : AttributeSet?) : super(context, attrs)
|
|
||||||
|
|
||||||
constructor(context : Context?) : super(context)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This changes [AppCompatDelegate.sDefaultNightMode] based on what the user's selection is
|
* This changes [AppCompatDelegate.sDefaultNightMode] based on what the user's selection is
|
||||||
*/
|
*/
|
||||||
|
@ -16,7 +16,7 @@ inline fun <reified T> sharedPreferences(context : Context, default : T, prefix
|
|||||||
class SharedPreferencesDelegate<T>(context : Context, private val clazz : Class<T>, private val default : T, private val prefix : String, prefName : String?) : ReadWriteProperty<Any, T> {
|
class SharedPreferencesDelegate<T>(context : Context, private val clazz : Class<T>, private val default : T, private val prefix : String, prefName : String?) : ReadWriteProperty<Any, T> {
|
||||||
private val prefs = prefName?.let { context.getSharedPreferences(prefName, Context.MODE_PRIVATE) } ?: PreferenceManager.getDefaultSharedPreferences(context)
|
private val prefs = prefName?.let { context.getSharedPreferences(prefName, Context.MODE_PRIVATE) } ?: PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
override fun setValue(thisRef : Any, property : KProperty<*>, value : T) = (prefix + pascalToSnakeCase(property.name)).let { keyName ->
|
override fun setValue(thisRef : Any, property : KProperty<*>, value : T) = (prefix + camelToSnakeCase(property.name)).let { keyName ->
|
||||||
prefs.edit().apply {
|
prefs.edit().apply {
|
||||||
when (clazz) {
|
when (clazz) {
|
||||||
Float::class.java, java.lang.Float::class.java -> putFloat(keyName, value as Float)
|
Float::class.java, java.lang.Float::class.java -> putFloat(keyName, value as Float)
|
||||||
@ -27,7 +27,7 @@ class SharedPreferencesDelegate<T>(context : Context, private val clazz : Class<
|
|||||||
}.apply()
|
}.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getValue(thisRef : Any, property : KProperty<*>) : T = (prefix + pascalToSnakeCase(property.name)).let { keyName ->
|
override fun getValue(thisRef : Any, property : KProperty<*>) : T = (prefix + camelToSnakeCase(property.name)).let { keyName ->
|
||||||
prefs.let {
|
prefs.let {
|
||||||
@Suppress("IMPLICIT_CAST_TO_ANY")
|
@Suppress("IMPLICIT_CAST_TO_ANY")
|
||||||
when (clazz) {
|
when (clazz) {
|
||||||
@ -39,7 +39,7 @@ class SharedPreferencesDelegate<T>(context : Context, private val clazz : Class<
|
|||||||
} as T
|
} as T
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pascalToSnakeCase(text : String) = StringBuilder().apply {
|
private fun camelToSnakeCase(text : String) = StringBuilder().apply {
|
||||||
text.forEachIndexed { index, c ->
|
text.forEachIndexed { index, c ->
|
||||||
if (index != 0 && c.isUpperCase()) append('_')
|
if (index != 0 && c.isUpperCase()) append('_')
|
||||||
append(c.toLowerCase())
|
append(c.toLowerCase())
|
||||||
|
@ -11,13 +11,13 @@ import androidx.core.view.MarginLayoutParamsCompat
|
|||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import emu.skyline.R
|
import emu.skyline.databinding.ViewSearchBarBinding
|
||||||
import kotlinx.android.synthetic.main.view_search_bar.view.*
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class SearchBarView @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = com.google.android.material.R.attr.materialCardViewStyle) : MaterialCardView(context, attrs, defStyleAttr) {
|
class SearchBarView @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = com.google.android.material.R.attr.materialCardViewStyle) : MaterialCardView(context, attrs, defStyleAttr) {
|
||||||
|
private val binding = ViewSearchBarBinding.inflate(LayoutInflater.from(context), this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
LayoutInflater.from(context).inflate(R.layout.view_search_bar, this)
|
|
||||||
useCompatPadding = true
|
useCompatPadding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,32 +32,32 @@ class SearchBarView @JvmOverloads constructor(context : Context, attrs : Attribu
|
|||||||
cardElevation = radius / 2f
|
cardElevation = radius / 2f
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRefreshIconListener(listener : OnClickListener) = refresh_icon.setOnClickListener(listener)
|
fun setRefreshIconListener(listener : OnClickListener) = binding.refreshIcon.setOnClickListener(listener)
|
||||||
fun setLogIconListener(listener : OnClickListener) = log_icon.setOnClickListener(listener)
|
fun setLogIconListener(listener : OnClickListener) = binding.logIcon.setOnClickListener(listener)
|
||||||
fun setSettingsIconListener(listener : OnClickListener) = settings_icon.setOnClickListener(listener)
|
fun setSettingsIconListener(listener : OnClickListener) = binding.settingsIcon.setOnClickListener(listener)
|
||||||
|
|
||||||
var refreshIconVisible = false
|
var refreshIconVisible = false
|
||||||
set(visible) {
|
set(visible) {
|
||||||
field = visible
|
field = visible
|
||||||
refresh_icon.apply {
|
binding.refreshIcon.apply {
|
||||||
if (visible != isVisible) {
|
if (visible != isVisible) {
|
||||||
refresh_icon.alpha = if (visible) 0f else 1f
|
binding.refreshIcon.alpha = if (visible) 0f else 1f
|
||||||
animate().alpha(if (visible) 1f else 0f).withStartAction { isVisible = true }.withEndAction { isInvisible = !visible }.apply { duration = 500 }.start()
|
animate().alpha(if (visible) 1f else 0f).withStartAction { isVisible = true }.withEndAction { isInvisible = !visible }.apply { duration = 500 }.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var text : CharSequence
|
var text : CharSequence
|
||||||
get() = search_field.text
|
get() = binding.searchField.text
|
||||||
set(value) = search_field.setText(value)
|
set(value) = binding.searchField.setText(value)
|
||||||
|
|
||||||
fun startTitleAnimation() {
|
fun startTitleAnimation() {
|
||||||
motion_layout.progress = 0f
|
binding.motionLayout.progress = 0f
|
||||||
motion_layout.transitionToEnd()
|
binding.motionLayout.transitionToEnd()
|
||||||
search_field.apply {
|
binding.searchField.apply {
|
||||||
setOnFocusChangeListener { v, hasFocus ->
|
setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
this@SearchBarView.motion_layout.progress = 1f
|
binding.motionLayout.progress = 1f
|
||||||
context.getSystemService(InputMethodManager::class.java).showSoftInput(v, InputMethodManager.SHOW_IMPLICIT)
|
context.getSystemService(InputMethodManager::class.java).showSoftInput(v, InputMethodManager.SHOW_IMPLICIT)
|
||||||
onFocusChangeListener = null
|
onFocusChangeListener = null
|
||||||
}
|
}
|
||||||
@ -66,23 +66,23 @@ class SearchBarView @JvmOverloads constructor(context : Context, attrs : Attribu
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun animateRefreshIcon() {
|
fun animateRefreshIcon() {
|
||||||
refresh_icon.animate().rotationBy(-180f)
|
binding.refreshIcon.animate().rotationBy(-180f)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun addTextChangedListener(
|
fun addTextChangedListener(
|
||||||
crossinline beforeTextChanged : (
|
beforeTextChanged : (
|
||||||
text : CharSequence?,
|
text : CharSequence?,
|
||||||
start : Int,
|
start : Int,
|
||||||
count : Int,
|
count : Int,
|
||||||
after : Int
|
after : Int
|
||||||
) -> Unit = { _, _, _, _ -> },
|
) -> Unit = { _, _, _, _ -> },
|
||||||
crossinline onTextChanged : (
|
onTextChanged : (
|
||||||
text : CharSequence?,
|
text : CharSequence?,
|
||||||
start : Int,
|
start : Int,
|
||||||
before : Int,
|
before : Int,
|
||||||
count : Int
|
count : Int
|
||||||
) -> Unit = { _, _, _, _ -> },
|
) -> Unit = { _, _, _, _ -> },
|
||||||
crossinline afterTextChanged : (text : Editable?) -> Unit = {}
|
afterTextChanged : (text : Editable?) -> Unit = {}
|
||||||
) : TextWatcher {
|
) : TextWatcher {
|
||||||
val textWatcher = object : TextWatcher {
|
val textWatcher = object : TextWatcher {
|
||||||
override fun afterTextChanged(s : Editable?) {
|
override fun afterTextChanged(s : Editable?) {
|
||||||
@ -97,7 +97,7 @@ class SearchBarView @JvmOverloads constructor(context : Context, attrs : Attribu
|
|||||||
onTextChanged.invoke(text, start, before, count)
|
onTextChanged.invoke(text, start, before, count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
search_field.addTextChangedListener(textWatcher)
|
binding.searchField.addTextChangedListener(textWatcher)
|
||||||
|
|
||||||
return textWatcher
|
return textWatcher
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".input.ControllerActivity">
|
tools:context=".input.ControllerActivity">
|
||||||
|
|
||||||
<include layout="@layout/titlebar" />
|
<include
|
||||||
|
android:id="@+id/titlebar"
|
||||||
|
layout="@layout/titlebar" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/controller_list"
|
android:id="@+id/controller_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".LogActivity">
|
tools:context=".LogActivity">
|
||||||
|
|
||||||
<include layout="@layout/titlebar" />
|
<include
|
||||||
|
android:id="@+id/titlebar"
|
||||||
|
layout="@layout/titlebar" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/log_list"
|
android:id="@+id/log_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fastScrollEnabled="true"
|
android:fastScrollEnabled="true"
|
||||||
android:focusedByDefault="true"
|
android:focusedByDefault="true"
|
||||||
android:transcriptMode="normal"
|
android:transcriptMode="normal"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/text_title"
|
android:id="@+id/text_title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<include layout="@layout/titlebar" />
|
<include
|
||||||
|
android:id="@+id/titlebar"
|
||||||
|
layout="@layout/titlebar" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/settings"
|
android:id="@+id/settings"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/app_bar_layout"
|
android:id="@+id/app_bar_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
android:keyboardNavigationCluster="false"
|
android:keyboardNavigationCluster="false"
|
||||||
android:touchscreenBlocksFocus="false">
|
android:touchscreenBlocksFocus="false">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
app:elevation="16dp"
|
app:elevation="16dp"
|
||||||
app:layout_scrollFlags="scroll" />
|
app:layout_scrollFlags="scroll" />
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
11
build.gradle
11
build.gradle
@ -1,24 +1,25 @@
|
|||||||
// 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.kotlin_version = '1.4.21'
|
||||||
|
ext.lifecycle_version = '2.2.0'
|
||||||
|
ext.hilt_version = '2.31.2-alpha'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.2'
|
classpath 'com.android.tools.build:gradle:4.1.2'
|
||||||
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"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
|
||||||
id "com.github.ben-manes.versions" version "0.36.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
Loading…
Reference in New Issue
Block a user