mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-28 08:55:29 +03:00
Make UI fully usable using a controller
This commit focuses on making the UI completely usable using a controller so that a user won't have to switch between their device's touch screen and a controller constantly.
This commit is contained in:
parent
102f26d08e
commit
8e1f8ae7e9
@ -15,6 +15,9 @@ android {
|
|||||||
abiFilters "arm64-v8a"
|
abiFilters "arm64-v8a"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lintOptions {
|
||||||
|
disable 'IconLocation'
|
||||||
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
@ -52,11 +55,11 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'com.google.android.material:material:1.2.0-alpha05'
|
implementation 'com.google.android.material:material:1.3.0-alpha02'
|
||||||
implementation "androidx.core:core-ktx:1.2.0"
|
implementation "androidx.core:core-ktx:1.3.1"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'info.debatty:java-string-similarity:1.2.1'
|
implementation 'info.debatty:java-string-similarity:1.2.1'
|
||||||
|
@ -46,7 +46,7 @@ class AppDialog(val item : AppItem) : BottomSheetDialogFragment() {
|
|||||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
|
||||||
dialog?.setOnKeyListener { _, keyCode, event ->
|
dialog?.setOnKeyListener { _, keyCode, event ->
|
||||||
if (keyCode == KeyEvent.KEYCODE_BUTTON_B && event.action == KeyEvent.ACTION_DOWN) {
|
if (keyCode == KeyEvent.KEYCODE_BUTTON_B && event.action == KeyEvent.ACTION_UP) {
|
||||||
dialog?.onBackPressed()
|
dialog?.onBackPressed()
|
||||||
return@setOnKeyListener true
|
return@setOnKeyListener true
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ class AppDialog(val item : AppItem) : BottomSheetDialogFragment() {
|
|||||||
game_pin.setOnClickListener {
|
game_pin.setOnClickListener {
|
||||||
val info = ShortcutInfo.Builder(context, item.title)
|
val info = ShortcutInfo.Builder(context, item.title)
|
||||||
info.setShortLabel(item.meta.name)
|
info.setShortLabel(item.meta.name)
|
||||||
info.setActivity(ComponentName(requireActivity(), EmulationActivity::class.java))
|
info.setActivity(ComponentName(requireContext(), EmulationActivity::class.java))
|
||||||
info.setIcon(Icon.createWithAdaptiveBitmap(item.icon ?: missingIcon))
|
info.setIcon(Icon.createWithAdaptiveBitmap(item.icon ?: missingIcon))
|
||||||
|
|
||||||
val intent = Intent(context, EmulationActivity::class.java)
|
val intent = Intent(context, EmulationActivity::class.java)
|
||||||
|
@ -8,6 +8,7 @@ package emu.skyline
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.KeyEvent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@ -176,4 +177,16 @@ class LogActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
shareThread.start()
|
shareThread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This handles on calling [onBackPressed] when [KeyEvent.KEYCODE_BUTTON_B] is lifted
|
||||||
|
*/
|
||||||
|
override fun onKeyUp(keyCode : Int, event : KeyEvent?) : Boolean {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BUTTON_B) {
|
||||||
|
onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onKeyUp(keyCode, event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package emu.skyline
|
package emu.skyline
|
||||||
|
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
@ -17,6 +18,7 @@ import android.view.View
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.core.animation.doOnEnd
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
@ -169,6 +171,8 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
|
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
|
||||||
})
|
})
|
||||||
|
|
||||||
|
refresh_fab.setOnClickListener(this)
|
||||||
|
settings_fab.setOnClickListener(this)
|
||||||
open_fab.setOnClickListener(this)
|
open_fab.setOnClickListener(this)
|
||||||
log_fab.setOnClickListener(this)
|
log_fab.setOnClickListener(this)
|
||||||
|
|
||||||
@ -187,6 +191,24 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
val controllerFabX = controller_fabs.translationX
|
||||||
|
window.decorView.findViewById<View>(android.R.id.content).viewTreeObserver.addOnTouchModeChangeListener {
|
||||||
|
if (!it) {
|
||||||
|
toolbar_layout.setExpanded(false)
|
||||||
|
|
||||||
|
controller_fabs.visibility = View.VISIBLE
|
||||||
|
ObjectAnimator.ofFloat(controller_fabs, "translationX", 0f).apply {
|
||||||
|
duration = 250
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ObjectAnimator.ofFloat(controller_fabs, "translationX", controllerFabX).apply {
|
||||||
|
duration = 250
|
||||||
|
start()
|
||||||
|
}.doOnEnd { controller_fabs.visibility = View.GONE }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupAppList() {
|
private fun setupAppList() {
|
||||||
@ -243,11 +265,16 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This handles on-click interaction with [R.id.log_fab], [R.id.open_fab]
|
* This handles on-click interaction with [R.id.refresh_fab], [R.id.settings_fab], [R.id.log_fab], [R.id.open_fab]
|
||||||
*/
|
*/
|
||||||
override fun onClick(view : View) {
|
override fun onClick(view : View) {
|
||||||
when (view.id) {
|
when (view.id) {
|
||||||
|
R.id.refresh_fab -> refreshAdapter(false)
|
||||||
|
|
||||||
|
R.id.settings_fab -> startActivityForResult(Intent(this, SettingsActivity::class.java), 3)
|
||||||
|
|
||||||
R.id.log_fab -> startActivity(Intent(this, LogActivity::class.java))
|
R.id.log_fab -> startActivity(Intent(this, LogActivity::class.java))
|
||||||
|
|
||||||
R.id.open_fab -> {
|
R.id.open_fab -> {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
@ -7,6 +7,7 @@ package emu.skyline
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.KeyEvent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import kotlinx.android.synthetic.main.titlebar.*
|
import kotlinx.android.synthetic.main.titlebar.*
|
||||||
@ -61,4 +62,16 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This handles on calling [onBackPressed] when [KeyEvent.KEYCODE_BUTTON_B] is lifted
|
||||||
|
*/
|
||||||
|
override fun onKeyUp(keyCode : Int, event : KeyEvent?) : Boolean {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BUTTON_B) {
|
||||||
|
onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onKeyUp(keyCode, event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,7 @@ package emu.skyline.preference
|
|||||||
|
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.*
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.Window
|
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import kotlinx.android.synthetic.main.license_dialog.*
|
import kotlinx.android.synthetic.main.license_dialog.*
|
||||||
@ -26,7 +23,7 @@ class LicenseDialog : DialogFragment() {
|
|||||||
val layout = layoutInflater.inflate(R.layout.license_dialog, container)
|
val layout = layoutInflater.inflate(R.layout.license_dialog, container)
|
||||||
|
|
||||||
val displayRectangle = Rect()
|
val displayRectangle = Rect()
|
||||||
val window : Window = activity!!.window
|
val window : Window = requireActivity().window
|
||||||
window.decorView.getWindowVisibleDisplayFrame(displayRectangle)
|
window.decorView.getWindowVisibleDisplayFrame(displayRectangle)
|
||||||
|
|
||||||
layout.minimumWidth = ((displayRectangle.width() * 0.9f).toInt())
|
layout.minimumWidth = ((displayRectangle.width() * 0.9f).toInt())
|
||||||
@ -43,5 +40,14 @@ class LicenseDialog : DialogFragment() {
|
|||||||
|
|
||||||
license_url.text = arguments?.getString("libraryUrl")!!
|
license_url.text = arguments?.getString("libraryUrl")!!
|
||||||
license_content.text = context?.getString(arguments?.getInt("libraryLicense")!!)!!
|
license_content.text = context?.getString(arguments?.getInt("libraryLicense")!!)!!
|
||||||
|
|
||||||
|
dialog?.setOnKeyListener { _, keyCode, event ->
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BUTTON_B && event.action == KeyEvent.ACTION_UP) {
|
||||||
|
dialog?.onBackPressed()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
package emu.skyline.views
|
package emu.skyline.views
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Rect
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
@ -16,13 +18,21 @@ import com.google.android.material.snackbar.Snackbar.SnackbarLayout
|
|||||||
* Custom linear layout with support for [CoordinatorLayout] to move children, when [com.google.android.material.snackbar.Snackbar] shows up.
|
* Custom linear layout with support for [CoordinatorLayout] to move children, when [com.google.android.material.snackbar.Snackbar] shows up.
|
||||||
*/
|
*/
|
||||||
class CustomLinearLayout : LinearLayout, CoordinatorLayout.AttachedBehavior {
|
class CustomLinearLayout : LinearLayout, CoordinatorLayout.AttachedBehavior {
|
||||||
|
|
||||||
constructor(context : Context) : this(context, null)
|
constructor(context : Context) : this(context, null)
|
||||||
constructor(context : Context, attrs : AttributeSet?) : this(context, attrs, 0)
|
constructor(context : Context, attrs : AttributeSet?) : this(context, attrs, 0)
|
||||||
constructor(context : Context, attrs : AttributeSet?, defStyleAttr : Int) : super(context, attrs, defStyleAttr)
|
constructor(context : Context, attrs : AttributeSet?, defStyleAttr : Int) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
override fun getBehavior() : CoordinatorLayout.Behavior<CustomLinearLayout> = MoveUpwardBehavior()
|
override fun getBehavior() : CoordinatorLayout.Behavior<CustomLinearLayout> = MoveUpwardBehavior()
|
||||||
|
|
||||||
|
override fun requestFocus(direction: Int, previouslyFocusedRect: Rect): Boolean = getChildAt(if (direction == View.FOCUS_UP) childCount - 1 else 0 )?.requestFocus() ?: false
|
||||||
|
|
||||||
|
/*
|
||||||
|
override fun onRequestFocusInDescendants(dir : Int, rect : Rect?) : Boolean {
|
||||||
|
Log.i("DESC", "$dir and $rect")
|
||||||
|
return getChildAt(0).requestFocus()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines behaviour when [com.google.android.material.snackbar.Snackbar] is shown.
|
* Defines behaviour when [com.google.android.material.snackbar.Snackbar] is shown.
|
||||||
* Simply sets an offset to y translation to move children out of the way.
|
* Simply sets an offset to y translation to move children out of the way.
|
||||||
|
@ -25,14 +25,15 @@
|
|||||||
android:id="@+id/game_title"
|
android:id="@+id/game_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/game_subtitle"
|
android:id="@+id/game_subtitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Display2"
|
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||||
|
android:textColor="@android:color/tertiary_text_light"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
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:transcriptMode="normal"
|
android:transcriptMode="normal"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@ -23,6 +23,36 @@
|
|||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<emu.skyline.views.CustomLinearLayout
|
||||||
|
android:id="@+id/controller_fabs"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:translationX="72dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/refresh_fab"
|
||||||
|
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:maxImageSize="26dp"
|
||||||
|
app:srcCompat="@drawable/ic_refresh" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/settings_fab"
|
||||||
|
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:maxImageSize="26dp"
|
||||||
|
app:srcCompat="@drawable/ic_settings" />
|
||||||
|
</emu.skyline.views.CustomLinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/open_fab"
|
android:id="@+id/open_fab"
|
||||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
<!-- Logger -->
|
<!-- Logger -->
|
||||||
<string name="file_missing">The log file was not found</string>
|
<string name="file_missing">The log file was not found</string>
|
||||||
<string name="io_error">An I/O error has occurred</string>
|
|
||||||
<string name="upload_logs">The logs are being uploaded</string>
|
<string name="upload_logs">The logs are being uploaded</string>
|
||||||
<string name="cleared">The logs have been cleared</string>
|
<string name="cleared">The logs have been cleared</string>
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
|
Loading…
Reference in New Issue
Block a user