diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index 48ef8b08..18b8f57c 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -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; diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index e8d2cbff..48749c88 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -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()}") diff --git a/app/src/main/java/emu/skyline/LogActivity.kt b/app/src/main/java/emu/skyline/LogActivity.kt index 66f14c7d..c86c41a6 100644 --- a/app/src/main/java/emu/skyline/LogActivity.kt +++ b/app/src/main/java/emu/skyline/LogActivity.kt @@ -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() - } } } diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt index de1255ad..54e488bd 100644 --- a/app/src/main/java/emu/skyline/MainActivity.kt +++ b/app/src/main/java/emu/skyline/MainActivity.kt @@ -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) } } } diff --git a/app/src/main/java/emu/skyline/SettingsActivity.kt b/app/src/main/java/emu/skyline/SettingsActivity.kt index 89627b90..cf0a27a4 100644 --- a/app/src/main/java/emu/skyline/SettingsActivity.kt +++ b/app/src/main/java/emu/skyline/SettingsActivity.kt @@ -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) } diff --git a/app/src/main/res/drawable/ic_open.xml b/app/src/main/res/drawable/ic_open.xml index b2353ced..12ac4182 100644 --- a/app/src/main/res/drawable/ic_open.xml +++ b/app/src/main/res/drawable/ic_open.xml @@ -1,9 +1,9 @@ + 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" /> diff --git a/app/src/main/res/layout/log_activity.xml b/app/src/main/res/layout/log_activity.xml index 5ff247b8..2e9e684b 100644 --- a/app/src/main/res/layout/log_activity.xml +++ b/app/src/main/res/layout/log_activity.xml @@ -1,22 +1,12 @@ - - + - + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index 44782500..5df194f0 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -1,61 +1,45 @@ - - + - - + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + android:layout_gravity="bottom|end" + android:layout_margin="16dp" + android:orientation="vertical"> - + diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml index a5275947..0466f167 100644 --- a/app/src/main/res/layout/settings_activity.xml +++ b/app/src/main/res/layout/settings_activity.xml @@ -4,17 +4,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4a5d488f..5406abd5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,24 +2,25 @@ Skyline Search + An error has occurred Settings Logger Refresh - The list of ROMs has been refreshed. Metadata Missing Icon Cannot find any ROMs Pin Play + Searching for ROMs Clear Share The log file was not found An I/O error has occurred - An error has occurred while sharing + The logs are being uploaded The logs have been cleared Emulator