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:
◱ PixelyIon 2020-04-13 01:59:19 +05:30 committed by ◱ PixelyIon
parent 0a5460f4fc
commit 4500f54e85
11 changed files with 216 additions and 140 deletions

View File

@ -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;

View File

@ -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()}")

View File

@ -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()
}
}
}

View File

@ -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,39 +30,49 @@ 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 ->
directory.listFiles().forEach { file ->
if (file.isDirectory) {
foundCurrent = findFile(ext, loader, file, foundCurrent)
foundCurrent = addEntries(extension, loader, file, foundCurrent)
} else {
if (ext.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
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
}
adapter.addItem(AppItem(entry))
}
}
document.close()
}
}
@ -73,7 +81,12 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
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)
}
}
}

View File

@ -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)
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View 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>

View File

@ -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>