mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-27 15:55:29 +03:00
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:
parent
f909c00e31
commit
4d787c904e
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) ?: ""
|
||||
}
|
||||
}
|
||||
|
@ -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")!!
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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" />
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user