face lift

This commit is contained in:
izzy2lost
2026-03-14 19:20:35 -04:00
parent 5d637a99ab
commit 754f77aa13
42 changed files with 979 additions and 320 deletions
@@ -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<CoverEntry>()
@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<TextView>(R.id.game_name_text)
val sizeText = item.findViewById<TextView>(R.id.game_size_text)
val pathText = item.findViewById<TextView>(R.id.game_path_text)
val coverImage = item.findViewById<ImageView>(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<TextView>(R.id.game_cover_name_text)
val sizeText = item.findViewById<TextView>(R.id.game_cover_size_text)
val coverImage = item.findViewById<ImageView>(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<String>()
@@ -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) {
@@ -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()
@@ -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) {
@@ -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
}
)
}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
M3 Expressive spring press animation for FILLED buttons only.
"state_pressed=false" as the last item acts as the default/release state:
since it has no other state constraints, it matches whenever nothing
more specific matches — i.e. normal and released states.
Using android:interpolator/overshoot causes the scale to briefly pass
1.0 on release (the "spring bounce-back" feel). When the view is already
at scale 1.0 (startup), delta is 0 so overshoot produces no visible movement.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Touch down: snap quickly to 94% -->
<item android:state_pressed="true">
<set android:ordering="together">
<objectAnimator
android:duration="80"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleX"
android:valueTo="0.94"
android:valueType="floatType" />
<objectAnimator
android:duration="80"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:valueTo="0.94"
android:valueType="floatType" />
</set>
</item>
<!-- Release / default: spring back to full size with overshoot bounce -->
<item>
<set android:ordering="together">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/overshoot"
android:propertyName="scaleX"
android:valueTo="1.0"
android:valueType="floatType" />
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/overshoot"
android:propertyName="scaleY"
android:valueTo="1.0"
android:valueType="floatType" />
</set>
</item>
</selector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.5" android:color="@color/xemu_text_muted" android:state_enabled="false" />
<item android:color="@color/xemu_text" />
</selector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.4" android:color="@color/xemu_outline_variant" android:state_enabled="false" />
<item android:color="@color/xemu_green" android:state_checked="true" />
<item android:color="@android:color/transparent" />
</selector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.5" android:color="@color/xemu_text_muted" android:state_enabled="false" />
<item android:color="@color/xemu_black" android:state_checked="true" />
<item android:color="@color/xemu_text" />
</selector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.5" android:color="@color/xemu_text_muted" android:state_enabled="false" />
<item android:color="@color/xemu_black" android:state_checked="true" />
<item android:color="@color/xemu_green_light" />
</selector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.5" android:color="@color/xemu_text_muted" android:state_enabled="false" />
<item android:color="@color/xemu_black" />
</selector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/xemu_outline_variant" android:state_enabled="false" />
<item android:color="@color/xemu_green" />
</selector>
@@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="270"
android:startColor="@color/xemu_surface_container_high"
android:endColor="@color/xemu_surface_container" />
<corners android:radius="14dp" />
</shape>
@@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="270"
android:startColor="@color/xemu_surface_container_high"
android:endColor="@color/xemu_surface_container" />
<corners android:radius="6dp" />
</shape>
@@ -0,0 +1,12 @@
<?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="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="@android:color/white"
android:pathData="M480-300q75 0 127.5-52.5T660-480q0-75-52.5-127.5T480-660q-75 0-127.5 52.5T300-480q0 75 52.5 127.5T480-300Zm0-140q-17 0-28.5-11.5T440-480q0-17 11.5-28.5T480-520q17 0 28.5 11.5T520-480q0 17-11.5 28.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
</group>
</vector>
@@ -0,0 +1,12 @@
<?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="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="@android:color/white"
android:pathData="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h360q17 0 28.5 11.5T880-680q0 17-11.5 28.5T840-640H447l-80-80H160v480l79-263q8-26 29.5-41.5T316-560h516q41 0 64.5 32.5T909-457l-72 240q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm-84-262v-218 218Zm84 262 72-240-72 240Z" />
</group>
</vector>
@@ -0,0 +1,12 @@
<?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="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="@android:color/white"
android:pathData="M182-200q-51 0-79-35.5T82-322l42-300q9-60 53.5-99T282-760h396q60 0 104.5 39t53.5 99l42 300q7 51-21 86.5T778-200q-21 0-39-7.5T706-230l-90-90H344l-90 90q-15 15-33 22.5t-39 7.5Zm16-86 114-114h336l114 114q2 2 16 6 11 0 17.5-6.5T800-304l-44-308q-4-29-26-48.5T678-680H282q-30 0-52 19.5T204-612l-44 308q-2 11 4.5 17.5T182-280q2 0 16-6Zm482-154q17 0 28.5-11.5T720-480q0-17-11.5-28.5T680-520q-17 0-28.5 11.5T640-480q0 17 11.5 28.5T680-440Zm-80-120q17 0 28.5-11.5T640-600q0-17-11.5-28.5T600-640q-17 0-28.5 11.5T560-600q0 17 11.5 28.5T600-560Zm-120 80Zm-170-30v40q0 13 8.5 21.5T340-440q13 0 21.5-8.5T370-470v-40h40q13 0 21.5-8.5T440-540q0-13-8.5-21.5T410-570h-40v-40q0-13-8.5-21.5T340-640q-13 0-21.5 8.5T310-610v40h-40q-13 0-21.5 8.5T240-540q0 13 8.5 21.5T270-510h40Z" />
</group>
</vector>
@@ -0,0 +1,12 @@
<?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="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="@android:color/white"
android:pathData="M200-520q-33 0-56.5-23.5T120-600v-160q0-33 23.5-56.5T200-840h160q33 0 56.5 23.5T440-760v160q0 33-23.5 56.5T360-520H200Zm0 400q-33 0-56.5-23.5T120-200v-160q0-33 23.5-56.5T200-440h160q33 0 56.5 23.5T440-360v160q0 33-23.5 56.5T360-120H200Zm400-400q-33 0-56.5-23.5T520-600v-160q0-33 23.5-56.5T600-840h160q33 0 56.5 23.5T840-760v160q0 33-23.5 56.5T760-520H600Zm0 400q-33 0-56.5-23.5T520-200v-160q0-33 23.5-56.5T600-440h160q33 0 56.5 23.5T840-360v160q0 33-23.5 56.5T760-120H600ZM200-600h160v-160H200v160Zm400 0h160v-160H600v160Zm0 400h160v-160H600v160Zm-400 0h160v-160H200v160Zm400-400Zm0 240Zm-240 0Zm0-240Z" />
</group>
</vector>
@@ -0,0 +1,12 @@
<?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="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="@android:color/white"
android:pathData="M560-200q17 0 28.5-11.5T600-240q0-17-11.5-28.5T560-280q-17 0-28.5 11.5T520-240q0 17 11.5 28.5T560-200Zm120 0q17 0 28.5-11.5T720-240q0-17-11.5-28.5T680-280q-17 0-28.5 11.5T640-240q0 17 11.5 28.5T680-200ZM120-440v-360q0-33 23.5-56.5T200-880h560q33 0 56.5 23.5T840-800v360h-80v-360H200v360h-80Zm80 80v200h560v-200H200Zm0 280q-33 0-56.5-23.5T120-160v-280h720v280q0 33-23.5 56.5T760-80H200Zm0-360h560-560Zm0 80h560-560Z" />
</group>
</vector>
@@ -0,0 +1,12 @@
<?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="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="@android:color/white"
android:pathData="M480-120q-126 0-223-76.5T131-392q-4-15 6-27.5t27-14.5q16-2 29 6t18 24q24 90 99 147t170 57q117 0 198.5-81.5T760-480q0-117-81.5-198.5T480-760q-69 0-129 32t-101 88h70q17 0 28.5 11.5T360-600q0 17-11.5 28.5T320-560H160q-17 0-28.5-11.5T120-600v-160q0-17 11.5-28.5T160-800q17 0 28.5 11.5T200-760v54q51-64 124.5-99T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-120Zm40-376 100 100q11 11 11 28t-11 28q-11 11-28 11t-28-11L452-452q-6-6-9-13.5t-3-15.5v-159q0-17 11.5-28.5T480-680q17 0 28.5 11.5T520-640v144Z" />
</group>
</vector>
@@ -0,0 +1,12 @@
<?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="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="@android:color/white"
android:pathData="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0 0v-560 560Zm80-80h400q12 0 18-11t-2-21L586-459q-6-8-16-8t-16 8L450-320l-74-99q-6-8-16-8t-16 8l-80 107q-8 10-2 21t18 11Z" />
</group>
</vector>

Some files were not shown because too many files have changed in this diff Show More