mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-29 14: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++;
|
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;
|
Halt = false;
|
||||||
FaultCount = 0;
|
FaultCount = 0;
|
||||||
|
|
||||||
|
@ -18,9 +18,6 @@ import emu.skyline.loader.getRomFormat
|
|||||||
import kotlinx.android.synthetic.main.app_activity.*
|
import kotlinx.android.synthetic.main.app_activity.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
|
||||||
* This activity is used for emulation using libskyline
|
|
||||||
*/
|
|
||||||
class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("skyline") // libskyline.so
|
System.loadLibrary("skyline") // libskyline.so
|
||||||
@ -65,7 +62,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
|||||||
* @param preferenceFd The file descriptor of the Preference XML
|
* @param preferenceFd The file descriptor of the Preference XML
|
||||||
* @param logFd The file descriptor of the Log file
|
* @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
|
* 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
|
* @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
|
val romType = getRomFormat(rom, contentResolver).ordinal
|
||||||
romFd = contentResolver.openFileDescriptor(rom, "r")!!
|
romFd = contentResolver.openFileDescriptor(rom, "r")!!
|
||||||
|
|
||||||
@ -94,7 +91,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
|||||||
while ((surface == null))
|
while ((surface == null))
|
||||||
Thread.yield()
|
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)
|
if (shouldFinish)
|
||||||
runOnUiThread { finish() }
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.game_activity)
|
setContentView(R.layout.app_activity)
|
||||||
|
|
||||||
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
|
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
|
||||||
preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE)
|
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)
|
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
game_view.holder.addCallback(this)
|
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?) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
shouldFinish = false
|
shouldFinish = false
|
||||||
@ -136,13 +133,13 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
|||||||
|
|
||||||
romFd.close()
|
romFd.close()
|
||||||
|
|
||||||
executeRom(intent?.data!!)
|
executeApplication(intent?.data!!)
|
||||||
|
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The onDestroy handler is used to halt emulation
|
* This is used to halt emulation entirely
|
||||||
*/
|
*/
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
shouldFinish = false
|
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?) {
|
override fun surfaceCreated(holder: SurfaceHolder?) {
|
||||||
Log.d("surfaceCreated", "Holder: ${holder.toString()}")
|
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) {
|
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
|
||||||
Log.d("surfaceChanged", "Holder: ${holder.toString()}, Format: $format, Width: $width, Height: $height")
|
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?) {
|
override fun surfaceDestroyed(holder: SurfaceHolder?) {
|
||||||
Log.d("surfaceDestroyed", "Holder: ${holder.toString()}")
|
Log.d("surfaceDestroyed", "Holder: ${holder.toString()}")
|
||||||
|
@ -10,7 +10,6 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.ListView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
@ -18,23 +17,37 @@ import androidx.preference.PreferenceManager
|
|||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import emu.skyline.adapter.LogAdapter
|
import emu.skyline.adapter.LogAdapter
|
||||||
import kotlinx.android.synthetic.main.log_activity.*
|
import kotlinx.android.synthetic.main.log_activity.*
|
||||||
|
import kotlinx.android.synthetic.main.titlebar.*
|
||||||
import org.json.JSONObject
|
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.net.URL
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
class LogActivity : AppCompatActivity() {
|
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)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.log_activity)
|
setContentView(R.layout.log_activity)
|
||||||
setSupportActionBar(findViewById(R.id.toolbar))
|
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
@ -51,24 +64,30 @@ class LogActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logFile = File("${applicationInfo.dataDir}/skyline.log")
|
logFile = File("${applicationInfo.dataDir}/skyline.log")
|
||||||
|
|
||||||
logFile.forEachLine {
|
logFile.forEachLine {
|
||||||
adapter.add(it)
|
adapter.add(it)
|
||||||
}
|
}
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
Log.w("Logger", "IO Error during access of log file: " + e.message)
|
Log.w("Logger", "IO Error during access of log file: " + e.message)
|
||||||
Toast.makeText(applicationContext, getString(R.string.file_missing), Toast.LENGTH_LONG).show()
|
Toast.makeText(applicationContext, getString(R.string.file_missing), Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
finish()
|
finish()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.w("Logger", "IO Error during access of log file: " + e.message)
|
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 {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.toolbar_log, menu)
|
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.isSubmitButtonEnabled = false
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
searchView.isIconified = false
|
searchView.isIconified = false
|
||||||
@ -80,9 +99,13 @@ class LogActivity : AppCompatActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
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 {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.action_clear -> {
|
R.id.action_clear -> {
|
||||||
@ -90,57 +113,67 @@ class LogActivity : AppCompatActivity() {
|
|||||||
logFile.writeText("")
|
logFile.writeText("")
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.w("Logger", "IO Error while clearing the log file: " + e.message)
|
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()
|
Toast.makeText(applicationContext, getString(R.string.cleared), Toast.LENGTH_LONG).show()
|
||||||
finish()
|
finish()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_share_log -> {
|
R.id.action_share_log -> {
|
||||||
uploadAndShareLog()
|
uploadAndShareLog()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This uploads the logs and launches the [Intent.ACTION_SEND] intent
|
||||||
|
*/
|
||||||
private fun uploadAndShareLog() {
|
private fun uploadAndShareLog() {
|
||||||
|
Snackbar.make(findViewById(android.R.id.content), getString(R.string.upload_logs), Snackbar.LENGTH_SHORT).show()
|
||||||
|
|
||||||
val shareThread = Thread(Runnable {
|
val shareThread = Thread(Runnable {
|
||||||
var urlConnection: HttpsURLConnection? = null
|
var urlConnection: HttpsURLConnection? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val url = URL("https://hastebin.com/documents")
|
val url = URL("https://hastebin.com/documents")
|
||||||
|
|
||||||
urlConnection = url.openConnection() as HttpsURLConnection
|
urlConnection = url.openConnection() as HttpsURLConnection
|
||||||
urlConnection.requestMethod = "POST"
|
urlConnection.requestMethod = "POST"
|
||||||
urlConnection.setRequestProperty("Host", "hastebin.com")
|
urlConnection.setRequestProperty("Host", "hastebin.com")
|
||||||
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
|
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
|
||||||
urlConnection.setRequestProperty("Referer", "https://hastebin.com/")
|
urlConnection.setRequestProperty("Referer", "https://hastebin.com/")
|
||||||
|
|
||||||
val bufferedWriter = urlConnection.outputStream.bufferedWriter()
|
val bufferedWriter = urlConnection.outputStream.bufferedWriter()
|
||||||
bufferedWriter.write(logFile.readText())
|
bufferedWriter.write(logFile.readText())
|
||||||
bufferedWriter.flush()
|
bufferedWriter.flush()
|
||||||
bufferedWriter.close()
|
bufferedWriter.close()
|
||||||
|
|
||||||
if (urlConnection.responseCode != 200) {
|
if (urlConnection.responseCode != 200) {
|
||||||
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.responseCode)
|
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.responseCode)
|
||||||
throw Exception()
|
throw Exception()
|
||||||
}
|
}
|
||||||
|
|
||||||
val bufferedReader = urlConnection.inputStream.bufferedReader()
|
val bufferedReader = urlConnection.inputStream.bufferedReader()
|
||||||
val key = JSONObject(bufferedReader.readText()).getString("key")
|
val key = JSONObject(bufferedReader.readText()).getString("key")
|
||||||
bufferedReader.close()
|
bufferedReader.close()
|
||||||
|
|
||||||
val result = "https://hastebin.com/$key"
|
val result = "https://hastebin.com/$key"
|
||||||
val sharingIntent = Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result)
|
val sharingIntent = Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result)
|
||||||
|
|
||||||
startActivity(Intent.createChooser(sharingIntent, "Share log url with:"))
|
startActivity(Intent.createChooser(sharingIntent, "Share log url with:"))
|
||||||
} catch (e: Exception) {
|
} 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()
|
e.printStackTrace()
|
||||||
} finally {
|
} finally {
|
||||||
urlConnection!!.disconnect()
|
urlConnection!!.disconnect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
shareThread.start()
|
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.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
|
||||||
import android.widget.AdapterView.OnItemClickListener
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
@ -32,39 +30,49 @@ import emu.skyline.loader.NroLoader
|
|||||||
import emu.skyline.utility.AppDialog
|
import emu.skyline.utility.AppDialog
|
||||||
import emu.skyline.utility.RandomAccessDocument
|
import emu.skyline.utility.RandomAccessDocument
|
||||||
import kotlinx.android.synthetic.main.main_activity.*
|
import kotlinx.android.synthetic.main.main_activity.*
|
||||||
|
import kotlinx.android.synthetic.main.titlebar.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.math.ceil
|
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 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
|
var foundCurrent = found
|
||||||
|
|
||||||
directory.listFiles()
|
directory.listFiles().forEach { file ->
|
||||||
.forEach { file ->
|
|
||||||
if (file.isDirectory) {
|
if (file.isDirectory) {
|
||||||
foundCurrent = findFile(ext, loader, file, foundCurrent)
|
foundCurrent = addEntries(extension, loader, file, foundCurrent)
|
||||||
} else {
|
} else {
|
||||||
if (ext.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
|
if (extension.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
|
||||||
val document = RandomAccessDocument(this, file)
|
val document = RandomAccessDocument(this, file)
|
||||||
|
|
||||||
if (loader.verifyFile(document)) {
|
if (loader.verifyFile(document)) {
|
||||||
val entry = loader.getAppEntry(document, file.uri)
|
val entry = loader.getAppEntry(document, file.uri)
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (!foundCurrent) {
|
if (!foundCurrent) {
|
||||||
adapter.addHeader(loader.format.name)
|
adapter.addHeader(loader.format.name)
|
||||||
foundCurrent = true
|
foundCurrent = true
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.addItem(AppItem(entry))
|
adapter.addItem(AppItem(entry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.close()
|
document.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +81,12 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
return foundCurrent
|
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) {
|
if (tryLoad) {
|
||||||
try {
|
try {
|
||||||
adapter.load(File("${applicationInfo.dataDir}/roms.bin"))
|
adapter.load(File("${applicationInfo.dataDir}/roms.bin"))
|
||||||
@ -89,7 +102,8 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
runOnUiThread { adapter.clear() }
|
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 {
|
runOnUiThread {
|
||||||
if (!foundNros)
|
if (!foundNros)
|
||||||
@ -113,7 +127,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
runOnUiThread {
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.main_activity)
|
setContentView(R.layout.main_activity)
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
|
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
AppCompatDelegate.setDefaultNightMode(when ((sharedPreferences.getString("app_theme", "2")?.toInt())) {
|
AppCompatDelegate.setDefaultNightMode(when ((sharedPreferences.getString("app_theme", "2")?.toInt())) {
|
||||||
0 -> AppCompatDelegate.MODE_NIGHT_NO
|
0 -> AppCompatDelegate.MODE_NIGHT_NO
|
||||||
1 -> AppCompatDelegate.MODE_NIGHT_YES
|
1 -> AppCompatDelegate.MODE_NIGHT_YES
|
||||||
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
|
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
|
||||||
})
|
})
|
||||||
setSupportActionBar(toolbar)
|
|
||||||
open_fab.setOnClickListener(this)
|
open_fab.setOnClickListener(this)
|
||||||
log_fab.setOnClickListener(this)
|
log_fab.setOnClickListener(this)
|
||||||
|
|
||||||
@ -157,22 +179,25 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
|
|
||||||
app_list.layoutManager = layoutManager
|
app_list.layoutManager = layoutManager
|
||||||
}
|
}
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getString("search_location", "") == "") {
|
if (sharedPreferences.getString("search_location", "") == "") {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
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
|
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)
|
startActivityForResult(intent, 1)
|
||||||
} else
|
} 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 {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.toolbar_main, menu)
|
menuInflater.inflate(R.menu.toolbar_main, menu)
|
||||||
val mSearch = menu.findItem(R.id.action_search_main)
|
|
||||||
val searchView = mSearch.actionView as SearchView
|
val searchView = menu.findItem(R.id.action_search_main).actionView as SearchView
|
||||||
searchView.isSubmitButtonEnabled = false
|
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
searchView.clearFocus()
|
searchView.clearFocus()
|
||||||
@ -184,19 +209,26 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
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) {
|
override fun onClick(view: View) {
|
||||||
when (view.id) {
|
when (view.id) {
|
||||||
R.id.log_fab -> startActivity(Intent(this, LogActivity::class.java))
|
R.id.log_fab -> startActivity(Intent(this, LogActivity::class.java))
|
||||||
|
|
||||||
R.id.open_fab -> {
|
R.id.open_fab -> {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
intent.type = "*/*"
|
intent.type = "*/*"
|
||||||
|
|
||||||
startActivityForResult(intent, 2)
|
startActivityForResult(intent, 2)
|
||||||
}
|
}
|
||||||
R.id.app_item_linear -> {
|
|
||||||
|
R.id.app_item_linear, R.id.app_item_grid -> {
|
||||||
val tag = view.tag
|
val tag = view.tag
|
||||||
|
|
||||||
if (tag is AppItem) {
|
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 {
|
override fun onLongClick(view: View?): Boolean {
|
||||||
when (view?.id) {
|
when (view?.id) {
|
||||||
R.id.app_item_linear -> {
|
R.id.app_item_linear, R.id.app_item_grid -> {
|
||||||
val tag = view.tag
|
val tag = view.tag
|
||||||
|
|
||||||
if (tag is AppItem) {
|
if (tag is AppItem) {
|
||||||
@ -225,47 +260,58 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
return when (item.itemId) {
|
||||||
R.id.action_settings -> {
|
R.id.action_settings -> {
|
||||||
startActivityForResult(Intent(this, SettingsActivity::class.java), 3)
|
startActivityForResult(Intent(this, SettingsActivity::class.java), 3)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_refresh -> {
|
R.id.action_refresh -> {
|
||||||
refreshFiles(false)
|
refreshAdapter(false)
|
||||||
notifyUser(getString(R.string.refreshed))
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
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?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, intent)
|
super.onActivityResult(requestCode, resultCode, intent)
|
||||||
|
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
1 -> {
|
1 -> {
|
||||||
val uri = intent!!.data!!
|
val uri = intent!!.data!!
|
||||||
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
sharedPreferences.edit().putString("search_location", uri.toString()).apply()
|
sharedPreferences.edit().putString("search_location", uri.toString()).apply()
|
||||||
refreshFiles(!sharedPreferences.getBoolean("refresh_required", false))
|
|
||||||
|
refreshAdapter(!sharedPreferences.getBoolean("refresh_required", false))
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
try {
|
try {
|
||||||
val uri = (intent!!.data!!)
|
|
||||||
val intentGame = Intent(this, EmulationActivity::class.java)
|
val intentGame = Intent(this, EmulationActivity::class.java)
|
||||||
intentGame.data = uri
|
intentGame.data = intent!!.data!!
|
||||||
|
|
||||||
if (resultCode != 0)
|
if (resultCode != 0)
|
||||||
startActivityForResult(intentGame, resultCode)
|
startActivityForResult(intentGame, resultCode)
|
||||||
else
|
else
|
||||||
startActivity(intentGame)
|
startActivity(intentGame)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
notifyUser(e.message!!)
|
Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
3 -> {
|
3 -> {
|
||||||
if (sharedPreferences.getBoolean("refresh_required", false))
|
if (sharedPreferences.getBoolean("refresh_required", false))
|
||||||
refreshFiles(false)
|
refreshAdapter(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,33 +9,54 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import kotlinx.android.synthetic.main.log_activity.*
|
import kotlinx.android.synthetic.main.titlebar.*
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
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)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.settings_activity)
|
setContentView(R.layout.settings_activity)
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(R.id.settings, preferenceFragment)
|
.replace(R.id.settings, preferenceFragment)
|
||||||
.commit()
|
.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?) {
|
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
preferenceFragment.refreshPreferences()
|
preferenceFragment.refreshPreferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This fragment is used to display all of the preferences and handle refreshing the preferences
|
||||||
|
*/
|
||||||
class PreferenceFragment : PreferenceFragmentCompat() {
|
class PreferenceFragment : PreferenceFragmentCompat() {
|
||||||
|
/**
|
||||||
|
* This clears the preference screen and reloads all preferences
|
||||||
|
*/
|
||||||
fun refreshPreferences() {
|
fun refreshPreferences() {
|
||||||
preferenceScreen = null
|
preferenceScreen = null
|
||||||
addPreferencesFromResource(R.xml.preferences)
|
addPreferencesFromResource(R.xml.preferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="32dp"
|
||||||
android:height="24dp"
|
android:height="32dp"
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="?attr/colorOnSecondary"
|
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>
|
</vector>
|
||||||
|
@ -1,22 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".LogActivity">
|
tools:context=".LogActivity">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<include layout="@layout/titlebar" />
|
||||||
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" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/log_list"
|
android:id="@+id/log_list"
|
||||||
@ -24,6 +14,5 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fastScrollEnabled="true"
|
android:fastScrollEnabled="true"
|
||||||
android:transcriptMode="normal"
|
android:transcriptMode="normal"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
@ -1,61 +1,45 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<include layout="@layout/titlebar" />
|
||||||
android:id="@+id/toolbar"
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/app_list"
|
android:id="@+id/app_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/colorPrimary"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
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" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingBottom="8dp"
|
android:layout_gravity="bottom|end"
|
||||||
android:orientation="vertical"
|
android:layout_margin="16dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:orientation="vertical">
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/open_fab"
|
android:id="@+id/open_fab"
|
||||||
|
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:clickable="true"
|
android:padding="8dp"
|
||||||
android:focusable="true"
|
app:maxImageSize="26dp"
|
||||||
app:srcCompat="@drawable/ic_open" />
|
app:srcCompat="@drawable/ic_open" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/log_fab"
|
android:id="@+id/log_fab"
|
||||||
|
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="16dp"
|
android:padding="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
app:maxImageSize="26dp"
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:srcCompat="@drawable/ic_log" />
|
app:srcCompat="@drawable/ic_log" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@ -4,17 +4,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<include layout="@layout/titlebar"/>
|
||||||
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" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/settings"
|
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>
|
<string name="app_name">Skyline</string>
|
||||||
<!-- Common -->
|
<!-- Common -->
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
|
<string name="error">An error has occurred</string>
|
||||||
<!-- Toolbar Main -->
|
<!-- Toolbar Main -->
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="log">Logger</string>
|
<string name="log">Logger</string>
|
||||||
<string name="refresh">Refresh</string>
|
<string name="refresh">Refresh</string>
|
||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
<string name="refreshed">The list of ROMs has been refreshed.</string>
|
|
||||||
<string name="metadata_missing">Metadata Missing</string>
|
<string name="metadata_missing">Metadata Missing</string>
|
||||||
<string name="icon">Icon</string>
|
<string name="icon">Icon</string>
|
||||||
<string name="no_rom">Cannot find any ROMs</string>
|
<string name="no_rom">Cannot find any ROMs</string>
|
||||||
<string name="pin">Pin</string>
|
<string name="pin">Pin</string>
|
||||||
<string name="play">Play</string>
|
<string name="play">Play</string>
|
||||||
|
<string name="searching_roms">Searching for ROMs</string>
|
||||||
<!-- Toolbar Logger -->
|
<!-- Toolbar Logger -->
|
||||||
<string name="clear">Clear</string>
|
<string name="clear">Clear</string>
|
||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
<!-- Logger -->
|
<!-- Logger -->
|
||||||
<string name="file_missing">The log file was not found</string>
|
<string name="file_missing">The log file was not found</string>
|
||||||
<string name="io_error">An I/O error has occurred</string>
|
<string name="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>
|
<string name="cleared">The logs have been cleared</string>
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<string name="emulator">Emulator</string>
|
<string name="emulator">Emulator</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user