From 754f77aa13eebef1bdccfd86be70cdf830eb92fd Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Sat, 14 Mar 2026 19:20:35 -0400 Subject: [PATCH] face lift --- .../izzy2lost/x1box/GameLibraryActivity.kt | 191 +++++++++++------- .../java/com/izzy2lost/x1box/MainActivity.kt | 61 ++++-- .../com/izzy2lost/x1box/SettingsActivity.kt | 90 ++++++--- .../izzy2lost/x1box/SetupWizardActivity.kt | 8 +- .../main/res/animator/button_press_spring.xml | 51 +++++ .../color/game_library_top_button_content.xml | 5 + .../game_library_view_toggle_background.xml | 6 + .../game_library_view_toggle_content.xml | 6 + .../main/res/color/xemu_button_content.xml | 6 + .../res/color/xemu_button_content_filled.xml | 5 + .../color/xemu_button_filled_background.xml | 5 + .../drawable/game_cover_image_background.xml | 7 + .../drawable/game_list_cover_background.xml | 7 + .../src/main/res/drawable/ic_xemu_disc.xml | 12 ++ .../main/res/drawable/ic_xemu_folder_open.xml | 12 ++ .../src/main/res/drawable/ic_xemu_gamepad.xml | 12 ++ .../main/res/drawable/ic_xemu_grid_view.xml | 12 ++ .../main/res/drawable/ic_xemu_hard_drive.xml | 12 ++ .../src/main/res/drawable/ic_xemu_history.xml | 12 ++ .../src/main/res/drawable/ic_xemu_image.xml | 12 ++ .../drawable/ic_xemu_image_placeholder.xml | 12 ++ .../src/main/res/drawable/ic_xemu_info.xml | 12 ++ .../src/main/res/drawable/ic_xemu_memory.xml | 12 ++ .../src/main/res/drawable/ic_xemu_play.xml | 12 ++ .../main/res/drawable/ic_xemu_settings.xml | 12 ++ .../main/res/drawable/ic_xemu_sync_alt.xml | 12 ++ .../main/res/drawable/ic_xemu_upload_file.xml | 12 ++ .../main/res/drawable/ic_xemu_view_list.xml | 12 ++ .../drawable/section_header_background.xml | 9 + .../res/drawable/setup_wizard_background.xml | 10 +- .../setup_wizard_indicator_complete.xml | 4 + .../drawable/setup_wizard_path_background.xml | 39 ++-- .../main/res/layout/activity_game_library.xml | 113 ++++++----- .../src/main/res/layout/activity_settings.xml | 114 +++++++---- .../main/res/layout/activity_setup_wizard.xml | 28 +-- .../src/main/res/layout/item_game_cover.xml | 82 ++++---- .../src/main/res/layout/item_game_entry.xml | 52 ++--- .../main/res/layout/item_snapshot_slot.xml | 2 +- android/app/src/main/res/values/colors.xml | 33 ++- android/app/src/main/res/values/dimens.xml | 4 + android/app/src/main/res/values/strings.xml | 2 - android/app/src/main/res/values/themes.xml | 179 +++++++++++++++- 42 files changed, 979 insertions(+), 320 deletions(-) create mode 100644 android/app/src/main/res/animator/button_press_spring.xml create mode 100644 android/app/src/main/res/color/game_library_top_button_content.xml create mode 100644 android/app/src/main/res/color/game_library_view_toggle_background.xml create mode 100644 android/app/src/main/res/color/game_library_view_toggle_content.xml create mode 100644 android/app/src/main/res/color/xemu_button_content.xml create mode 100644 android/app/src/main/res/color/xemu_button_content_filled.xml create mode 100644 android/app/src/main/res/color/xemu_button_filled_background.xml create mode 100644 android/app/src/main/res/drawable/game_cover_image_background.xml create mode 100644 android/app/src/main/res/drawable/game_list_cover_background.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_disc.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_folder_open.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_gamepad.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_grid_view.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_hard_drive.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_history.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_image.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_image_placeholder.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_info.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_memory.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_play.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_settings.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_sync_alt.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_upload_file.xml create mode 100644 android/app/src/main/res/drawable/ic_xemu_view_list.xml create mode 100644 android/app/src/main/res/drawable/section_header_background.xml create mode 100644 android/app/src/main/res/drawable/setup_wizard_indicator_complete.xml create mode 100644 android/app/src/main/res/values/dimens.xml diff --git a/android/app/src/main/java/com/izzy2lost/x1box/GameLibraryActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/GameLibraryActivity.kt index bbdbebb0fb..3c7f9a0264 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/GameLibraryActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/GameLibraryActivity.kt @@ -30,7 +30,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.documentfile.provider.DocumentFile import coil.load import com.google.android.material.button.MaterialButton -import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.materialswitch.MaterialSwitch import java.io.File @@ -107,19 +106,18 @@ class GameLibraryActivity : AppCompatActivity() { private val coverEntries = ArrayList() @Volatile private var coverIndexLoaded = false - private lateinit var folderText: TextView private lateinit var loadingSpinner: ProgressBar private lateinit var loadingText: TextView private lateinit var emptyText: TextView private lateinit var gamesListContainer: LinearLayout private lateinit var gamesGridContainer: LinearLayout - private lateinit var btnChangeFolder: MaterialButton private lateinit var btnBootDashboard: MaterialButton private lateinit var btnSettings: MaterialButton private lateinit var btnSnapshots: MaterialButton private lateinit var btnConvertIso: MaterialButton private lateinit var btnAbout: ImageButton - private lateinit var viewModeToggle: MaterialButtonToggleGroup + private lateinit var btnViewList: MaterialButton + private lateinit var btnViewGrid: MaterialButton private lateinit var switchBoxArtLookup: MaterialSwitch private var gamesFolderUri: Uri? = null @@ -161,36 +159,29 @@ class GameLibraryActivity : AppCompatActivity() { return } - folderText = findViewById(R.id.library_folder_text) loadingSpinner = findViewById(R.id.library_loading) loadingText = findViewById(R.id.library_loading_text) emptyText = findViewById(R.id.library_empty_text) gamesListContainer = findViewById(R.id.library_games_container) gamesGridContainer = findViewById(R.id.library_games_grid_container) - btnChangeFolder = findViewById(R.id.btn_change_games_folder) btnBootDashboard = findViewById(R.id.btn_boot_dashboard) btnSettings = findViewById(R.id.btn_settings) btnSnapshots = findViewById(R.id.btn_snapshots) btnConvertIso = findViewById(R.id.btn_convert_iso) btnAbout = findViewById(R.id.btn_library_about) - viewModeToggle = findViewById(R.id.library_view_mode_toggle) + btnViewList = findViewById(R.id.btn_view_list) + btnViewGrid = findViewById(R.id.btn_view_grid) switchBoxArtLookup = findViewById(R.id.switch_box_art_lookup) + btnViewList.isCheckable = true + btnViewGrid.isCheckable = true gamesFolderUri = prefs.getString("gamesFolderUri", null)?.let(Uri::parse) useCoverGrid = prefs.getBoolean("library_cover_grid", false) boxArtLookupEnabled = prefs.getBoolean("library_box_art_lookup", true) switchBoxArtLookup.isChecked = boxArtLookupEnabled - viewModeToggle.check(if (useCoverGrid) R.id.btn_view_grid else R.id.btn_view_list) syncDisplayModeUi() - btnChangeFolder.setOnClickListener { - if (isConvertingIso) { - Toast.makeText(this, getString(R.string.library_convert_busy), Toast.LENGTH_SHORT).show() - return@setOnClickListener - } - pickGamesFolder.launch(gamesFolderUri) - } btnBootDashboard.setOnClickListener { if (isConvertingIso) { Toast.makeText(this, getString(R.string.library_convert_busy), Toast.LENGTH_SHORT).show() @@ -210,19 +201,8 @@ class GameLibraryActivity : AppCompatActivity() { btnAbout.setOnClickListener { showAboutDialog() } - viewModeToggle.addOnButtonCheckedListener { _, checkedId, isChecked -> - if (!isChecked) { - return@addOnButtonCheckedListener - } - val nextGrid = checkedId == R.id.btn_view_grid - if (nextGrid == useCoverGrid) { - return@addOnButtonCheckedListener - } - useCoverGrid = nextGrid - prefs.edit().putBoolean("library_cover_grid", useCoverGrid).apply() - syncDisplayModeUi() - renderGames() - } + btnViewList.setOnClickListener { setDisplayMode(false) } + btnViewGrid.setOnClickListener { setDisplayMode(true) } switchBoxArtLookup.setOnCheckedChangeListener { _, checked -> boxArtLookupEnabled = checked prefs.edit().putBoolean("library_box_art_lookup", checked).apply() @@ -233,7 +213,6 @@ class GameLibraryActivity : AppCompatActivity() { updateConvertButtonState() if (!isFolderReady(gamesFolderUri)) { - folderText.text = getString(R.string.library_no_folder) Toast.makeText(this, getString(R.string.setup_pick_disc), Toast.LENGTH_SHORT).show() pickGamesFolder.launch(gamesFolderUri) return @@ -500,7 +479,7 @@ class GameLibraryActivity : AppCompatActivity() { showSnapshotPreviewDialog(preview) } } else { - thumbnail.setImageResource(android.R.drawable.ic_menu_report_image) + thumbnail.setImageResource(R.drawable.ic_xemu_image_placeholder) previewHint.text = getString(R.string.snapshot_preview_unavailable) previewHint.visibility = View.VISIBLE thumbnail.setOnClickListener(null) @@ -532,16 +511,15 @@ class GameLibraryActivity : AppCompatActivity() { setLoading(false) currentGames = emptyList() renderGames() - folderText.text = getString(R.string.library_no_folder) return } + val readyFolderUri = folderUri ?: return - folderText.text = getString(R.string.library_folder_value, formatTreeLabel(folderUri!!)) setLoading(true, getString(R.string.library_loading_games)) val generation = ++scanGeneration Thread { - val games = scanFolderForGames(folderUri) + val games = scanFolderForGames(readyFolderUri) runOnUiThread { if (generation != scanGeneration) { return@runOnUiThread @@ -554,11 +532,23 @@ class GameLibraryActivity : AppCompatActivity() { } private fun syncDisplayModeUi() { + btnViewList.isChecked = !useCoverGrid + btnViewGrid.isChecked = useCoverGrid switchBoxArtLookup.visibility = if (useCoverGrid) View.VISIBLE else View.GONE gamesListContainer.visibility = if (useCoverGrid) View.GONE else View.VISIBLE gamesGridContainer.visibility = if (useCoverGrid) View.VISIBLE else View.GONE } + private fun setDisplayMode(nextGrid: Boolean) { + if (nextGrid == useCoverGrid) { + return + } + useCoverGrid = nextGrid + prefs.edit().putBoolean("library_cover_grid", useCoverGrid).apply() + syncDisplayModeUi() + renderGames() + } + private fun setLoading(loading: Boolean, message: String? = null) { if (message != null) { loadingText.text = message @@ -598,13 +588,15 @@ class GameLibraryActivity : AppCompatActivity() { val nameText = item.findViewById(R.id.game_name_text) val sizeText = item.findViewById(R.id.game_size_text) val pathText = item.findViewById(R.id.game_path_text) + val coverImage = item.findViewById(R.id.game_list_cover_image) nameText.text = game.title sizeText.text = buildGameSizeText(game) - pathText.text = getString(R.string.library_game_path, game.relativePath) + pathText.text = game.relativePath item.setOnClickListener { launchGame(game) } item.setOnLongClickListener { showGameContextMenu(game); true } + bindCoverArt(coverImage, game) gamesListContainer.addView(item) } } @@ -639,11 +631,9 @@ class GameLibraryActivity : AppCompatActivity() { row!!.addView(item, itemLp) val nameText = item.findViewById(R.id.game_cover_name_text) - val sizeText = item.findViewById(R.id.game_cover_size_text) val coverImage = item.findViewById(R.id.game_cover_image) nameText.text = game.title - sizeText.text = buildGameSizeText(game) item.setOnClickListener { launchGame(game) } item.setOnLongClickListener { showGameContextMenu(game); true } bindCoverArt(coverImage, game) @@ -673,19 +663,14 @@ class GameLibraryActivity : AppCompatActivity() { private fun bindCoverArt(coverView: ImageView, game: GameEntry) { coverView.tag = game.uri.toString() + showCoverPlaceholder(coverView) val customFile = getCustomCoverFile(game) if (customFile.exists()) { - coverView.load(customFile) { - crossfade(true) - placeholder(android.R.drawable.ic_menu_report_image) - error(android.R.drawable.ic_menu_report_image) - } + loadCoverArtIntoView(coverView, customFile) return } - coverView.setImageResource(android.R.drawable.ic_menu_report_image) - if (!boxArtLookupEnabled) { return } @@ -705,13 +690,37 @@ class GameLibraryActivity : AppCompatActivity() { } private fun applyBoxArtToView(coverView: ImageView, url: String) { - coverView.load(url) { + loadCoverArtIntoView(coverView, url) + } + + private fun loadCoverArtIntoView(coverView: ImageView, model: Any) { + coverView.load(model) { crossfade(true) - placeholder(android.R.drawable.ic_menu_report_image) - error(android.R.drawable.ic_menu_report_image) + placeholder(R.drawable.ic_xemu_image_placeholder) + error(R.drawable.ic_xemu_image_placeholder) + listener( + onSuccess = { _, _ -> + if (coverView.tag != null) { + coverView.background = null + } + }, + onError = { _, _ -> + showCoverPlaceholder(coverView) + } + ) } } + private fun showCoverPlaceholder(coverView: ImageView) { + val backgroundRes = if (coverView.id == R.id.game_list_cover_image) { + R.drawable.game_list_cover_background + } else { + R.drawable.game_cover_image_background + } + coverView.setBackgroundResource(backgroundRes) + coverView.setImageResource(R.drawable.ic_xemu_image_placeholder) + } + private fun lookupBoxArtUrl(title: String): String? { ensureCoverIndexLoaded() val candidates = linkedSetOf() @@ -936,13 +945,35 @@ class GameLibraryActivity : AppCompatActivity() { "${game.title}\n${game.relativePath}" }.toTypedArray() - MaterialAlertDialogBuilder(this) - .setTitle(R.string.library_convert_pick_title) - .setItems(labels) { _, which -> - confirmIsoConversion(isoGames[which]) + val dp = resources.displayMetrics.density + lateinit var convertDialog: androidx.appcompat.app.AlertDialog + + val buttonList = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding((20 * dp).toInt(), (12 * dp).toInt(), (20 * dp).toInt(), 0) + labels.forEachIndexed { i, label -> + addView(MaterialButton(this@GameLibraryActivity, null, + com.google.android.material.R.attr.materialButtonOutlinedStyle).apply { + text = label + isAllCaps = false + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).also { lp -> lp.bottomMargin = (8 * dp).toInt() } + setOnClickListener { + convertDialog.dismiss() + confirmIsoConversion(isoGames[i]) + } + }) } + } + + convertDialog = MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) + .setTitle(R.string.library_convert_pick_title) + .setView(buttonList) .setNegativeButton(android.R.string.cancel, null) - .show() + .create() + convertDialog.show() } private fun confirmIsoConversion(game: GameEntry) { @@ -1323,11 +1354,16 @@ class GameLibraryActivity : AppCompatActivity() { private fun toGameTitle(fileName: String): String { val lower = fileName.lowercase(Locale.ROOT) - return when { + val rawTitle = when { lower.endsWith(".xiso.iso") -> fileName.dropLast(".xiso.iso".length) fileName.contains('.') -> fileName.substringBeforeLast('.') else -> fileName } + val cleanedTitle = rawTitle + .replace(Regex("(\\s*(\\([^\\)]*\\)|\\[[^\\]]*\\]))+$"), "") + .replace(Regex("\\s+"), " ") + .trim() + return cleanedTitle.ifEmpty { rawTitle.trim() } } private fun formatSize(bytes: Long): String { @@ -1372,14 +1408,6 @@ class GameLibraryActivity : AppCompatActivity() { } } - private fun formatTreeLabel(uri: Uri): String { - val name = DocumentFile.fromTreeUri(this, uri)?.name - if (!name.isNullOrBlank()) { - return name - } - return uri.toString() - } - private fun dp(value: Int): Int { return (value * resources.displayMetrics.density).toInt() } @@ -1551,20 +1579,41 @@ class GameLibraryActivity : AppCompatActivity() { } }.toTypedArray() - MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) - .setTitle(game.title) - .setItems(options) { _, which -> - when (actions[which]) { - GameContextAction.SET_CUSTOM_COVER -> { - pendingCustomCoverGame = game - pickCustomCover.launch("image/*") + val dp = resources.displayMetrics.density + lateinit var contextDialog: androidx.appcompat.app.AlertDialog + + val buttonList = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding((20 * dp).toInt(), (12 * dp).toInt(), (20 * dp).toInt(), 0) + options.forEachIndexed { i, label -> + addView(MaterialButton(this@GameLibraryActivity, null, + com.google.android.material.R.attr.materialButtonOutlinedStyle).apply { + text = label + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).also { lp -> lp.bottomMargin = (8 * dp).toInt() } + setOnClickListener { + contextDialog.dismiss() + when (actions[i]) { + GameContextAction.SET_CUSTOM_COVER -> { + pendingCustomCoverGame = game + pickCustomCover.launch("image/*") + } + GameContextAction.REMOVE_CUSTOM_COVER -> removeCustomCover(game) + GameContextAction.DELETE_GAME -> confirmDeleteGame(game) + } } - GameContextAction.REMOVE_CUSTOM_COVER -> removeCustomCover(game) - GameContextAction.DELETE_GAME -> confirmDeleteGame(game) - } + }) } + } + + contextDialog = MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) + .setTitle(game.title) + .setView(buttonList) .setNegativeButton(android.R.string.cancel, null) - .show() + .create() + contextDialog.show() } private fun openExternalLink(url: String) { diff --git a/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt index bc781d4286..dc45fc4a3f 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt @@ -21,10 +21,13 @@ import android.view.ViewConfiguration import android.widget.BaseAdapter import android.widget.FrameLayout import android.widget.ImageView +import android.widget.LinearLayout import android.widget.ListView import android.widget.RelativeLayout import android.widget.TextView import android.widget.Toast +import androidx.core.widget.NestedScrollView +import com.google.android.material.button.MaterialButton import androidx.appcompat.app.AlertDialog import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.libsdl.app.SDLActivity @@ -663,7 +666,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { showSnapshotPreviewDialog(preview) } } else { - thumbnail.setImageResource(android.R.drawable.ic_menu_report_image) + thumbnail.setImageResource(R.drawable.ic_xemu_image_placeholder) previewHint.text = getString(R.string.snapshot_preview_unavailable) previewHint.visibility = View.VISIBLE thumbnail.setOnClickListener(null) @@ -732,19 +735,51 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { getString(R.string.in_game_menu_quit_app), ) - val dialog = MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) - .setTitle(getString(R.string.in_game_menu_title)) - .setItems(options) { _, which -> - when (which) { - 0 -> { /* Resume — dismiss dialog */ } - 1 -> toggleOnScreenController() - 2 -> showSaveStateDialog() - 3 -> showLoadStateDialog() - 4 -> showRebootSystemConfirmation() - 5 -> exitToGameLibrary() - 6 -> quitApp() - } + val dp = resources.displayMetrics.density + lateinit var dialog: androidx.appcompat.app.AlertDialog + + val buttonList = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding((20 * dp).toInt(), (12 * dp).toInt(), (20 * dp).toInt(), 0) + options.forEachIndexed { i, label -> + addView(MaterialButton(this@MainActivity, null, + com.google.android.material.R.attr.materialButtonOutlinedStyle).apply { + text = label + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).also { lp -> lp.bottomMargin = (8 * dp).toInt() } + setOnClickListener { + dialog.dismiss() + when (i) { + 0 -> { /* Resume — dialog dismissed above */ } + 1 -> toggleOnScreenController() + 2 -> showSaveStateDialog() + 3 -> showLoadStateDialog() + 4 -> showRebootSystemConfirmation() + 5 -> exitToGameLibrary() + 6 -> quitApp() + } + } + }) } + } + + val scrollContainer = NestedScrollView(this).apply { + isFillViewport = true + overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS + addView( + buttonList, + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ) + } + + dialog = MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) + .setTitle(getString(R.string.in_game_menu_title)) + .setView(scrollContainer) .setOnDismissListener { inGameMenuDialog = null hideSystemUI() diff --git a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt index 2f83e05580..bf33099a9d 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt @@ -8,6 +8,7 @@ import android.provider.OpenableColumns import android.text.format.Formatter import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView +import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts @@ -640,23 +641,45 @@ class SettingsActivity : AppCompatActivity() { } } .toTypedArray() - MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) - .setTitle(R.string.settings_hdd_layout_pick_title) - .setItems(labels) { _, which -> - val layout = allLayouts[which] - val availability = XboxHddFormatter.availabilityFor(inspection, layout) - if (availability == XboxHddFormatter.LayoutAvailability.AVAILABLE) { - showInitializeHddConfirmation(hddFile, layout, button) - } else { - MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) - .setTitle(R.string.settings_hdd_layout_unavailable_title) - .setMessage(getString(hddLayoutUnavailableReasonRes(availability))) - .setPositiveButton(android.R.string.ok, null) - .show() - } + val dp = resources.displayMetrics.density + lateinit var hddDialog: androidx.appcompat.app.AlertDialog + + val buttonList = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding((20 * dp).toInt(), (12 * dp).toInt(), (20 * dp).toInt(), 0) + labels.forEachIndexed { i, label -> + addView(MaterialButton(this@SettingsActivity, null, + com.google.android.material.R.attr.materialButtonOutlinedStyle).apply { + text = label + isAllCaps = false + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).also { lp -> lp.bottomMargin = (8 * dp).toInt() } + setOnClickListener { + hddDialog.dismiss() + val layout = allLayouts[i] + val availability = XboxHddFormatter.availabilityFor(inspection, layout) + if (availability == XboxHddFormatter.LayoutAvailability.AVAILABLE) { + showInitializeHddConfirmation(hddFile, layout, button) + } else { + MaterialAlertDialogBuilder(this@SettingsActivity, R.style.ThemeOverlay_Xemu_RoundedDialog) + .setTitle(R.string.settings_hdd_layout_unavailable_title) + .setMessage(getString(hddLayoutUnavailableReasonRes(availability))) + .setPositiveButton(android.R.string.ok, null) + .show() + } + } + }) } + } + + hddDialog = MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) + .setTitle(R.string.settings_hdd_layout_pick_title) + .setView(buttonList) .setNegativeButton(android.R.string.cancel, null) - .show() + .create() + hddDialog.show() } private fun showInitializeHddConfirmation( @@ -783,16 +806,37 @@ class SettingsActivity : AppCompatActivity() { getString(R.string.settings_dashboard_import_source_zip), getString(R.string.settings_dashboard_import_source_folder), ) - MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) - .setTitle(R.string.settings_dashboard_import_source_title) - .setItems(labels) { _, which -> - when (which) { - 0 -> pickDashboardZip.launch(arrayOf("application/zip", "application/octet-stream")) - else -> pickDashboardFolder.launch(null) - } + val dp = resources.displayMetrics.density + lateinit var importDialog: androidx.appcompat.app.AlertDialog + + val buttonList = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding((20 * dp).toInt(), (12 * dp).toInt(), (20 * dp).toInt(), 0) + labels.forEachIndexed { i, label -> + addView(MaterialButton(this@SettingsActivity, null, + com.google.android.material.R.attr.materialButtonOutlinedStyle).apply { + text = label + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).also { lp -> lp.bottomMargin = (8 * dp).toInt() } + setOnClickListener { + importDialog.dismiss() + when (i) { + 0 -> pickDashboardZip.launch(arrayOf("application/zip", "application/octet-stream")) + else -> pickDashboardFolder.launch(null) + } + } + }) } + } + + importDialog = MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) + .setTitle(R.string.settings_dashboard_import_source_title) + .setView(buttonList) .setNegativeButton(android.R.string.cancel, null) - .show() + .create() + importDialog.show() } private fun prepareDashboardImportFromZip(uri: Uri) { diff --git a/android/app/src/main/java/com/izzy2lost/x1box/SetupWizardActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/SetupWizardActivity.kt index 063eef0c91..bb72e73eda 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/SetupWizardActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/SetupWizardActivity.kt @@ -247,10 +247,10 @@ class SetupWizardActivity : AppCompatActivity() { } indicators.forEachIndexed { index, view -> view.setBackgroundResource( - if (currentStep == index) { - R.drawable.setup_wizard_indicator_active - } else { - R.drawable.setup_wizard_indicator_inactive + when { + currentStep == index -> R.drawable.setup_wizard_indicator_active + index < currentStep -> R.drawable.setup_wizard_indicator_complete + else -> R.drawable.setup_wizard_indicator_inactive } ) } diff --git a/android/app/src/main/res/animator/button_press_spring.xml b/android/app/src/main/res/animator/button_press_spring.xml new file mode 100644 index 0000000000..df443fecdd --- /dev/null +++ b/android/app/src/main/res/animator/button_press_spring.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/color/game_library_top_button_content.xml b/android/app/src/main/res/color/game_library_top_button_content.xml new file mode 100644 index 0000000000..0aada99635 --- /dev/null +++ b/android/app/src/main/res/color/game_library_top_button_content.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/color/game_library_view_toggle_background.xml b/android/app/src/main/res/color/game_library_view_toggle_background.xml new file mode 100644 index 0000000000..cbcf3b048f --- /dev/null +++ b/android/app/src/main/res/color/game_library_view_toggle_background.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/app/src/main/res/color/game_library_view_toggle_content.xml b/android/app/src/main/res/color/game_library_view_toggle_content.xml new file mode 100644 index 0000000000..76b399b2cd --- /dev/null +++ b/android/app/src/main/res/color/game_library_view_toggle_content.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/app/src/main/res/color/xemu_button_content.xml b/android/app/src/main/res/color/xemu_button_content.xml new file mode 100644 index 0000000000..8a34cc035b --- /dev/null +++ b/android/app/src/main/res/color/xemu_button_content.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/app/src/main/res/color/xemu_button_content_filled.xml b/android/app/src/main/res/color/xemu_button_content_filled.xml new file mode 100644 index 0000000000..f74aa10951 --- /dev/null +++ b/android/app/src/main/res/color/xemu_button_content_filled.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/color/xemu_button_filled_background.xml b/android/app/src/main/res/color/xemu_button_filled_background.xml new file mode 100644 index 0000000000..2dc16b1110 --- /dev/null +++ b/android/app/src/main/res/color/xemu_button_filled_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/drawable/game_cover_image_background.xml b/android/app/src/main/res/drawable/game_cover_image_background.xml new file mode 100644 index 0000000000..c4e262dc78 --- /dev/null +++ b/android/app/src/main/res/drawable/game_cover_image_background.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/res/drawable/game_list_cover_background.xml b/android/app/src/main/res/drawable/game_list_cover_background.xml new file mode 100644 index 0000000000..1786172a45 --- /dev/null +++ b/android/app/src/main/res/drawable/game_list_cover_background.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_disc.xml b/android/app/src/main/res/drawable/ic_xemu_disc.xml new file mode 100644 index 0000000000..af364a5384 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_disc.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_folder_open.xml b/android/app/src/main/res/drawable/ic_xemu_folder_open.xml new file mode 100644 index 0000000000..a54eb1f851 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_folder_open.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_gamepad.xml b/android/app/src/main/res/drawable/ic_xemu_gamepad.xml new file mode 100644 index 0000000000..eae0f9618c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_gamepad.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_grid_view.xml b/android/app/src/main/res/drawable/ic_xemu_grid_view.xml new file mode 100644 index 0000000000..676156913c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_grid_view.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_hard_drive.xml b/android/app/src/main/res/drawable/ic_xemu_hard_drive.xml new file mode 100644 index 0000000000..d1fb329258 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_hard_drive.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_history.xml b/android/app/src/main/res/drawable/ic_xemu_history.xml new file mode 100644 index 0000000000..ef5de6029b --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_history.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_image.xml b/android/app/src/main/res/drawable/ic_xemu_image.xml new file mode 100644 index 0000000000..ec4ff78d52 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_image.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_image_placeholder.xml b/android/app/src/main/res/drawable/ic_xemu_image_placeholder.xml new file mode 100644 index 0000000000..dc71a0c114 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_image_placeholder.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_info.xml b/android/app/src/main/res/drawable/ic_xemu_info.xml new file mode 100644 index 0000000000..6d16b0cd39 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_info.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_memory.xml b/android/app/src/main/res/drawable/ic_xemu_memory.xml new file mode 100644 index 0000000000..6605a3ed5c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_memory.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_play.xml b/android/app/src/main/res/drawable/ic_xemu_play.xml new file mode 100644 index 0000000000..bf0744b6a2 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_play.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_settings.xml b/android/app/src/main/res/drawable/ic_xemu_settings.xml new file mode 100644 index 0000000000..6968650ad5 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_settings.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_sync_alt.xml b/android/app/src/main/res/drawable/ic_xemu_sync_alt.xml new file mode 100644 index 0000000000..9727d66c65 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_sync_alt.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_upload_file.xml b/android/app/src/main/res/drawable/ic_xemu_upload_file.xml new file mode 100644 index 0000000000..ea287476b7 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_upload_file.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_xemu_view_list.xml b/android/app/src/main/res/drawable/ic_xemu_view_list.xml new file mode 100644 index 0000000000..892a522fb9 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_xemu_view_list.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/section_header_background.xml b/android/app/src/main/res/drawable/section_header_background.xml new file mode 100644 index 0000000000..39fc1212bc --- /dev/null +++ b/android/app/src/main/res/drawable/section_header_background.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/android/app/src/main/res/drawable/setup_wizard_background.xml b/android/app/src/main/res/drawable/setup_wizard_background.xml index 2bd73d6bec..8b6d29bc77 100644 --- a/android/app/src/main/res/drawable/setup_wizard_background.xml +++ b/android/app/src/main/res/drawable/setup_wizard_background.xml @@ -1,7 +1,9 @@ + android:angle="150" + android:startColor="#090C09" + android:centerColor="#0D1410" + android:centerX="0.4" + android:endColor="#141B15" + android:type="linear" /> diff --git a/android/app/src/main/res/drawable/setup_wizard_indicator_complete.xml b/android/app/src/main/res/drawable/setup_wizard_indicator_complete.xml new file mode 100644 index 0000000000..fa10467018 --- /dev/null +++ b/android/app/src/main/res/drawable/setup_wizard_indicator_complete.xml @@ -0,0 +1,4 @@ + + + + diff --git a/android/app/src/main/res/drawable/setup_wizard_path_background.xml b/android/app/src/main/res/drawable/setup_wizard_path_background.xml index 87a905c742..d60b254840 100644 --- a/android/app/src/main/res/drawable/setup_wizard_path_background.xml +++ b/android/app/src/main/res/drawable/setup_wizard_path_background.xml @@ -1,12 +1,27 @@ - - - - - - + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/activity_game_library.xml b/android/app/src/main/res/layout/activity_game_library.xml index 7134265659..d1d9353569 100644 --- a/android/app/src/main/res/layout/activity_game_library.xml +++ b/android/app/src/main/res/layout/activity_game_library.xml @@ -11,7 +11,8 @@ android:layout_height="0dp" android:clipToPadding="false" android:fillViewport="true" - android:padding="24dp" + android:paddingHorizontal="@dimen/top_level_card_outer_padding_horizontal" + android:paddingVertical="@dimen/top_level_card_outer_padding_vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -28,9 +29,9 @@ android:layout_height="wrap_content" app:cardBackgroundColor="@color/xemu_surface" app:cardCornerRadius="28dp" - app:cardElevation="8dp" - app:strokeColor="@color/xemu_outline_variant" - app:strokeWidth="1dp"> + app:cardElevation="14dp" + app:strokeColor="@color/xemu_outline" + app:strokeWidth="2dp"> + android:src="@drawable/ic_xemu_info" + android:tint="@color/game_library_top_button_content" /> - + + + - - - - + app:iconTint="@color/xemu_black" + app:iconPadding="10dp" /> + + app:iconTint="@color/game_library_top_button_content" + app:iconPadding="10dp" /> + app:iconTint="@color/game_library_top_button_content" + app:iconPadding="10dp" /> + app:iconTint="@color/game_library_top_button_content" + app:iconPadding="10dp" /> - + + android:layout_marginTop="14dp" + android:baselineAligned="false" + android:orientation="horizontal"> - + + app:cardElevation="14dp" + app:strokeColor="@color/xemu_outline" + app:strokeWidth="2dp"> + android:textColor="@color/xemu_green_light" /> - + + + android:textColor="@color/xemu_green_light" /> - + + + android:textColor="@color/xemu_green_light" /> - + + + android:textColor="@color/xemu_green_light" /> - + + + android:textColor="@color/xemu_green_light" /> - + + + android:textColor="@color/xemu_green_light" /> - - + + + android:textColor="@color/xemu_green_light" /> @@ -665,7 +709,7 @@ + app:cardElevation="14dp" + app:strokeColor="@color/xemu_outline" + app:strokeWidth="2dp"> @@ -131,7 +131,7 @@ android:layout_height="40dp" android:layout_gravity="center_horizontal" android:contentDescription="@string/setup_flash_title" - android:src="@android:drawable/ic_menu_manage" + android:src="@drawable/ic_xemu_memory" android:tint="@color/xemu_green" /> @@ -186,7 +186,7 @@ android:layout_height="40dp" android:layout_gravity="center_horizontal" android:contentDescription="@string/setup_hdd_title" - android:src="@android:drawable/ic_menu_save" + android:src="@drawable/ic_xemu_hard_drive" android:tint="@color/xemu_green" /> @@ -241,7 +241,7 @@ android:layout_height="40dp" android:layout_gravity="center_horizontal" android:contentDescription="@string/setup_disc_title" - android:src="@android:drawable/ic_menu_agenda" + android:src="@drawable/ic_xemu_disc" android:tint="@color/xemu_green" /> @@ -335,7 +335,7 @@ - + android:padding="6dp"> - + + android:src="@drawable/ic_xemu_image_placeholder" /> + - - - - - + + diff --git a/android/app/src/main/res/layout/item_game_entry.xml b/android/app/src/main/res/layout/item_game_entry.xml index 657ec4ce03..f4c851e070 100644 --- a/android/app/src/main/res/layout/item_game_entry.xml +++ b/android/app/src/main/res/layout/item_game_entry.xml @@ -3,34 +3,45 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="12dp" + android:layout_marginTop="6dp" android:clickable="true" android:focusable="true" - app:cardBackgroundColor="@color/xemu_surface_variant" + app:cardBackgroundColor="@android:color/transparent" app:cardCornerRadius="22dp" app:cardElevation="0dp" app:rippleColor="@color/xemu_green" - app:strokeColor="@color/xemu_outline" - app:strokeWidth="1dp"> + app:strokeWidth="0dp"> + android:paddingHorizontal="12dp" + android:paddingVertical="10dp"> - + + + + @@ -39,17 +50,17 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" - android:maxLines="1" - android:textAppearance="@style/TextAppearance.Material3.TitleMedium" /> + android:maxLines="2" + android:textAppearance="@style/TextAppearance.Material3.TitleSmall" /> - - diff --git a/android/app/src/main/res/layout/item_snapshot_slot.xml b/android/app/src/main/res/layout/item_snapshot_slot.xml index 3ccf95ae9e..d7d0c9b056 100644 --- a/android/app/src/main/res/layout/item_snapshot_slot.xml +++ b/android/app/src/main/res/layout/item_snapshot_slot.xml @@ -12,7 +12,7 @@ android:id="@+id/snapshot_thumbnail" android:layout_width="120dp" android:layout_height="90dp" - android:background="#1AFFFFFF" + android:background="@color/xemu_surface_container" android:contentDescription="@string/snapshot_thumbnail_desc" android:scaleType="centerCrop" /> diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 86f7541c3a..87124895f9 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -1,12 +1,27 @@ - #080A08 - #0F120F - #141914 - #E8F5E9 - #A3B7A3 + + #080B08 + #0E1410 + #161D17 + #1C261D + #222E23 + + + #EAF6EB + #A8BFA9 + + + #66FF00 + #66FF00 + #66FF00 + #1A5224 + + #FF6B6B - #41B14D - #1E7A2E - #263126 - #1A211A + #4A1515 + + + #66FF00 + #222E23 + #15330B diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..1428716a67 --- /dev/null +++ b/android/app/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + 8dp + 12dp + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 43210b76e4..9b8cb5ee21 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -38,14 +38,12 @@ Games Folder: %1$s Game Library - Change Folder Boot Dashboard Snapshots Convert ISO to XISO List Cover Grid Look up box art online - Folder: %1$s No folder selected Scanning games... Converting %1$s... diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml index 560087aed2..233f392165 100644 --- a/android/app/src/main/res/values/themes.xml +++ b/android/app/src/main/res/values/themes.xml @@ -1,29 +1,196 @@ + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +