mirror of
https://github.com/izzy2lost/Super3.git
synced 2026-07-05 15:18:38 -07:00
ui improvements
This commit is contained in:
@@ -63,7 +63,8 @@ dependencies {
|
||||
implementation("androidx.core:core-ktx:1.15.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.4.0")
|
||||
implementation("com.google.android.material:material:1.14.0-alpha07")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -3,27 +3,53 @@ package com.izzy2lost.super3
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.search.SearchBar
|
||||
import com.google.android.material.search.SearchView
|
||||
import java.io.File
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val prefs by lazy { getSharedPreferences("super3_prefs", MODE_PRIVATE) }
|
||||
|
||||
private lateinit var drawerLayout: DrawerLayout
|
||||
private lateinit var navigationView: NavigationView
|
||||
private lateinit var toolbar: MaterialToolbar
|
||||
private lateinit var searchBar: SearchBar
|
||||
private lateinit var searchView: SearchView
|
||||
|
||||
private lateinit var gamesFolderText: TextView
|
||||
private lateinit var userFolderText: TextView
|
||||
private lateinit var gamesList: ListView
|
||||
private lateinit var gamesList: RecyclerView
|
||||
private lateinit var searchResultsList: RecyclerView
|
||||
private lateinit var statusText: TextView
|
||||
|
||||
private lateinit var gamesAdapter: GamesAdapter
|
||||
|
||||
private var gamesTreeUri: Uri? = null
|
||||
private var userTreeUri: Uri? = null
|
||||
|
||||
private var games: List<GameDef> = emptyList()
|
||||
private var zipDocs: Map<String, DocumentFile> = emptyMap()
|
||||
|
||||
@Volatile
|
||||
private var scanning = false
|
||||
|
||||
private val pickGamesFolder =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
||||
if (uri != null) {
|
||||
@@ -49,28 +75,69 @@ class MainActivity : AppCompatActivity() {
|
||||
applyImmersiveMode()
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
gamesFolderText = findViewById(R.id.games_folder_text)
|
||||
userFolderText = findViewById(R.id.user_folder_text)
|
||||
drawerLayout = findViewById(R.id.drawer_layout)
|
||||
navigationView = findViewById(R.id.navigation_view)
|
||||
toolbar = findViewById(R.id.toolbar)
|
||||
searchBar = findViewById(R.id.search_bar)
|
||||
searchView = findViewById(R.id.search_view)
|
||||
gamesList = findViewById(R.id.games_list)
|
||||
statusText = findViewById(R.id.status_text)
|
||||
searchResultsList = findViewById(R.id.search_results_list)
|
||||
|
||||
findViewById<Button>(R.id.pick_games_folder).setOnClickListener { pickGamesFolder.launch(null) }
|
||||
findViewById<Button>(R.id.pick_user_folder).setOnClickListener { pickUserFolder.launch(null) }
|
||||
findViewById<Button>(R.id.rescan_button).setOnClickListener { refreshUi() }
|
||||
// Get views from the navigation header
|
||||
val headerView = navigationView.getHeaderView(0)
|
||||
gamesFolderText = headerView.findViewById(R.id.games_folder_text)
|
||||
userFolderText = headerView.findViewById(R.id.user_folder_text)
|
||||
statusText = headerView.findViewById(R.id.status_text)
|
||||
|
||||
gamesList.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
|
||||
val item = (gamesList.adapter as GameAdapter).getItem(position) ?: return@OnItemClickListener
|
||||
val btnPickGamesFolder: MaterialButton = headerView.findViewById(R.id.btn_pick_games_folder)
|
||||
val btnPickUserFolder: MaterialButton = headerView.findViewById(R.id.btn_pick_user_folder)
|
||||
val btnRescan: MaterialButton = headerView.findViewById(R.id.btn_rescan)
|
||||
|
||||
gamesAdapter = GamesAdapter { item ->
|
||||
if (!item.launchable) {
|
||||
Toast.makeText(this, item.status, Toast.LENGTH_SHORT).show()
|
||||
return@OnItemClickListener
|
||||
return@GamesAdapter
|
||||
}
|
||||
launchGame(item.game)
|
||||
}
|
||||
gamesList.adapter = gamesAdapter
|
||||
searchResultsList.adapter = gamesAdapter
|
||||
|
||||
toolbar.setNavigationOnClickListener {
|
||||
drawerLayout.openDrawer(GravityCompat.START)
|
||||
}
|
||||
|
||||
btnPickGamesFolder.setOnClickListener { pickGamesFolder.launch(null) }
|
||||
btnPickUserFolder.setOnClickListener { pickUserFolder.launch(null) }
|
||||
btnRescan.setOnClickListener { refreshUi() }
|
||||
|
||||
runCatching { searchView.setupWithSearchBar(searchBar) }
|
||||
searchBar.setOnClickListener {
|
||||
searchView.show()
|
||||
searchView.requestFocusAndShowKeyboard()
|
||||
}
|
||||
searchView.setAutoShowKeyboard(true)
|
||||
|
||||
val onSurface = MaterialColors.getColor(searchView, com.google.android.material.R.attr.colorOnSurface)
|
||||
val onSurfaceVariant =
|
||||
MaterialColors.getColor(searchView, com.google.android.material.R.attr.colorOnSurfaceVariant)
|
||||
searchView.editText.setTextColor(onSurface)
|
||||
searchView.editText.setHintTextColor(onSurfaceVariant)
|
||||
|
||||
searchView.addTransitionListener { _, _, newState ->
|
||||
val searchActive = newState != SearchView.TransitionState.HIDDEN
|
||||
gamesList.visibility = if (searchActive) View.GONE else View.VISIBLE
|
||||
if (newState == SearchView.TransitionState.SHOWING || newState == SearchView.TransitionState.SHOWN) {
|
||||
searchView.requestFocusAndShowKeyboard()
|
||||
}
|
||||
}
|
||||
searchView.editText.addTextChangedListener { editable ->
|
||||
gamesAdapter.setFilter(editable?.toString().orEmpty())
|
||||
}
|
||||
|
||||
loadPrefs()
|
||||
games = GameXml.parseGamesXmlFromAssets(this)
|
||||
|
||||
// Ensure internal user root has the required files from APK assets (Config/Games.xml, Assets/, etc.).
|
||||
AssetInstaller.ensureInstalled(this, internalUserRoot())
|
||||
|
||||
refreshUi()
|
||||
@@ -87,7 +154,6 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun internalUserRoot(): File {
|
||||
// Matches native default: <externalFiles>/super3
|
||||
return File(getExternalFilesDir(null), "super3")
|
||||
}
|
||||
|
||||
@@ -100,16 +166,29 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun refreshUi() {
|
||||
gamesFolderText.text = gamesTreeUri?.toString() ?: "Not set"
|
||||
userFolderText.text = userTreeUri?.toString() ?: "Not set"
|
||||
gamesFolderText.text = "Games folder: ${gamesTreeUri?.toString() ?: "Not set"}"
|
||||
userFolderText.text = "Data folder: ${userTreeUri?.toString() ?: "Not set"}"
|
||||
val gamesUri = gamesTreeUri
|
||||
if (gamesUri == null) {
|
||||
zipDocs = emptyMap()
|
||||
gamesAdapter.submitList(buildItems(games, zipDocs))
|
||||
statusText.text = "Games folder not set. Tap \"Games folder\" to choose."
|
||||
scanning = false
|
||||
return
|
||||
}
|
||||
|
||||
if (scanning) return
|
||||
scanning = true
|
||||
statusText.text = "Scanning…"
|
||||
|
||||
thread(name = "Super3Scanner") {
|
||||
val zips = scanZipDocs(gamesTreeUri)
|
||||
val zips = scanZipDocs(gamesUri)
|
||||
val items = buildItems(games, zips)
|
||||
runOnUiThread {
|
||||
zipDocs = zips
|
||||
gamesAdapter.submitList(items)
|
||||
statusText.text = "Found ${zipDocs.size} ZIP(s). Tap a game to launch."
|
||||
gamesList.adapter = GameAdapter(this, games, zipDocs)
|
||||
scanning = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +220,6 @@ class MainActivity : AppCompatActivity() {
|
||||
val userUri = userTreeUri
|
||||
|
||||
if (userUri != null) {
|
||||
// Pull saved NVRAM/Saves/Config from user folder into internal root before launch.
|
||||
UserDataSync.syncFromTreeIntoInternal(this, userUri, internalRoot)
|
||||
}
|
||||
|
||||
@@ -151,12 +229,15 @@ class MainActivity : AppCompatActivity() {
|
||||
if (missing.isNotEmpty()) {
|
||||
runOnUiThread {
|
||||
statusText.text = "Missing required ZIP(s): ${missing.joinToString(", ")}"
|
||||
Toast.makeText(this, "Missing required ZIP(s): ${missing.joinToString(", ")}", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(
|
||||
this,
|
||||
"Missing required ZIP(s): ${missing.joinToString(", ")}",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
return@thread
|
||||
}
|
||||
|
||||
// Copy selected game zip + required parent chain zips into cacheDir so GameLoader can find them.
|
||||
for (zipBase in required) {
|
||||
val doc = zipDocs[zipBase] ?: continue
|
||||
val ok = copyDocToCacheIfNeeded(doc, File(cacheDir, "$zipBase.zip"))
|
||||
@@ -170,8 +251,6 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
val romPath = File(cacheDir, "${game.name}.zip").absolutePath
|
||||
|
||||
// Ensure Games.xml exists on disk for the native loader.
|
||||
val gamesXmlPath = File(internalRoot, "Config/Games.xml").absolutePath
|
||||
val userDataRoot = internalRoot.absolutePath
|
||||
|
||||
@@ -210,8 +289,7 @@ class MainActivity : AppCompatActivity() {
|
||||
if (outFile.exists() && expectedSize > 0 && outFile.length() == expectedSize) {
|
||||
return true
|
||||
}
|
||||
val uri = doc.uri
|
||||
val input = contentResolver.openInputStream(uri) ?: return false
|
||||
val input = contentResolver.openInputStream(doc.uri) ?: return false
|
||||
input.use { ins ->
|
||||
outFile.outputStream().use { outs ->
|
||||
ins.copyTo(outs)
|
||||
@@ -221,25 +299,6 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private class GameAdapter(
|
||||
context: MainActivity,
|
||||
private val games: List<GameDef>,
|
||||
zipDocs: Map<String, DocumentFile>,
|
||||
) : ArrayAdapter<GameItem>(context, android.R.layout.simple_list_item_2, buildItems(games, zipDocs)) {
|
||||
override fun getView(position: Int, convertView: android.view.View?, parent: android.view.ViewGroup): android.view.View {
|
||||
val v = convertView ?: android.view.LayoutInflater.from(context)
|
||||
.inflate(android.R.layout.simple_list_item_2, parent, false)
|
||||
val item = getItem(position)!!
|
||||
val t1 = v.findViewById<TextView>(android.R.id.text1)
|
||||
val t2 = v.findViewById<TextView>(android.R.id.text2)
|
||||
t1.text = item.game.displayName
|
||||
t2.text = item.status
|
||||
t1.isEnabled = item.launchable
|
||||
t2.isEnabled = item.launchable
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
private data class GameItem(val game: GameDef, val launchable: Boolean, val status: String)
|
||||
|
||||
private fun buildItems(games: List<GameDef>, zipDocs: Map<String, DocumentFile>): List<GameItem> {
|
||||
@@ -263,14 +322,75 @@ private fun buildItems(games: List<GameDef>, zipDocs: Map<String, DocumentFile>)
|
||||
val req = requiredZips(g)
|
||||
val missing = req.filter { !zipDocs.containsKey(it) }
|
||||
val launchable = missing.isEmpty() && zipDocs.containsKey(g.name)
|
||||
val status = if (missing.isEmpty()) {
|
||||
if (req.size == 1) "${g.name}.zip found"
|
||||
else "needs ${req.joinToString(" + ") { "${it}.zip" }}"
|
||||
} else {
|
||||
"missing ${missing.joinToString(" + ") { "${it}.zip" }}"
|
||||
}
|
||||
val status =
|
||||
if (missing.isEmpty()) {
|
||||
if (req.size == 1) "${g.name}.zip found" else "needs ${req.joinToString(" + ") { "${it}.zip" }}"
|
||||
} else {
|
||||
"missing ${missing.joinToString(" + ") { "${it}.zip" }}"
|
||||
}
|
||||
GameItem(game = g, launchable = launchable, status = status)
|
||||
}
|
||||
|
||||
return items.sortedWith(compareByDescending<GameItem> { it.launchable }.thenBy { it.game.displayName })
|
||||
}
|
||||
|
||||
private class GamesAdapter(
|
||||
private val onClick: (GameItem) -> Unit,
|
||||
) : RecyclerView.Adapter<GamesAdapter.VH>() {
|
||||
private var allItems: List<GameItem> = emptyList()
|
||||
private var shownItems: List<GameItem> = emptyList()
|
||||
private var filter: String = ""
|
||||
|
||||
fun submitList(items: List<GameItem>) {
|
||||
allItems = items
|
||||
applyFilter()
|
||||
}
|
||||
|
||||
fun setFilter(query: String) {
|
||||
val normalized = query.trim()
|
||||
if (normalized == filter) return
|
||||
filter = normalized
|
||||
applyFilter()
|
||||
}
|
||||
|
||||
private fun applyFilter() {
|
||||
val q = filter.trim()
|
||||
shownItems =
|
||||
if (q.isBlank()) {
|
||||
allItems
|
||||
} else {
|
||||
val needle = q.lowercase()
|
||||
allItems.filter {
|
||||
it.game.displayName.lowercase().contains(needle) || it.game.name.lowercase().contains(needle)
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_game, parent, false)
|
||||
return VH(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
val item = shownItems[position]
|
||||
holder.bind(item, onClick)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = shownItems.size
|
||||
|
||||
class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val title: TextView = itemView.findViewById(R.id.game_title)
|
||||
private val subtitle: TextView = itemView.findViewById(R.id.game_subtitle)
|
||||
|
||||
fun bind(item: GameItem, onClick: (GameItem) -> Unit) {
|
||||
title.text = item.game.displayName
|
||||
subtitle.text = item.status
|
||||
itemView.isEnabled = item.launchable
|
||||
title.isEnabled = item.launchable
|
||||
subtitle.isEnabled = item.launchable
|
||||
itemView.alpha = if (item.launchable) 1.0f else 0.5f
|
||||
itemView.setOnClickListener { onClick(item) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,333L760,333L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,333ZM200,547L760,547L760,413L200,413L200,547ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,627L200,627L200,760Q200,760 200,760Q200,760 200,760ZM240,306L240,226L320,226L320,306L240,306ZM240,520L240,440L320,440L320,520L240,520ZM240,734L240,654L320,654L320,734L240,734Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorOnSurface"
|
||||
android:pathData="M3,6h18v2H3zM3,11h18v2H3zM3,16h18v2H3z" />
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/md_theme_onSurface"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||
</vector>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,79 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Games folder (SAF):"
|
||||
android:textStyle="bold" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/games_folder_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Not set"
|
||||
android:textSize="12sp"
|
||||
android:maxLines="2" />
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:materialThemeOverlay="@style/ThemeOverlay.Material3Expressive.AppBarWithSearch"
|
||||
app:elevation="8dp"
|
||||
app:liftOnScroll="false">
|
||||
|
||||
<Button
|
||||
android:id="@+id/pick_games_folder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pick Games Folder" />
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
app:navigationIcon="@drawable/ic_menu_24"
|
||||
app:navigationIconTint="?attr/colorOnPrimary">
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp" />
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:hint="Search games"
|
||||
app:liftOnScroll="true"
|
||||
app:liftOnScrollColor="?attr/colorSurfaceContainerHighest" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="User folder (SAF, optional):"
|
||||
android:textStyle="bold" />
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/user_folder_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Not set"
|
||||
android:textSize="12sp"
|
||||
android:maxLines="2" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/pick_user_folder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pick User Folder" />
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/games_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp" />
|
||||
<com.google.android.material.search.SearchView
|
||||
android:id="@+id/search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="Search games"
|
||||
android:elevation="16dp"
|
||||
app:autoShowKeyboard="true"
|
||||
app:layout_anchor="@id/search_bar">
|
||||
|
||||
<Button
|
||||
android:id="@+id/rescan_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rescan" />
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/search_results_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:text="Tap a game to launch"
|
||||
android:textSize="12sp" />
|
||||
</com.google.android.material.search.SearchView>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/games_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:divider="@android:color/darker_gray"
|
||||
android:dividerHeight="1dp" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/navigation_view"
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:menu="@menu/drawer_menu"
|
||||
app:headerLayout="@layout/nav_header"
|
||||
app:itemTextColor="?attr/colorOnPrimary"
|
||||
app:itemIconTint="?attr/colorOnPrimary" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView 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"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardElevation="4dp"
|
||||
app:strokeWidth="0dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardBackgroundColor="?attr/colorSurface">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/game_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/game_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
android:background="?attr/colorPrimary">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="180dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:contentDescription="Logo"
|
||||
android:src="@drawable/logo" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="Tap a game to launch"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textColor="?attr/colorOnPrimary"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_pick_games_folder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Pick Games Folder"
|
||||
app:icon="@drawable/stadia_controller_24px"
|
||||
style="?attr/materialButtonElevatedStyle" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_pick_user_folder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Pick Data Folder"
|
||||
app:icon="@drawable/data_table_24px"
|
||||
style="?attr/materialButtonElevatedStyle" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_rescan"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Rescan"
|
||||
app:icon="@drawable/refresh_24px"
|
||||
style="?attr/materialButtonElevatedStyle" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/games_folder_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Games folder: Not set"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnPrimary"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/user_folder_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Data folder: Not set"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnPrimary"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
</menu>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Dark scheme -->
|
||||
<color name="md_theme_primary">#3BC1FF</color>
|
||||
<color name="md_theme_onPrimary">#003548</color>
|
||||
<color name="md_theme_primaryContainer">#004C67</color>
|
||||
<color name="md_theme_onPrimaryContainer">#BFE9FF</color>
|
||||
|
||||
<color name="md_theme_secondary">#AFC6FF</color>
|
||||
<color name="md_theme_onSecondary">#152A5B</color>
|
||||
<color name="md_theme_secondaryContainer">#2C4173</color>
|
||||
<color name="md_theme_onSecondaryContainer">#DDE3FF</color>
|
||||
|
||||
<color name="md_theme_tertiary">#D8D6D7</color>
|
||||
<color name="md_theme_onTertiary">#1C1C1C</color>
|
||||
|
||||
<color name="md_theme_background">#000000</color>
|
||||
<color name="md_theme_onBackground">#E6E6E6</color>
|
||||
<color name="md_theme_surface">#0A0B0D</color>
|
||||
<color name="md_theme_onSurface">#E6E6E6</color>
|
||||
<color name="md_theme_surfaceVariant">#1A2026</color>
|
||||
<color name="md_theme_onSurfaceVariant">#C0C8CF</color>
|
||||
|
||||
<color name="md_theme_outline">#8A939B</color>
|
||||
<color name="md_theme_outlineVariant">#3F484E</color>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Brand colors from logo.xml -->
|
||||
<color name="brand_blue">#0297D6</color>
|
||||
<color name="brand_blue_deep">#0843AF</color>
|
||||
<color name="brand_silver">#D8D6D7</color>
|
||||
|
||||
<!-- Light scheme -->
|
||||
<color name="md_theme_primary">@color/brand_blue</color>
|
||||
<color name="md_theme_onPrimary">#FFFFFF</color>
|
||||
<color name="md_theme_primaryContainer">#D2F2FE</color>
|
||||
<color name="md_theme_onPrimaryContainer">#001E2A</color>
|
||||
|
||||
<color name="md_theme_secondary">@color/brand_blue_deep</color>
|
||||
<color name="md_theme_onSecondary">#FFFFFF</color>
|
||||
<color name="md_theme_secondaryContainer">#DDE3FF</color>
|
||||
<color name="md_theme_onSecondaryContainer">#0A1636</color>
|
||||
|
||||
<color name="md_theme_tertiary">@color/brand_silver</color>
|
||||
<color name="md_theme_onTertiary">#1C1C1C</color>
|
||||
|
||||
<color name="md_theme_background">#FBFCFF</color>
|
||||
<color name="md_theme_onBackground">#0F1115</color>
|
||||
<color name="md_theme_surface">#FBFCFF</color>
|
||||
<color name="md_theme_onSurface">#0F1115</color>
|
||||
<color name="md_theme_surfaceVariant">#EAF1F6</color>
|
||||
<color name="md_theme_onSurfaceVariant">#3F484E</color>
|
||||
|
||||
<color name="md_theme_outline">#6F7880</color>
|
||||
<color name="md_theme_outlineVariant">#C0C8CF</color>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Widget.Super3.Button.ElevatedButton" parent="Widget.Material3Expressive.Button">
|
||||
<item name="android:stateListAnimator">@animator/m3_btn_elevated_btn_state_list_anim</item>
|
||||
<item name="elevation">@dimen/m3_comp_button_elevated_container_elevation</item>
|
||||
<item name="materialThemeOverlay">@style/ThemeOverlay.Material3Expressive.Button.Elevated</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,5 +1,31 @@
|
||||
<resources>
|
||||
<style name="Theme.Super3" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<style name="Theme.Super3" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/md_theme_primary</item>
|
||||
<item name="colorOnPrimary">@color/md_theme_onPrimary</item>
|
||||
<item name="colorPrimaryContainer">@color/md_theme_primaryContainer</item>
|
||||
<item name="colorOnPrimaryContainer">@color/md_theme_onPrimaryContainer</item>
|
||||
|
||||
<item name="colorSecondary">@color/md_theme_secondary</item>
|
||||
<item name="colorOnSecondary">@color/md_theme_onSecondary</item>
|
||||
<item name="colorSecondaryContainer">@color/md_theme_secondaryContainer</item>
|
||||
<item name="colorOnSecondaryContainer">@color/md_theme_onSecondaryContainer</item>
|
||||
|
||||
<item name="colorTertiary">@color/md_theme_tertiary</item>
|
||||
<item name="colorOnTertiary">@color/md_theme_onTertiary</item>
|
||||
|
||||
<item name="android:colorBackground">@color/md_theme_background</item>
|
||||
<item name="colorSurface">@color/md_theme_surface</item>
|
||||
<item name="colorOnSurface">@color/md_theme_onSurface</item>
|
||||
<item name="colorSurfaceVariant">@color/md_theme_surfaceVariant</item>
|
||||
<item name="colorOnSurfaceVariant">@color/md_theme_onSurfaceVariant</item>
|
||||
|
||||
<item name="colorOutline">@color/md_theme_outline</item>
|
||||
<item name="colorOutlineVariant">@color/md_theme_outlineVariant</item>
|
||||
|
||||
<item name="materialSearchBarStyle">@style/Widget.Material3Expressive.SearchBar</item>
|
||||
<item name="materialSearchViewStyle">@style/Widget.Material3Expressive.SearchView</item>
|
||||
|
||||
<item name="materialButtonStyle">@style/Widget.Material3Expressive.Button</item>
|
||||
<item name="materialButtonElevatedStyle">@style/Widget.Super3.Button.ElevatedButton</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user