Improvements to UI/UX

This commit makes a few improvements to the UI/UX:
* Crop Game Icons to ImageView
* Controller Support for Game List
* EmulationActivity is fullscreen now
This commit is contained in:
◱ PixelyIon 2020-04-24 17:09:13 +05:30
parent f909c00e31
commit 4d787c904e
22 changed files with 232 additions and 182 deletions

View File

@ -2,7 +2,7 @@
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
-keep class emu.skyline.loader.TitleEntry {
-keep class emu.skyline.loader.AppEntry {
void writeObject(java.io.ObjectOutputStream);
void readObject(java.io.ObjectInputStream);
}

View File

@ -3,7 +3,7 @@
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.utility
package emu.skyline
import android.content.ComponentName
import android.content.Intent
@ -11,14 +11,13 @@ import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.drawable.toBitmap
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import emu.skyline.EmulationActivity
import emu.skyline.R
import emu.skyline.adapter.AppItem
import kotlinx.android.synthetic.main.app_dialog.*
@ -27,29 +26,38 @@ import kotlinx.android.synthetic.main.app_dialog.*
*
* @param item This is used to hold the [AppItem] between instances
*/
class AppDialog(val item: AppItem? = null) : BottomSheetDialogFragment() {
class AppDialog(val item : AppItem? = null) : BottomSheetDialogFragment() {
/**
* 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)
}
/**
* This expands the bottom sheet so that it's fully visible
* This expands the bottom sheet so that it's fully visible and map the B button to back
*/
override fun onStart() {
super.onStart()
val behavior = BottomSheetBehavior.from(requireView().parent as View)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
dialog?.setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BUTTON_B && event.action == KeyEvent.ACTION_DOWN) {
dialog?.onBackPressed()
true
} else {
false
}
}
}
/**
* This fills all the dialog with the information from [item] if it is valid and setup all user interaction
*/
override fun onActivityCreated(savedInstanceState: Bundle?) {
override fun onActivityCreated(savedInstanceState : Bundle?) {
super.onActivityCreated(savedInstanceState)
if (item is AppItem) {
@ -59,6 +67,13 @@ class AppDialog(val item: AppItem? = null) : BottomSheetDialogFragment() {
game_title.text = item.title
game_subtitle.text = item.subTitle ?: getString(R.string.metadata_missing)
game_play.setOnClickListener {
val intent = Intent(activity, EmulationActivity::class.java)
intent.data = item.uri
startActivity(intent)
}
val shortcutManager = activity?.getSystemService(ShortcutManager::class.java)!!
game_pin.isEnabled = shortcutManager.isRequestPinShortcutSupported
@ -76,13 +91,6 @@ class AppDialog(val item: AppItem? = null) : BottomSheetDialogFragment() {
shortcutManager.requestPinShortcut(info.build(), null)
}
game_play.setOnClickListener {
val intent = Intent(activity, EmulationActivity::class.java)
intent.data = item.uri
startActivity(intent)
}
} else
activity?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit()
}

View File

@ -13,7 +13,7 @@ import android.os.ParcelFileDescriptor
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.WindowManager
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import emu.skyline.loader.getRomFormat
@ -28,32 +28,32 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
/**
* The file descriptor of the ROM
*/
private lateinit var romFd: ParcelFileDescriptor
private lateinit var romFd : ParcelFileDescriptor
/**
* The file descriptor of the application Preference XML
*/
private lateinit var preferenceFd: ParcelFileDescriptor
private lateinit var preferenceFd : ParcelFileDescriptor
/**
* The file descriptor of the Log file
*/
private lateinit var logFd: ParcelFileDescriptor
private lateinit var logFd : ParcelFileDescriptor
/**
* The surface object used for displaying frames
*/
private var surface: Surface? = null
private var surface : Surface? = null
/**
* A boolean flag denoting if the emulation thread should call finish() or not
*/
private var shouldFinish: Boolean = true
private var shouldFinish : Boolean = true
/**
* The Kotlin thread on which emulation code executes
*/
private lateinit var emulationThread: Thread
private lateinit var emulationThread : Thread
/**
* This is the entry point into the emulation code for libskyline
@ -64,38 +64,38 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
* @param preferenceFd The file descriptor of the Preference XML
* @param logFd The file descriptor of the Log file
*/
private external fun executeApplication(romUri: String, romType: Int, romFd: Int, preferenceFd: Int, logFd: Int)
private external fun executeApplication(romUri : String, romType : Int, romFd : Int, preferenceFd : Int, logFd : Int)
/**
* This sets the halt flag in libskyline to the provided value, if set to true it causes libskyline to halt emulation
*
* @param halt The value to set halt to
*/
private external fun setHalt(halt: Boolean)
private external fun setHalt(halt : Boolean)
/**
* This sets the surface object in libskyline to the provided value, emulation is halted if set to null
*
* @param surface The value to set surface to
*/
private external fun setSurface(surface: Surface?)
private external fun setSurface(surface : Surface?)
/**
* This returns the current FPS of the application
*/
private external fun getFps(): Int
private external fun getFps() : Int
/**
* This returns the current frame-time of the application
*/
private external fun getFrametime(): Float
private external fun getFrametime() : Float
/**
* This executes the specified ROM, [preferenceFd] and [logFd] are assumed to be valid beforehand
*
* @param rom The URI of the ROM to execute
*/
private fun executeApplication(rom: Uri) {
private fun executeApplication(rom : Uri) {
val romType = getRomFormat(rom, contentResolver).ordinal
romFd = contentResolver.openFileDescriptor(rom, "r")!!
@ -116,12 +116,17 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
* This makes the window fullscreen then sets up [preferenceFd] and [logFd], sets up the performance statistics and finally calls [executeApplication] for executing the application
*/
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.emu_activity)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE)
@ -134,7 +139,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
if (sharedPreferences.getBoolean("perf_stats", false)) {
lateinit var perfRunnable: Runnable
lateinit var perfRunnable : Runnable
perfRunnable = Runnable {
perf_stats.text = "${getFps()} FPS\n${getFrametime()}ms"
@ -150,7 +155,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
/**
* This is used to stop the currently executing ROM and replace it with the one specified in the new intent
*/
override fun onNewIntent(intent: Intent?) {
override fun onNewIntent(intent : Intent?) {
shouldFinish = false
setHalt(true)
@ -184,7 +189,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
/**
* This sets [surface] to [holder].surface and passes it into libskyline
*/
override fun surfaceCreated(holder: SurfaceHolder?) {
override fun surfaceCreated(holder : SurfaceHolder?) {
Log.d("surfaceCreated", "Holder: ${holder.toString()}")
surface = holder!!.surface
setSurface(surface)
@ -193,14 +198,14 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
/**
* This is purely used for debugging surface changes
*/
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
override fun surfaceChanged(holder : SurfaceHolder?, format : Int, width : Int, height : Int) {
Log.d("surfaceChanged", "Holder: ${holder.toString()}, Format: $format, Width: $width, Height: $height")
}
/**
* This sets [surface] to null and passes it into libskyline
*/
override fun surfaceDestroyed(holder: SurfaceHolder?) {
override fun surfaceDestroyed(holder : SurfaceHolder?) {
Log.d("surfaceDestroyed", "Holder: ${holder.toString()}")
surface = null
setSurface(surface)

View File

@ -32,17 +32,17 @@ class LogActivity : AppCompatActivity() {
/**
* 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 lateinit var adapter: LogAdapter
private lateinit var adapter : LogAdapter
/**
* 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)
setContentView(R.layout.log_activity)
@ -68,12 +68,12 @@ class LogActivity : AppCompatActivity() {
logFile.forEachLine {
adapter.add(it)
}
} catch (e: FileNotFoundException) {
} catch (e : FileNotFoundException) {
Log.w("Logger", "IO Error during access of log file: " + e.message)
Toast.makeText(applicationContext, getString(R.string.file_missing), Toast.LENGTH_LONG).show()
finish()
} catch (e: IOException) {
} catch (e : IOException) {
Log.w("Logger", "IO Error during access of log file: " + e.message)
Toast.makeText(applicationContext, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show()
}
@ -82,19 +82,19 @@ class LogActivity : AppCompatActivity() {
/**
* This inflates the layout for the menu [R.menu.toolbar_log] and sets up searching the logs
*/
override fun onCreateOptionsMenu(menu: Menu): Boolean {
override fun onCreateOptionsMenu(menu : Menu) : Boolean {
menuInflater.inflate(R.menu.toolbar_log, menu)
val searchView = menu.findItem(R.id.action_search_log).actionView as SearchView
searchView.isSubmitButtonEnabled = false
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
override fun onQueryTextSubmit(query : String) : Boolean {
searchView.isIconified = false
return false
}
override fun onQueryTextChange(newText: String): Boolean {
override fun onQueryTextChange(newText : String) : Boolean {
adapter.filter.filter(newText)
return true
}
@ -106,12 +106,12 @@ class LogActivity : AppCompatActivity() {
/**
* This handles menu selection for [R.id.action_clear] and [R.id.action_share_log]
*/
override fun onOptionsItemSelected(item: MenuItem): Boolean {
override fun onOptionsItemSelected(item : MenuItem) : Boolean {
return when (item.itemId) {
R.id.action_clear -> {
try {
logFile.writeText("")
} catch (e: IOException) {
} catch (e : IOException) {
Log.w("Logger", "IO Error while clearing the log file: " + e.message)
Toast.makeText(applicationContext, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show()
}
@ -137,7 +137,7 @@ class LogActivity : AppCompatActivity() {
Snackbar.make(findViewById(android.R.id.content), getString(R.string.upload_logs), Snackbar.LENGTH_SHORT).show()
val shareThread = Thread(Runnable {
var urlConnection: HttpsURLConnection? = null
var urlConnection : HttpsURLConnection? = null
try {
val url = URL("https://hastebin.com/documents")
@ -166,7 +166,7 @@ class LogActivity : AppCompatActivity() {
val sharingIntent = Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result)
startActivity(Intent.createChooser(sharingIntent, "Share log url with:"))
} catch (e: Exception) {
} catch (e : Exception) {
runOnUiThread { Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_LONG).show() }
e.printStackTrace()
} finally {

View File

@ -7,6 +7,7 @@ package emu.skyline
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import android.util.Log
@ -29,7 +30,6 @@ import emu.skyline.adapter.GridLayoutSpan
import emu.skyline.adapter.LayoutType
import emu.skyline.loader.BaseLoader
import emu.skyline.loader.NroLoader
import emu.skyline.utility.AppDialog
import emu.skyline.utility.RandomAccessDocument
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.titlebar.*
@ -42,17 +42,17 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
/**
* This is used to get/set shared preferences
*/
private lateinit var sharedPreferences: SharedPreferences
private lateinit var sharedPreferences : SharedPreferences
/**
* The adapter used for adding elements to [app_list]
*/
private lateinit var adapter: AppAdapter
private lateinit var adapter : AppAdapter
/**
* This adds all files in [directory] with [extension] as an entry in [adapter] using [loader] to load metadata
*/
private fun addEntries(extension: String, loader: BaseLoader, directory: DocumentFile, found: Boolean = false): Boolean {
private fun addEntries(extension : String, loader : BaseLoader, directory : DocumentFile, found : Boolean = false) : Boolean {
var foundCurrent = found
directory.listFiles().forEach { file ->
@ -89,19 +89,22 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
*
* @param tryLoad If this is false then trying to load cached adapter data is skipped entirely
*/
private fun refreshAdapter(tryLoad: Boolean) {
private fun refreshAdapter(tryLoad : Boolean) {
if (tryLoad) {
try {
adapter.load(File("${applicationInfo.dataDir}/roms.bin"))
return
} catch (e: Exception) {
} catch (e : Exception) {
Log.w("refreshFiles", "Ran into exception while loading: ${e.message}")
}
}
thread(start = true) {
val snackbar = Snackbar.make(findViewById(android.R.id.content), getString(R.string.searching_roms), Snackbar.LENGTH_INDEFINITE)
runOnUiThread { snackbar.show() }
runOnUiThread {
snackbar.show()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
}
try {
runOnUiThread { adapter.clear() }
@ -114,13 +117,13 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
try {
adapter.save(File("${applicationInfo.dataDir}/roms.bin"))
} catch (e: IOException) {
} catch (e : IOException) {
Log.w("refreshFiles", "Ran into exception while saving: ${e.message}")
}
}
sharedPreferences.edit().putBoolean("refresh_required", false).apply()
} catch (e: IllegalArgumentException) {
} catch (e : IllegalArgumentException) {
runOnUiThread {
sharedPreferences.edit().remove("search_location").apply()
@ -128,20 +131,23 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
finish()
startActivity(intent)
}
} catch (e: Exception) {
} catch (e : Exception) {
runOnUiThread {
Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_SHORT).show()
}
}
runOnUiThread { snackbar.dismiss() }
runOnUiThread {
snackbar.dismiss()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
}
/**
* This initializes [toolbar], [open_fab], [log_fab] and [app_list]
*/
override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
@ -184,6 +190,23 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
}
}
app_list.addOnScrollListener(object : RecyclerView.OnScrollListener() {
var y : Int = 0
override fun onScrolled(recyclerView : RecyclerView, dx : Int, dy : Int) {
y += dy
if (!app_list.isInTouchMode) {
if (y == 0)
toolbar_layout.setExpanded(true)
else
toolbar_layout.setExpanded(false)
}
super.onScrolled(recyclerView, dx, dy)
}
})
if (sharedPreferences.getString("search_location", "") == "") {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
@ -196,18 +219,18 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
/**
* This inflates the layout for the menu [R.menu.toolbar_main] and sets up searching the logs
*/
override fun onCreateOptionsMenu(menu: Menu): Boolean {
override fun onCreateOptionsMenu(menu : Menu) : Boolean {
menuInflater.inflate(R.menu.toolbar_main, menu)
val searchView = menu.findItem(R.id.action_search_main).actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
override fun onQueryTextSubmit(query : String) : Boolean {
searchView.clearFocus()
return false
}
override fun onQueryTextChange(newText: String): Boolean {
override fun onQueryTextChange(newText : String) : Boolean {
adapter.filter.filter(newText)
return true
}
@ -219,7 +242,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
/**
* This handles on-click interaction with [R.id.log_fab], [R.id.open_fab], [R.id.app_item_linear] and [R.id.app_item_grid]
*/
override fun onClick(view: View) {
override fun onClick(view : View) {
when (view.id) {
R.id.log_fab -> startActivity(Intent(this, LogActivity::class.java))
@ -250,7 +273,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
/**
* This handles long-click interaction with [R.id.app_item_linear] and [R.id.app_item_grid]
*/
override fun onLongClick(view: View?): Boolean {
override fun onLongClick(view : View?) : Boolean {
when (view?.id) {
R.id.app_item_linear, R.id.app_item_grid -> {
val tag = view.tag
@ -269,7 +292,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
/**
* This handles menu interaction for [R.id.action_settings] and [R.id.action_refresh]
*/
override fun onOptionsItemSelected(item: MenuItem): Boolean {
override fun onOptionsItemSelected(item : MenuItem) : Boolean {
return when (item.itemId) {
R.id.action_settings -> {
startActivityForResult(Intent(this, SettingsActivity::class.java), 3)
@ -288,7 +311,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
/**
* This handles receiving activity result from [Intent.ACTION_OPEN_DOCUMENT_TREE], [Intent.ACTION_OPEN_DOCUMENT] and [SettingsActivity]
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
override fun onActivityResult(requestCode : Int, resultCode : Int, intent : Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
if (resultCode == RESULT_OK) {
@ -310,7 +333,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
startActivityForResult(intentGame, resultCode)
else
startActivity(intentGame)
} catch (e: Exception) {
} catch (e : Exception) {
Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_SHORT).show()
}
}

View File

@ -15,12 +15,12 @@ class SettingsActivity : AppCompatActivity() {
/**
* This is the instance of [PreferenceFragment] that is shown inside [R.id.settings]
*/
private val preferenceFragment: PreferenceFragment = PreferenceFragment()
private val preferenceFragment : PreferenceFragment = PreferenceFragment()
/**
* This initializes [toolbar] and [R.id.settings]
*/
override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
@ -37,7 +37,7 @@ class SettingsActivity : AppCompatActivity() {
/**
* This is used to refresh the preferences after [emu.skyline.preference.FolderActivity] has returned
*/
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
public override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
super.onActivityResult(requestCode, resultCode, data)
preferenceFragment.refreshPreferences()
}
@ -57,7 +57,7 @@ class SettingsActivity : AppCompatActivity() {
/**
* This constructs the preferences from [R.xml.preferences]
*/
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
override fun onCreatePreferences(savedInstanceState : Bundle?, rootKey : String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
}

View File

@ -28,41 +28,41 @@ import emu.skyline.loader.AppEntry
/**
* This class is a wrapper around [AppEntry], it is used for passing around game metadata
*/
class AppItem(val meta: AppEntry) : BaseItem() {
class AppItem(val meta : AppEntry) : BaseItem() {
/**
* The icon of the application
*/
val icon: Bitmap?
val icon : Bitmap?
get() = meta.icon
/**
* The title of the application
*/
val title: String
val title : String
get() = meta.name
/**
* The string used as the sub-title, we currently use the author
*/
val subTitle: String?
val subTitle : String?
get() = meta.author
/**
* The URI of the application's image file
*/
val uri: Uri
val uri : Uri
get() = meta.uri
/**
* The format of the application ROM as a string
*/
private val type: String
private val type : String
get() = meta.format.name
/**
* The name and author is used as the key
*/
override fun key(): String? {
override fun key() : String? {
return if (meta.author != null) meta.name + " " + meta.author else meta.name
}
}
@ -78,21 +78,21 @@ enum class LayoutType {
/**
* This adapter is used to display all found applications using their metadata
*/
internal class AppAdapter(val context: Context?, private val layoutType: LayoutType) : HeaderAdapter<AppItem, BaseHeader, RecyclerView.ViewHolder>(), View.OnClickListener {
internal class AppAdapter(val context : Context?, private val layoutType : LayoutType) : HeaderAdapter<AppItem, BaseHeader, RecyclerView.ViewHolder>(), View.OnClickListener {
private val missingIcon = context?.resources?.getDrawable(R.drawable.default_icon, context.theme)?.toBitmap(256, 256)
private val missingString = context?.getString(R.string.metadata_missing)
/**
* This adds a header to the view with the contents of [string]
*/
fun addHeader(string: String) {
fun addHeader(string : String) {
super.addHeader(BaseHeader(string))
}
/**
* The onClick handler for the supplied [view], used for the icon preview
*/
override fun onClick(view: View) {
override fun onClick(view : View) {
val position = view.tag as Int
if (getItem(position) is AppItem) {
@ -120,7 +120,7 @@ internal class AppAdapter(val context: Context?, private val layoutType: LayoutT
* @param title The TextView associated with the title
* @param subtitle The TextView associated with the subtitle
*/
private class ItemViewHolder(val parent: View, var icon: ImageView, var title: TextView, var subtitle: TextView, var card: View? = null) : RecyclerView.ViewHolder(parent)
private class ItemViewHolder(val parent : View, var icon : ImageView, var title : TextView, var subtitle : TextView, var card : View? = null) : RecyclerView.ViewHolder(parent)
/**
* The ViewHolder used by headers is used to hold the views associated with an headers
@ -128,14 +128,14 @@ internal class AppAdapter(val context: Context?, private val layoutType: LayoutT
* @param parent The parent view that contains all the others
* @param header The TextView associated with the header
*/
private class HeaderViewHolder(val parent: View, var header: TextView? = null) : RecyclerView.ViewHolder(parent)
private class HeaderViewHolder(val parent : View, var header : TextView? = null) : RecyclerView.ViewHolder(parent)
/**
* This function creates the view-holder of type [viewType] with the layout parent as [parent]
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) : RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(context)
var holder: RecyclerView.ViewHolder? = null
var holder : RecyclerView.ViewHolder? = null
if (viewType == Item.ordinal) {
val view = inflater.inflate(if (layoutType == LayoutType.List) R.layout.app_item_linear else R.layout.app_item_grid, parent, false)
@ -155,6 +155,8 @@ internal class AppAdapter(val context: Context?, private val layoutType: LayoutT
if (context is View.OnLongClickListener)
holder.card!!.setOnLongClickListener(context as View.OnLongClickListener)
holder.title.isSelected = true
}
} else if (viewType == Header.ordinal) {
val view = inflater.inflate(R.layout.section_item, parent, false)
@ -169,7 +171,7 @@ internal class AppAdapter(val context: Context?, private val layoutType: LayoutT
/**
* This function binds the item at [position] to the supplied [viewHolder]
*/
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
override fun onBindViewHolder(viewHolder : RecyclerView.ViewHolder, position : Int) {
val item = getItem(position)
if (item is AppItem) {

View File

@ -20,7 +20,7 @@ import kotlin.collections.ArrayList
/**
* An enumeration of the type of elements in this adapter
*/
enum class ElementType(val type: Int) {
enum class ElementType(val type : Int) {
Header(0x0),
Item(0x1)
}
@ -28,12 +28,12 @@ enum class ElementType(val type: Int) {
/**
* This is an abstract class that all adapter element classes inherit from
*/
abstract class BaseElement constructor(val elementType: ElementType) : Serializable
abstract class BaseElement constructor(val elementType : ElementType) : Serializable
/**
* This is an abstract class that all adapter header classes inherit from
*/
class BaseHeader constructor(val title: String) : BaseElement(ElementType.Header)
class BaseHeader constructor(val title : String) : BaseElement(ElementType.Header)
/**
* This is an abstract class that all adapter item classes inherit from
@ -42,7 +42,7 @@ abstract class BaseItem : BaseElement(ElementType.Item) {
/**
* This function returns a string used for searching
*/
abstract fun key(): String?
abstract fun key() : String?
}
/**
@ -52,12 +52,12 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
/**
* This holds all the elements in an array even if they may not be visible
*/
var elementArray: ArrayList<BaseElement?> = ArrayList()
var elementArray : ArrayList<BaseElement?> = ArrayList()
/**
* This holds the indices of all the visible items in [elementArray]
*/
var visibleArray: ArrayList<Int> = ArrayList()
var visibleArray : ArrayList<Int> = ArrayList()
/**
* This holds the search term if there is any, to filter any items added during a search
@ -67,7 +67,7 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
/**
* This functions adds [item] to [elementArray] and [visibleArray] based on the filter
*/
fun addItem(item: ItemType) {
fun addItem(item : ItemType) {
elementArray.add(item)
if (searchTerm.isNotEmpty())
filter.filter(searchTerm)
@ -80,7 +80,7 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
/**
* This function adds [header] to [elementArray] and [visibleArray] based on if the filter is active
*/
fun addHeader(header: HeaderType) {
fun addHeader(header : HeaderType) {
elementArray.add(header)
if (searchTerm.isEmpty())
visibleArray.add(elementArray.size - 1)
@ -91,7 +91,7 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
* This serializes [elementArray] into [file]
*/
@Throws(IOException::class)
fun save(file: File) {
fun save(file : File) {
val fileObj = FileOutputStream(file)
val out = ObjectOutputStream(fileObj)
out.writeObject(elementArray)
@ -103,7 +103,7 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
* This reads in [elementArray] from [file]
*/
@Throws(IOException::class, ClassNotFoundException::class)
open fun load(file: File) {
open fun load(file : File) {
val fileObj = FileInputStream(file)
val input = ObjectInputStream(fileObj)
@Suppress("UNCHECKED_CAST")
@ -125,12 +125,12 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
/**
* This returns the amount of elements that should be drawn to the list
*/
override fun getItemCount(): Int = visibleArray.size
override fun getItemCount() : Int = visibleArray.size
/**
* This returns a particular element at [position]
*/
fun getItem(position: Int): BaseElement? {
fun getItem(position : Int) : BaseElement? {
return elementArray[visibleArray[position]]
}
@ -139,14 +139,14 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
*
* @param position The position of the element
*/
override fun getItemViewType(position: Int): Int {
override fun getItemViewType(position : Int) : Int {
return elementArray[visibleArray[position]]!!.elementType.type
}
/**
* This returns an instance of the filter object which is used to search for items in the view
*/
override fun getFilter(): Filter {
override fun getFilter() : Filter {
return object : Filter() {
/**
* We use Jaro-Winkler distance for string similarity (https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance)
@ -164,13 +164,13 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
* @param score The score of this result
* @param index The index of this item
*/
inner class ScoredItem(val score: Double, val index: Int) {}
inner class ScoredItem(val score : Double, val index : Int) {}
/**
* This sorts the items in [keyArray] in relation to how similar they are to [term]
*/
fun extractSorted(term: String, keyArray: ArrayList<String>): Array<ScoredItem> {
val scoredItems: MutableList<ScoredItem> = ArrayList()
fun extractSorted(term : String, keyArray : ArrayList<String>) : Array<ScoredItem> {
val scoredItems : MutableList<ScoredItem> = ArrayList()
keyArray.forEachIndexed { index, item ->
val similarity = (jw.similarity(term, item) + cos.similarity(term, item)) / 2
@ -187,7 +187,7 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
/**
* This performs filtering on the items in [elementArray] based on similarity to [term]
*/
override fun performFiltering(term: CharSequence): FilterResults {
override fun performFiltering(term : CharSequence) : FilterResults {
val results = FilterResults()
searchTerm = (term as String).toLowerCase(Locale.getDefault())
@ -226,7 +226,7 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
/**
* This publishes the results that were calculated in [performFiltering] to the view
*/
override fun publishResults(charSequence: CharSequence, results: FilterResults) {
override fun publishResults(charSequence : CharSequence, results : FilterResults) {
if (results.values is ArrayList<*>) {
@Suppress("UNCHECKED_CAST")
visibleArray = results.values as ArrayList<Int>
@ -244,11 +244,11 @@ abstract class HeaderAdapter<ItemType : BaseItem?, HeaderType : BaseHeader?, Vie
* @param adapter The adapter which is used to deduce the type of the item based on the position
* @param headerSpan The span size to return for headers
*/
class GridLayoutSpan<ItemType : BaseItem?, HeaderType : BaseHeader?, ViewHolder : RecyclerView.ViewHolder?>(val adapter: HeaderAdapter<ItemType, HeaderType, ViewHolder>, var headerSpan: Int) : GridLayoutManager.SpanSizeLookup() {
class GridLayoutSpan<ItemType : BaseItem?, HeaderType : BaseHeader?, ViewHolder : RecyclerView.ViewHolder?>(val adapter : HeaderAdapter<ItemType, HeaderType, ViewHolder>, var headerSpan : Int) : GridLayoutManager.SpanSizeLookup() {
/**
* This returns the size of the span based on the type of the element at [position]
*/
override fun getSpanSize(position: Int): Int {
override fun getSpanSize(position : Int) : Int {
val item = adapter.getItem(position)!!
return if (item.elementType == ElementType.Item)
1

View File

@ -20,11 +20,11 @@ import emu.skyline.R
/**
* This class is used to hold all data about a log entry
*/
internal class LogItem(val message: String, val level: String) : BaseItem() {
internal class LogItem(val message : String, val level : String) : BaseItem() {
/**
* The log message itself is used as the search key
*/
override fun key(): String? {
override fun key() : String? {
return message
}
}
@ -32,13 +32,13 @@ internal class LogItem(val message: String, val level: String) : BaseItem() {
/**
* This adapter is used for displaying logs outputted by the application
*/
internal class LogAdapter internal constructor(val context: Context, val compact: Boolean, private val debug_level: Int, private val level_str: Array<String>) : HeaderAdapter<LogItem, BaseHeader, RecyclerView.ViewHolder>(), OnLongClickListener {
private val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
internal class LogAdapter internal constructor(val context : Context, val compact : Boolean, private val debug_level : Int, private val level_str : Array<String>) : HeaderAdapter<LogItem, BaseHeader, RecyclerView.ViewHolder>(), OnLongClickListener {
private val clipboard : ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
/**
* This function adds a line to this log adapter
*/
fun add(logLine: String) {
fun add(logLine : String) {
try {
val logMeta = logLine.split("|", limit = 3)
@ -50,8 +50,8 @@ internal class LogAdapter internal constructor(val context: Context, val compact
} else {
addHeader(BaseHeader(logMeta[1]))
}
} catch (ignored: IndexOutOfBoundsException) {
} catch (ignored: NumberFormatException) {
} catch (ignored : IndexOutOfBoundsException) {
} catch (ignored : NumberFormatException) {
}
}
@ -62,7 +62,7 @@ internal class LogAdapter internal constructor(val context: Context, val compact
* @param title The TextView associated with the title
* @param subtitle The TextView associated with the subtitle
*/
private class ItemViewHolder(val parent: View, var title: TextView, var subtitle: TextView? = null) : RecyclerView.ViewHolder(parent)
private class ItemViewHolder(val parent : View, var title : TextView, var subtitle : TextView? = null) : RecyclerView.ViewHolder(parent)
/**
* The ViewHolder used by headers is used to hold the views associated with an headers
@ -70,12 +70,12 @@ internal class LogAdapter internal constructor(val context: Context, val compact
* @param parent The parent view that contains all the others
* @param header The TextView associated with the header
*/
private class HeaderViewHolder(val parent: View, var header: TextView) : RecyclerView.ViewHolder(parent)
private class HeaderViewHolder(val parent : View, var header : TextView) : RecyclerView.ViewHolder(parent)
/**
* The onLongClick handler for the supplied [view], used to copy a log into the clipboard
*/
override fun onLongClick(view: View): Boolean {
override fun onLongClick(view : View) : Boolean {
val item = view.tag as LogItem
clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.message + " (" + item.level + ")"))
Toast.makeText(view.context, "Copied to clipboard", Toast.LENGTH_LONG).show()
@ -85,9 +85,9 @@ internal class LogAdapter internal constructor(val context: Context, val compact
/**
* This function creates the view-holder of type [viewType] with the layout parent as [parent]
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) : RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(context)
var holder: RecyclerView.ViewHolder? = null
var holder : RecyclerView.ViewHolder? = null
if (viewType == ElementType.Item.ordinal) {
if (compact) {
@ -112,7 +112,7 @@ internal class LogAdapter internal constructor(val context: Context, val compact
/**
* This function binds the item at [position] to the supplied [viewHolder]
*/
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
override fun onBindViewHolder(viewHolder : RecyclerView.ViewHolder, position : Int) {
val item = getItem(position)
if (item is LogItem) {

View File

@ -33,10 +33,10 @@ enum class RomFormat {
* @param uri The URL of the ROM
* @param contentResolver The instance of ContentResolver associated with the current context
*/
fun getRomFormat(uri: Uri, contentResolver: ContentResolver): RomFormat {
fun getRomFormat(uri : Uri, contentResolver : ContentResolver) : RomFormat {
var uriStr = ""
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val nameIndex : Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
uriStr = cursor.getString(nameIndex)
}
@ -50,26 +50,26 @@ class AppEntry : Serializable {
/**
* The name of the application
*/
var name: String
var name : String
/**
* The author of the application, if it can be extracted from the metadata
*/
var author: String? = null
var author : String? = null
var icon: Bitmap? = null
var icon : Bitmap? = null
/**
* The format of the application ROM
*/
var format: RomFormat
var format : RomFormat
/**
* The URI of the application ROM
*/
var uri: Uri
var uri : Uri
constructor(name: String, author: String, format: RomFormat, uri: Uri, icon: Bitmap) {
constructor(name : String, author : String, format : RomFormat, uri : Uri, icon : Bitmap) {
this.name = name
this.author = author
this.icon = icon
@ -77,9 +77,9 @@ class AppEntry : Serializable {
this.uri = uri
}
constructor(context: Context, format: RomFormat, uri: Uri) {
constructor(context : Context, format : RomFormat, uri : Uri) {
this.name = context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val nameIndex : Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
cursor.getString(nameIndex)
}!!.dropLast(format.name.length + 1)
@ -93,7 +93,7 @@ class AppEntry : Serializable {
* @param output The stream to which the object is written into
*/
@Throws(IOException::class)
private fun writeObject(output: ObjectOutputStream) {
private fun writeObject(output : ObjectOutputStream) {
output.writeUTF(name)
output.writeObject(format)
output.writeUTF(uri.toString())
@ -111,7 +111,7 @@ class AppEntry : Serializable {
* @param input The stream from which the object data is retrieved from
*/
@Throws(IOException::class, ClassNotFoundException::class)
private fun readObject(input: ObjectInputStream) {
private fun readObject(input : ObjectInputStream) {
name = input.readUTF()
format = input.readObject() as RomFormat
uri = Uri.parse(input.readUTF())
@ -125,14 +125,14 @@ class AppEntry : Serializable {
/**
* This class is used as the base class for all loaders
*/
internal abstract class BaseLoader(val context: Context, val format: RomFormat) {
internal abstract class BaseLoader(val context : Context, val format : RomFormat) {
/**
* This is used to get the [AppEntry] for the specified [file] at the supplied [uri]
*/
abstract fun getAppEntry(file: RandomAccessDocument, uri: Uri): AppEntry
abstract fun getAppEntry(file : RandomAccessDocument, uri : Uri) : AppEntry
/**
* This returns if the supplied [file] is a valid ROM or not
*/
abstract fun verifyFile(file: RandomAccessDocument): Boolean
abstract fun verifyFile(file : RandomAccessDocument) : Boolean
}

View File

@ -14,11 +14,11 @@ import java.io.IOException
/**
* This loader is used to load in NRO (Nintendo Relocatable Object) files (https://switchbrew.org/wiki/NRO)
*/
internal class NroLoader(context: Context) : BaseLoader(context, RomFormat.NRO) {
internal class NroLoader(context : Context) : BaseLoader(context, RomFormat.NRO) {
/**
* This is used to get the [AppEntry] for the specified NRO
*/
override fun getAppEntry(file: RandomAccessDocument, uri: Uri): AppEntry {
override fun getAppEntry(file : RandomAccessDocument, uri : Uri) : AppEntry {
return try {
file.seek(0x18) // Skip to NroHeader.size
@ -52,7 +52,7 @@ internal class NroLoader(context: Context) : BaseLoader(context, RomFormat.NRO)
file.read(author)
AppEntry(String(name).substringBefore((0.toChar())), String(author).substringBefore((0.toChar())), format, uri, icon)
} catch (e: IOException) {
} catch (e : IOException) {
AppEntry(context, format, uri)
}
}
@ -60,14 +60,14 @@ internal class NroLoader(context: Context) : BaseLoader(context, RomFormat.NRO)
/**
* This verifies if [file] is a valid NRO file
*/
override fun verifyFile(file: RandomAccessDocument): Boolean {
override fun verifyFile(file : RandomAccessDocument) : Boolean {
try {
file.seek(0x10) // Skip to NroHeader.magic
val buffer = ByteArray(4)
file.read(buffer)
if (String(buffer) != "NRO0") return false
} catch (e: IOException) {
} catch (e : IOException) {
return false
}
return true

View File

@ -18,7 +18,7 @@ class FolderActivity : AppCompatActivity() {
/**
* This launches the [Intent.ACTION_OPEN_DOCUMENT_TREE] intent on creation
*/
override fun onCreate(state: Bundle?) {
override fun onCreate(state : Bundle?) {
super.onCreate(state)
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
@ -28,7 +28,7 @@ class FolderActivity : AppCompatActivity() {
/**
* This changes the search location preference if the [Intent.ACTION_OPEN_DOCUMENT_TREE] has returned and [finish]es the activity
*/
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
public override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {

View File

@ -20,15 +20,15 @@ class FolderPreference : Preference {
/**
* The directory the preference is currently set to
*/
private var mDirectory: String? = null
private var mDirectory : String? = null
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int) : super(context, attrs, defStyleAttr) {
summaryProvider = SimpleSummaryProvider()
}
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, R.attr.preferenceStyle)
constructor(context : Context?, attrs : AttributeSet?) : this(context, attrs, R.attr.preferenceStyle)
constructor(context: Context?) : this(context, null)
constructor(context : Context?) : this(context, null)
/**
* This launches [FolderActivity] on click to change the directory
@ -41,7 +41,7 @@ class FolderPreference : Preference {
/**
* This sets the initial value of [mDirectory]
*/
override fun onSetInitialValue(defaultValue: Any?) {
override fun onSetInitialValue(defaultValue : Any?) {
mDirectory = getPersistedString(defaultValue as String?)
}
@ -52,7 +52,7 @@ class FolderPreference : Preference {
/**
* This returns the decoded URI of the directory as the summary
*/
override fun provideSummary(preference: FolderPreference): CharSequence {
override fun provideSummary(preference : FolderPreference) : CharSequence {
return Uri.decode(preference.mDirectory) ?: ""
}
}

View File

@ -22,11 +22,11 @@ class LicenseDialog : DialogFragment() {
/**
* 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 window: Window = activity!!.window
val window : Window = activity!!.window
window.decorView.getWindowVisibleDisplayFrame(displayRectangle)
layout.minimumWidth = ((displayRectangle.width() * 0.9f).toInt())
@ -38,7 +38,7 @@ class LicenseDialog : DialogFragment() {
/**
* 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)
license_url.text = arguments?.getString("libraryUrl")!!

View File

@ -20,7 +20,7 @@ class LicensePreference : Preference {
/**
* The [FragmentManager] is used to show the [LicenseDialog] fragment
*/
private val fragmentManager: FragmentManager
private val fragmentManager : FragmentManager
/**
* The tag used by this preference when launching a corresponding fragment
@ -30,17 +30,17 @@ class LicensePreference : Preference {
/**
* The URL of the library
*/
private var libraryUrl: String? = null
private var libraryUrl : String? = null
/**
* The contents of the license of this library
*/
private var libraryLicense: Int? = null
private var libraryLicense : Int? = null
/**
* 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) {
constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int, defStyleRes : Int) : super(context, attrs, defStyleAttr, defStyleRes) {
fragmentManager = (context as AppCompatActivity).supportFragmentManager
for (i in 0 until attrs!!.attributeCount) {
@ -53,11 +53,11 @@ class LicensePreference : Preference {
}
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, 0)
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?, attrs : AttributeSet?) : this(context, attrs, R.attr.dialogPreferenceStyle)
constructor(context: Context?) : this(context, null)
constructor(context : Context?) : this(context, null)
/**
* The [LicenseDialog] fragment is shown using [fragmentManager] on click with [libraryUrl] and [libraryLicense] passed as arguments

View File

@ -14,16 +14,16 @@ import androidx.preference.ListPreference
* This preference is used to set the theme to Light/Dark mode
*/
class ThemePreference : ListPreference {
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(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?, attrs : AttributeSet?) : super(context, attrs)
constructor(context: Context?) : super(context)
constructor(context : Context?) : super(context)
/**
* This changes [AppCompatDelegate.sDefaultNightMode] based on what the user's selection is
*/
override fun callChangeListener(newValue: Any?): Boolean {
override fun callChangeListener(newValue : Any?) : Boolean {
AppCompatDelegate.setDefaultNightMode(when ((newValue as String).toInt()) {
0 -> AppCompatDelegate.MODE_NIGHT_NO
1 -> AppCompatDelegate.MODE_NIGHT_YES

View File

@ -15,7 +15,7 @@ import java.nio.ByteBuffer
*
* @param parcelFileDescriptor The file descriptor for the [DocumentFile]
*/
class RandomAccessDocument(private var parcelFileDescriptor: ParcelFileDescriptor) {
class RandomAccessDocument(private var parcelFileDescriptor : ParcelFileDescriptor) {
/**
* The actual file descriptor for the [DocumentFile] as an [FileDescriptor] object
*/
@ -24,19 +24,19 @@ class RandomAccessDocument(private var parcelFileDescriptor: ParcelFileDescripto
/**
* The current position of where the file is being read
*/
private var position: Long = 0
private var position : Long = 0
/**
* The constructor sets [parcelFileDescriptor] by opening a read-only FD to [file]
*/
constructor(context: Context, file: DocumentFile) : this(context.contentResolver.openFileDescriptor(file.uri, "r")!!)
constructor(context : Context, file : DocumentFile) : this(context.contentResolver.openFileDescriptor(file.uri, "r")!!)
/**
* This reads in as many as possible bytes into [array] (Generally [array].size)
*
* @return The amount of bytes read from the file
*/
fun read(array: ByteArray): Int {
fun read(array : ByteArray) : Int {
val bytesRead = android.system.Os.pread(fileDescriptor, array, 0, array.size, position)
position += bytesRead
return bytesRead
@ -47,7 +47,7 @@ class RandomAccessDocument(private var parcelFileDescriptor: ParcelFileDescripto
*
* @return The amount of bytes read from the file
*/
fun read(buffer: ByteBuffer): Int {
fun read(buffer : ByteBuffer) : Int {
val bytesRead = android.system.Os.pread(fileDescriptor, buffer.array(), 0, buffer.array().size, position)
position += bytesRead
return bytesRead
@ -56,8 +56,8 @@ class RandomAccessDocument(private var parcelFileDescriptor: ParcelFileDescripto
/**
* This returns a single [Long] from the file at the current [position]
*/
fun readLong(): Long {
val buffer: ByteBuffer = ByteBuffer.allocate(Long.SIZE_BYTES)
fun readLong() : Long {
val buffer : ByteBuffer = ByteBuffer.allocate(Long.SIZE_BYTES)
read(buffer)
return buffer.long
}
@ -65,8 +65,8 @@ class RandomAccessDocument(private var parcelFileDescriptor: ParcelFileDescripto
/**
* This returns a single [Int] from the file at the current [position]
*/
fun readInt(): Int {
val buffer: ByteBuffer = ByteBuffer.allocate(Int.SIZE_BYTES)
fun readInt() : Int {
val buffer : ByteBuffer = ByteBuffer.allocate(Int.SIZE_BYTES)
read(buffer)
return buffer.int
}
@ -74,14 +74,14 @@ class RandomAccessDocument(private var parcelFileDescriptor: ParcelFileDescripto
/**
* This sets [RandomAccessDocument.position] to the supplied [position]
*/
fun seek(position: Long) {
fun seek(position : Long) {
this.position = position
}
/**
* This increments [position] by [amount]
*/
fun skipBytes(amount: Long) {
fun skipBytes(amount : Long) {
this.position += amount
}

View File

@ -4,6 +4,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:nextFocusRight="@id/game_play"
android:padding="16dp">
<com.google.android.material.imageview.ShapeableImageView
@ -11,6 +12,7 @@
android:layout_width="150dp"
android:layout_height="150dp"
android:contentDescription="@string/icon"
android:focusable="false"
app:shapeAppearanceOverlay="@style/roundedAppImage" />
<LinearLayout
@ -47,6 +49,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:focusedByDefault="true"
android:text="@string/play"
app:icon="@drawable/ic_play" />

View File

@ -16,38 +16,46 @@
card_view:cardCornerRadius="4dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_width="155dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_width="match_parent"
android:layout_height="155dp"
android:layout_alignParentTop="false"
android:layout_centerHorizontal="true"
android:contentDescription="@string/icon" />
android:contentDescription="@string/icon"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/text_title"
android:layout_width="150dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/icon"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:paddingStart="15dp"
android:paddingTop="15dp"
android:paddingTop="10dp"
android:paddingEnd="15dp"
android:singleLine="true"
android:textAlignment="center"
android:textAppearance="?android:attr/textAppearanceListItem"
tools:ignore="RelativeOverlap" />
<TextView
android:id="@+id/text_subtitle"
android:layout_width="150dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:paddingBottom="15dp"
android:singleLine="true"
android:textAlignment="center"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light" />

View File

@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
tools:context=".EmulationActivity">
<SurfaceView

View File

@ -28,7 +28,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:padding="8dp"
app:maxImageSize="26dp"
app:srcCompat="@drawable/ic_open" />
@ -37,7 +36,6 @@
style="@style/Widget.MaterialComponents.FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
app:maxImageSize="26dp"
app:srcCompat="@drawable/ic_log" />
</LinearLayout>

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar