mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-28 08:05:29 +03:00
Refactor Activities and Improve MainActivity
This commit mainly refactors all activities to bring them in-line with the guidelines and makes certain improvements such as using `Snackbar`s rather than `Toast`s where possible, Using `CoordinatorLayout` to allow the app bar to hide itself when possible, the app bar has also been consolidated into it's own layout file to increase layout redraw performance as existing views can be used.
This commit is contained in:
parent
0a5460f4fc
commit
4500f54e85
@ -21,7 +21,7 @@ void signalHandler(int signal) {
|
||||
FaultCount++;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeRom(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jint logFd) {
|
||||
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jint logFd) {
|
||||
Halt = false;
|
||||
FaultCount = 0;
|
||||
|
||||
|
@ -18,9 +18,6 @@ import emu.skyline.loader.getRomFormat
|
||||
import kotlinx.android.synthetic.main.app_activity.*
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* This activity is used for emulation using libskyline
|
||||
*/
|
||||
class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
init {
|
||||
System.loadLibrary("skyline") // libskyline.so
|
||||
@ -65,7 +62,7 @@ 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 executeRom(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
|
||||
@ -86,7 +83,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
*
|
||||
* @param rom The URI of the ROM to execute
|
||||
*/
|
||||
private fun executeRom(rom : Uri) {
|
||||
private fun executeApplication(rom: Uri) {
|
||||
val romType = getRomFormat(rom, contentResolver).ordinal
|
||||
romFd = contentResolver.openFileDescriptor(rom, "r")!!
|
||||
|
||||
@ -94,7 +91,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
while ((surface == null))
|
||||
Thread.yield()
|
||||
|
||||
executeRom(Uri.decode(rom.toString()), romType, romFd.fd, preferenceFd.fd, logFd.fd)
|
||||
executeApplication(Uri.decode(rom.toString()), romType, romFd.fd, preferenceFd.fd, logFd.fd)
|
||||
|
||||
if (shouldFinish)
|
||||
runOnUiThread { finish() }
|
||||
@ -104,12 +101,12 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
/**
|
||||
* The onCreate handler for the activity, it sets up the FDs and calls [executeRom]
|
||||
* This sets up [preferenceFd] and [logFd] then calls [executeApplication] for executing the application
|
||||
*/
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.game_activity)
|
||||
setContentView(R.layout.app_activity)
|
||||
|
||||
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
|
||||
preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE)
|
||||
@ -120,11 +117,11 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
game_view.holder.addCallback(this)
|
||||
|
||||
executeRom(intent.data!!)
|
||||
executeApplication(intent.data!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* The onNewIntent handler is used to stop the currently executing ROM and replace it with the one specified in the new intent
|
||||
* 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?) {
|
||||
shouldFinish = false
|
||||
@ -136,13 +133,13 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
|
||||
romFd.close()
|
||||
|
||||
executeRom(intent?.data!!)
|
||||
executeApplication(intent?.data!!)
|
||||
|
||||
super.onNewIntent(intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* The onDestroy handler is used to halt emulation
|
||||
* This is used to halt emulation entirely
|
||||
*/
|
||||
override fun onDestroy() {
|
||||
shouldFinish = false
|
||||
@ -158,7 +155,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
/**
|
||||
* The surfaceCreated handler passes in the surface to libskyline
|
||||
* This sets [surface] to [holder].surface and passes it into libskyline
|
||||
*/
|
||||
override fun surfaceCreated(holder: SurfaceHolder?) {
|
||||
Log.d("surfaceCreated", "Holder: ${holder.toString()}")
|
||||
@ -167,14 +164,14 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
/**
|
||||
* The surfaceChanged handler is purely used for debugging purposes
|
||||
* This is purely used for debugging surface changes
|
||||
*/
|
||||
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
|
||||
Log.d("surfaceChanged", "Holder: ${holder.toString()}, Format: $format, Width: $width, Height: $height")
|
||||
}
|
||||
|
||||
/**
|
||||
* The surfaceDestroyed handler passes sets the surface to null
|
||||
* This sets [surface] to null and passes it into libskyline
|
||||
*/
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder?) {
|
||||
Log.d("surfaceDestroyed", "Holder: ${holder.toString()}")
|
||||
|
@ -10,7 +10,6 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.ListView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SearchView
|
||||
@ -18,23 +17,37 @@ import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import emu.skyline.adapter.LogAdapter
|
||||
import kotlinx.android.synthetic.main.log_activity.*
|
||||
import kotlinx.android.synthetic.main.titlebar.*
|
||||
import org.json.JSONObject
|
||||
import java.io.*
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.stream.Collectors
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
class LogActivity : AppCompatActivity() {
|
||||
/**
|
||||
* The log file is used to read log entries from or to clear all entries
|
||||
*/
|
||||
private lateinit var logFile: File
|
||||
|
||||
/**
|
||||
* The adapter used for adding elements from the log to [log_list]
|
||||
*/
|
||||
private lateinit var adapter: LogAdapter
|
||||
|
||||
/**
|
||||
* This initializes [toolbar] and fills [log_list] with data from the logs
|
||||
*/
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.log_activity)
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
@ -51,24 +64,30 @@ class LogActivity : AppCompatActivity() {
|
||||
|
||||
try {
|
||||
logFile = File("${applicationInfo.dataDir}/skyline.log")
|
||||
|
||||
logFile.forEachLine {
|
||||
adapter.add(it)
|
||||
}
|
||||
} 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) {
|
||||
Log.w("Logger", "IO Error during access of log file: " + e.message)
|
||||
Toast.makeText(applicationContext, getString(R.string.io_error) + ": " + e.message, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(applicationContext, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This inflates the layout for the menu [R.menu.toolbar_log] and sets up searching the logs
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.toolbar_log, menu)
|
||||
val mSearch = menu.findItem(R.id.action_search_log)
|
||||
val searchView = mSearch.actionView as SearchView
|
||||
|
||||
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 {
|
||||
searchView.isIconified = false
|
||||
@ -80,9 +99,13 @@ class LogActivity : AppCompatActivity() {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles menu selection for [R.id.action_clear] and [R.id.action_share_log]
|
||||
*/
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_clear -> {
|
||||
@ -90,57 +113,67 @@ class LogActivity : AppCompatActivity() {
|
||||
logFile.writeText("")
|
||||
} catch (e: IOException) {
|
||||
Log.w("Logger", "IO Error while clearing the log file: " + e.message)
|
||||
Toast.makeText(applicationContext, getString(R.string.io_error) + ": " + e.message, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(applicationContext, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
Toast.makeText(applicationContext, getString(R.string.cleared), Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_share_log -> {
|
||||
uploadAndShareLog()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This uploads the logs and launches the [Intent.ACTION_SEND] intent
|
||||
*/
|
||||
private fun uploadAndShareLog() {
|
||||
Snackbar.make(findViewById(android.R.id.content), getString(R.string.upload_logs), Snackbar.LENGTH_SHORT).show()
|
||||
|
||||
val shareThread = Thread(Runnable {
|
||||
var urlConnection: HttpsURLConnection? = null
|
||||
|
||||
try {
|
||||
val url = URL("https://hastebin.com/documents")
|
||||
|
||||
urlConnection = url.openConnection() as HttpsURLConnection
|
||||
urlConnection.requestMethod = "POST"
|
||||
urlConnection.setRequestProperty("Host", "hastebin.com")
|
||||
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
|
||||
urlConnection.setRequestProperty("Referer", "https://hastebin.com/")
|
||||
|
||||
val bufferedWriter = urlConnection.outputStream.bufferedWriter()
|
||||
bufferedWriter.write(logFile.readText())
|
||||
bufferedWriter.flush()
|
||||
bufferedWriter.close()
|
||||
|
||||
if (urlConnection.responseCode != 200) {
|
||||
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.responseCode)
|
||||
throw Exception()
|
||||
}
|
||||
|
||||
val bufferedReader = urlConnection.inputStream.bufferedReader()
|
||||
val key = JSONObject(bufferedReader.readText()).getString("key")
|
||||
bufferedReader.close()
|
||||
|
||||
val result = "https://hastebin.com/$key"
|
||||
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) {
|
||||
runOnUiThread { Toast.makeText(applicationContext, getString(R.string.share_error), Toast.LENGTH_LONG).show() }
|
||||
runOnUiThread { Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_LONG).show() }
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
urlConnection!!.disconnect()
|
||||
}
|
||||
})
|
||||
|
||||
shareThread.start()
|
||||
try {
|
||||
shareThread.join(1000)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(applicationContext, getString(R.string.share_error), Toast.LENGTH_LONG).show()
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,6 @@ import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.AdapterView.OnItemClickListener
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.widget.SearchView
|
||||
@ -32,48 +30,63 @@ 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.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.math.ceil
|
||||
|
||||
class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClickListener {
|
||||
/**
|
||||
* This is used to get/set shared preferences
|
||||
*/
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private var adapter = AppAdapter(this)
|
||||
|
||||
private fun notifyUser(text: String) {
|
||||
Snackbar.make(findViewById(android.R.id.content), text, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
/**
|
||||
* The adapter used for adding elements to [app_list]
|
||||
*/
|
||||
private lateinit var adapter: AppAdapter
|
||||
|
||||
private fun findFile(ext: String, loader: BaseLoader, directory: DocumentFile, found: Boolean = false): Boolean {
|
||||
/**
|
||||
* 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 {
|
||||
var foundCurrent = found
|
||||
|
||||
directory.listFiles()
|
||||
.forEach { file ->
|
||||
if (file.isDirectory) {
|
||||
foundCurrent = findFile(ext, loader, file, foundCurrent)
|
||||
} else {
|
||||
if (ext.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
|
||||
val document = RandomAccessDocument(this, file)
|
||||
if (loader.verifyFile(document)) {
|
||||
val entry = loader.getAppEntry(document, file.uri)
|
||||
runOnUiThread {
|
||||
if (!foundCurrent) {
|
||||
adapter.addHeader(loader.format.name)
|
||||
foundCurrent = true
|
||||
}
|
||||
adapter.addItem(AppItem(entry))
|
||||
}
|
||||
directory.listFiles().forEach { file ->
|
||||
if (file.isDirectory) {
|
||||
foundCurrent = addEntries(extension, loader, file, foundCurrent)
|
||||
} else {
|
||||
if (extension.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
|
||||
val document = RandomAccessDocument(this, file)
|
||||
|
||||
if (loader.verifyFile(document)) {
|
||||
val entry = loader.getAppEntry(document, file.uri)
|
||||
|
||||
runOnUiThread {
|
||||
if (!foundCurrent) {
|
||||
adapter.addHeader(loader.format.name)
|
||||
foundCurrent = true
|
||||
}
|
||||
document.close()
|
||||
|
||||
adapter.addItem(AppItem(entry))
|
||||
}
|
||||
}
|
||||
|
||||
document.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundCurrent
|
||||
}
|
||||
|
||||
private fun refreshFiles(tryLoad: Boolean) {
|
||||
/**
|
||||
* This refreshes the contents of the adapter by either trying to load cached adapter data or searches for them to recreate a list
|
||||
*
|
||||
* @param tryLoad If this is false then trying to load cached adapter data is skipped entirely
|
||||
*/
|
||||
private fun refreshAdapter(tryLoad: Boolean) {
|
||||
if (tryLoad) {
|
||||
try {
|
||||
adapter.load(File("${applicationInfo.dataDir}/roms.bin"))
|
||||
@ -89,7 +102,8 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
|
||||
try {
|
||||
runOnUiThread { adapter.clear() }
|
||||
val foundNros = findFile("nro", NroLoader(this), DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
|
||||
|
||||
val foundNros = addEntries("nro", NroLoader(this), DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
|
||||
|
||||
runOnUiThread {
|
||||
if (!foundNros)
|
||||
@ -113,7 +127,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
runOnUiThread {
|
||||
notifyUser(e.message!!)
|
||||
Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,18 +135,26 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This initializes [toolbar], [open_fab], [log_fab] and [app_list]
|
||||
*/
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.main_activity)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(when ((sharedPreferences.getString("app_theme", "2")?.toInt())) {
|
||||
0 -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
1 -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
|
||||
})
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
open_fab.setOnClickListener(this)
|
||||
log_fab.setOnClickListener(this)
|
||||
|
||||
@ -157,22 +179,25 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
|
||||
app_list.layoutManager = layoutManager
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
startActivityForResult(intent, 1)
|
||||
} else
|
||||
refreshFiles(!sharedPreferences.getBoolean("refresh_required", false))
|
||||
refreshAdapter(!sharedPreferences.getBoolean("refresh_required", false))
|
||||
}
|
||||
|
||||
/**
|
||||
* This inflates the layout for the menu [R.menu.toolbar_main] and sets up searching the logs
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.toolbar_main, menu)
|
||||
val mSearch = menu.findItem(R.id.action_search_main)
|
||||
val searchView = mSearch.actionView as SearchView
|
||||
searchView.isSubmitButtonEnabled = false
|
||||
|
||||
val searchView = menu.findItem(R.id.action_search_main).actionView as SearchView
|
||||
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
searchView.clearFocus()
|
||||
@ -184,19 +209,26 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
when (view.id) {
|
||||
R.id.log_fab -> startActivity(Intent(this, LogActivity::class.java))
|
||||
|
||||
R.id.open_fab -> {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "*/*"
|
||||
|
||||
startActivityForResult(intent, 2)
|
||||
}
|
||||
R.id.app_item_linear -> {
|
||||
|
||||
R.id.app_item_linear, R.id.app_item_grid -> {
|
||||
val tag = view.tag
|
||||
|
||||
if (tag is AppItem) {
|
||||
@ -209,9 +241,12 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles long-click interaction with [R.id.app_item_linear] and [R.id.app_item_grid]
|
||||
*/
|
||||
override fun onLongClick(view: View?): Boolean {
|
||||
when (view?.id) {
|
||||
R.id.app_item_linear -> {
|
||||
R.id.app_item_linear, R.id.app_item_grid -> {
|
||||
val tag = view.tag
|
||||
|
||||
if (tag is AppItem) {
|
||||
@ -225,47 +260,58 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles menu interaction for [R.id.action_settings] and [R.id.action_refresh]
|
||||
*/
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_settings -> {
|
||||
startActivityForResult(Intent(this, SettingsActivity::class.java), 3)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_refresh -> {
|
||||
refreshFiles(false)
|
||||
notifyUser(getString(R.string.refreshed))
|
||||
refreshAdapter(false)
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles receiving activity result from [Intent.ACTION_OPEN_DOCUMENT_TREE], [Intent.ACTION_OPEN_DOCUMENT] and [SettingsActivity]
|
||||
*/
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, intent)
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
when (requestCode) {
|
||||
1 -> {
|
||||
val uri = intent!!.data!!
|
||||
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
sharedPreferences.edit().putString("search_location", uri.toString()).apply()
|
||||
refreshFiles(!sharedPreferences.getBoolean("refresh_required", false))
|
||||
|
||||
refreshAdapter(!sharedPreferences.getBoolean("refresh_required", false))
|
||||
}
|
||||
|
||||
2 -> {
|
||||
try {
|
||||
val uri = (intent!!.data!!)
|
||||
val intentGame = Intent(this, EmulationActivity::class.java)
|
||||
intentGame.data = uri
|
||||
intentGame.data = intent!!.data!!
|
||||
|
||||
if (resultCode != 0)
|
||||
startActivityForResult(intentGame, resultCode)
|
||||
else
|
||||
startActivity(intentGame)
|
||||
} catch (e: Exception) {
|
||||
notifyUser(e.message!!)
|
||||
Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
3 -> {
|
||||
if (sharedPreferences.getBoolean("refresh_required", false))
|
||||
refreshFiles(false)
|
||||
refreshAdapter(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,33 +9,54 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import kotlinx.android.synthetic.main.log_activity.*
|
||||
import kotlinx.android.synthetic.main.titlebar.*
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
/**
|
||||
* This is the instance of [PreferenceFragment] that is shown inside [R.id.settings]
|
||||
*/
|
||||
private val preferenceFragment: PreferenceFragment = PreferenceFragment()
|
||||
|
||||
/**
|
||||
* This initializes [toolbar] and [R.id.settings]
|
||||
*/
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.settings_activity)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, preferenceFragment)
|
||||
.commit()
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to refresh the preferences after [emu.skyline.preference.FolderActivity] has returned
|
||||
*/
|
||||
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
preferenceFragment.refreshPreferences()
|
||||
}
|
||||
|
||||
/**
|
||||
* This fragment is used to display all of the preferences and handle refreshing the preferences
|
||||
*/
|
||||
class PreferenceFragment : PreferenceFragmentCompat() {
|
||||
/**
|
||||
* This clears the preference screen and reloads all preferences
|
||||
*/
|
||||
fun refreshPreferences() {
|
||||
preferenceScreen = null
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructs the preferences from [R.xml.preferences]
|
||||
*/
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="?attr/colorOnSecondary"
|
||||
android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2L5,18L5,8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM12,10l-4,4h3v6h2v-6h3l-4,-4z" />
|
||||
android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z" />
|
||||
</vector>
|
||||
|
@ -1,22 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".LogActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/AppTheme.ActionBar"
|
||||
app:popupTheme="@style/AppTheme.PopupMenu"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<include layout="@layout/titlebar" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/log_list"
|
||||
@ -24,6 +14,5 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:fastScrollEnabled="true"
|
||||
android:transcriptMode="normal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
@ -1,61 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
<include layout="@layout/titlebar" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/app_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/AppTheme.ActionBar"
|
||||
app:popupTheme="@style/AppTheme.PopupMenu"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/game_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/open_fab"
|
||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
app:maxImageSize="26dp"
|
||||
app:srcCompat="@drawable/ic_open" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/log_fab"
|
||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
app:maxImageSize="26dp"
|
||||
app:srcCompat="@drawable/ic_log" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
@ -4,17 +4,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/AppTheme.ActionBar"
|
||||
app:popupTheme="@style/AppTheme.PopupMenu"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<include layout="@layout/titlebar"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/settings"
|
||||
|
15
app/src/main/res/layout/titlebar.xml
Normal file
15
app/src/main/res/layout/titlebar.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:liftOnScroll="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||
android:theme="@style/AppTheme.ActionBar" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
@ -2,24 +2,25 @@
|
||||
<string name="app_name">Skyline</string>
|
||||
<!-- Common -->
|
||||
<string name="search">Search</string>
|
||||
<string name="error">An error has occurred</string>
|
||||
<!-- Toolbar Main -->
|
||||
<string name="settings">Settings</string>
|
||||
<string name="log">Logger</string>
|
||||
<string name="refresh">Refresh</string>
|
||||
<!-- Main -->
|
||||
<string name="refreshed">The list of ROMs has been refreshed.</string>
|
||||
<string name="metadata_missing">Metadata Missing</string>
|
||||
<string name="icon">Icon</string>
|
||||
<string name="no_rom">Cannot find any ROMs</string>
|
||||
<string name="pin">Pin</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="searching_roms">Searching for ROMs</string>
|
||||
<!-- Toolbar Logger -->
|
||||
<string name="clear">Clear</string>
|
||||
<string name="share">Share</string>
|
||||
<!-- Logger -->
|
||||
<string name="file_missing">The log file was not found</string>
|
||||
<string name="io_error">An I/O error has occurred</string>
|
||||
<string name="share_error">An error has occurred while sharing</string>
|
||||
<string name="upload_logs">The logs are being uploaded</string>
|
||||
<string name="cleared">The logs have been cleared</string>
|
||||
<!-- Settings -->
|
||||
<string name="emulator">Emulator</string>
|
||||
|
Loading…
Reference in New Issue
Block a user