Skip to content

Commit 92400ce

Browse files
Sirpixelalotclaude
andcommitted
Add Material 3 UI theme, decompile dialog improvements, and Android 15+ compatibility
UI Enhancements: - Migrate to Material 3 design system with dynamic color theming - Add smooth animations and transitions to cards and progress screens - Implement color-coded status indicators for operations - Add custom scrollbar indicator for main screen - Improve file picker UI with better visual hierarchy Decompile Options Dialog: - Add "Don't show again" checkbox to decompile options dialog - Add Settings option to re-enable decompile dialog - Store user preference in SharedPreferences Android 15+ Compatibility: - Add 16KB page alignment configuration for native libraries - Configure packaging to support Android 15+ devices with 16KB pages - Ensure compatibility with cancelled FFmpeg-Kit library Theme System: - Add ThemeUtils helper for centralized theme management - Support Light, Dark, and System theme modes - Fix theme switching to recreate activity properly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent bad6a66 commit 92400ce

29 files changed

Lines changed: 1460 additions & 432 deletions

app/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ android {
6969
buildFeatures {
7070
compose = true
7171
}
72+
73+
// 16KB page alignment for Android 15+ compatibility
74+
// AGP 8.7+ automatically handles alignment when useLegacyPackaging = false
75+
packaging {
76+
jniLibs {
77+
useLegacyPackaging = false
78+
}
79+
}
7280
}
7381

7482
dependencies {

app/src/main/java/com/renpytool/FilePickerActivity.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.os.Bundle
66
import android.widget.Toast
77
import androidx.activity.ComponentActivity
88
import androidx.activity.compose.setContent
9+
import androidx.activity.enableEdgeToEdge
910
import androidx.activity.viewModels
1011
import androidx.compose.runtime.collectAsState
1112
import androidx.compose.runtime.getValue
@@ -34,10 +35,14 @@ class FilePickerActivity : ComponentActivity() {
3435
}
3536

3637
private val viewModel: FilePickerViewModel by viewModels()
38+
private var currentThemeMode: MainViewModel.ThemeMode? = null
3739

3840
override fun onCreate(savedInstanceState: Bundle?) {
3941
super.onCreate(savedInstanceState)
4042

43+
// Enable edge-to-edge display
44+
enableEdgeToEdge()
45+
4146
// Get intent extras
4247
val mode = intent.getIntExtra(EXTRA_MODE, MODE_FILE)
4348
val fileFilter = intent.getStringExtra(EXTRA_FILE_FILTER)
@@ -47,8 +52,14 @@ class FilePickerActivity : ComponentActivity() {
4752
// Initialize ViewModel
4853
viewModel.initialize(mode, fileFilter, startPath)
4954

55+
// Store initial theme mode
56+
currentThemeMode = ThemeUtils.getThemeMode(this)
57+
5058
setContent {
51-
RenpytoolTheme {
59+
val themeMode = ThemeUtils.getThemeMode(this)
60+
val darkTheme = ThemeUtils.shouldUseDarkTheme(themeMode)
61+
62+
RenpytoolTheme(darkTheme = darkTheme) {
5263
val uiState by viewModel.uiState.collectAsState()
5364

5465
FilePickerScreen(
@@ -57,12 +68,26 @@ class FilePickerActivity : ComponentActivity() {
5768
onFileItemClick = { item -> onFileItemClick(item, uiState) },
5869
onFileItemLongClick = { item -> onFileItemLongClick(item) },
5970
onNavigationClick = { handleNavigationClick(uiState) },
60-
onFabClick = { confirmSelection(uiState) }
71+
onFabClick = { confirmSelection(uiState) },
72+
onBreadcrumbClick = { directory -> viewModel.navigateToDirectory(directory) }
6173
)
6274
}
6375
}
6476
}
6577

78+
override fun onResume() {
79+
super.onResume()
80+
checkThemeChange()
81+
}
82+
83+
private fun checkThemeChange() {
84+
val newThemeMode = ThemeUtils.getThemeMode(this)
85+
if (currentThemeMode != null && currentThemeMode != newThemeMode) {
86+
recreate()
87+
}
88+
currentThemeMode = newThemeMode
89+
}
90+
6691
private fun onFileItemClick(item: FileItem, uiState: FilePickerUiState) {
6792
if (uiState.isMultiSelectMode) {
6893
// In multi-select mode, toggle selection

app/src/main/java/com/renpytool/MainActivity.kt

Lines changed: 93 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.provider.Settings
1313
import android.widget.Toast
1414
import androidx.activity.ComponentActivity
1515
import androidx.activity.compose.setContent
16+
import androidx.activity.enableEdgeToEdge
1617
import androidx.activity.result.ActivityResultLauncher
1718
import androidx.activity.result.contract.ActivityResultContracts
1819
import androidx.activity.viewModels
@@ -34,6 +35,7 @@ import com.renpytool.keystore.KeystoreInfo
3435
import com.renpytool.keystore.KeystoreManager
3536
import com.renpytool.keystore.SigningOption
3637
import com.renpytool.ui.CompressionSettingsDialog
38+
import com.renpytool.ui.DecompileOptionsDialogContent
3739
import com.renpytool.ui.KeystoreSelectionDialog
3840
import com.renpytool.ui.MainScreenContent
3941
import com.renpytool.ui.RandomKeyWarningDialog
@@ -80,6 +82,9 @@ class MainActivity : ComponentActivity() {
8082
override fun onCreate(savedInstanceState: Bundle?) {
8183
super.onCreate(savedInstanceState)
8284

85+
// Enable edge-to-edge display
86+
enableEdgeToEdge()
87+
8388
// Initialize Python (must be done before ViewModel access)
8489
if (!Python.isStarted()) {
8590
Python.start(AndroidPlatform(this))
@@ -88,6 +93,9 @@ class MainActivity : ComponentActivity() {
8893
// Initialize file picker launchers
8994
initFilePickerLaunchers()
9095

96+
// Store initial theme mode from SharedPreferences (source of truth)
97+
currentThemeMode = ThemeUtils.getThemeMode(this)
98+
9199
// Set up Compose UI
92100
setupMainUI()
93101

@@ -99,13 +107,14 @@ class MainActivity : ComponentActivity() {
99107
}
100108

101109
private fun setupMainUI() {
110+
// Set transparent window background for proper theming in Compose
111+
window.setBackgroundDrawableResource(android.R.color.transparent)
112+
102113
setContent {
103-
val themeMode by viewModel.themeMode.collectAsState()
104-
val darkTheme = when (themeMode) {
105-
MainViewModel.ThemeMode.LIGHT -> false
106-
MainViewModel.ThemeMode.DARK -> true
107-
MainViewModel.ThemeMode.SYSTEM -> isSystemInDarkTheme()
108-
}
114+
// Read theme directly from SharedPreferences (source of truth)
115+
// Don't use ViewModel - it's cached and survives recreate()
116+
val themeMode = ThemeUtils.getThemeMode(this)
117+
val darkTheme = ThemeUtils.shouldUseDarkTheme(themeMode)
109118

110119
RenpytoolTheme(darkTheme = darkTheme) {
111120
MainScreen()
@@ -140,11 +149,26 @@ class MainActivity : ComponentActivity() {
140149
)
141150
}
142151

152+
private var currentThemeMode: MainViewModel.ThemeMode? = null
153+
143154
private fun startSettingsActivity() {
144155
val intent = Intent(this, SettingsActivity::class.java)
145156
startActivity(intent)
146157
}
147158

159+
private fun checkThemeChange() {
160+
// Read theme from SharedPreferences (source of truth)
161+
// ViewModel might have stale value from another activity instance
162+
val newThemeMode = ThemeUtils.getThemeMode(this)
163+
if (currentThemeMode != null && currentThemeMode != newThemeMode) {
164+
// Theme changed, recreate activity
165+
currentThemeMode = newThemeMode
166+
recreate()
167+
return
168+
}
169+
currentThemeMode = newThemeMode
170+
}
171+
148172

149173
private fun checkPermissions() {
150174
// Check notification permission for Android 13+ (for background operation notifications)
@@ -330,6 +354,7 @@ class MainActivity : ComponentActivity() {
330354
// Start decompile with the extracted path - delegate to ViewModel
331355
val intent = Intent(this, ProgressActivity::class.java).apply {
332356
putExtra("DECOMPILE_PATH", chainPath)
357+
putExtra("OPERATION_TYPE", "decompile")
333358
}
334359
startActivity(intent)
335360
viewModel.performDecompile(chainPath)
@@ -522,47 +547,58 @@ class MainActivity : ComponentActivity() {
522547
}
523548

524549
private fun showDecompileOptionsDialog(sourcePath: String) {
525-
// Create custom dialog view with checkbox
526-
val dialogView = layoutInflater.inflate(android.R.layout.select_dialog_singlechoice, null)
527-
val checkboxView = android.widget.CheckBox(this).apply {
528-
text = "Try Harder Mode (slower, for obfuscated files)"
529-
setPadding(50, 30, 50, 30)
530-
isChecked = false
531-
}
532-
533-
val layout = android.widget.LinearLayout(this).apply {
534-
orientation = android.widget.LinearLayout.VERTICAL
535-
setPadding(50, 20, 50, 20)
536-
addView(android.widget.TextView(this@MainActivity).apply {
537-
text = "Decompile Options"
538-
textSize = 16f
539-
setPadding(0, 0, 0, 20)
540-
})
541-
addView(checkboxView)
542-
addView(android.widget.TextView(this@MainActivity).apply {
543-
text = "\nTry Harder Mode performs aggressive decompilation for heavily obfuscated files. " +
544-
"This will take significantly longer.\n\n" +
545-
"Use this only if default decompilation fails."
546-
textSize = 12f
547-
setPadding(0, 20, 0, 0)
548-
setTextColor(android.graphics.Color.GRAY)
549-
})
550+
// Check if user has disabled the dialog
551+
val prefs = getSharedPreferences("RentoolPrefs", MODE_PRIVATE)
552+
val dontShowDialog = prefs.getBoolean("dont_show_decompile_dialog", false)
553+
554+
if (dontShowDialog) {
555+
// Skip dialog and proceed with default settings (tryHarder = false)
556+
val intent = Intent(this, ProgressActivity::class.java).apply {
557+
putExtra("DECOMPILE_PATH", sourcePath)
558+
putExtra("OPERATION_TYPE", "decompile")
559+
}
560+
startActivity(intent)
561+
viewModel.performDecompile(sourcePath, false)
562+
return
550563
}
551564

552-
MaterialAlertDialogBuilder(this)
553-
.setTitle("Decompile Settings")
554-
.setView(layout)
555-
.setPositiveButton("Start") { _, _ ->
556-
val tryHarder = checkboxView.isChecked
557-
// Launch progress activity and delegate to ViewModel
558-
val intent = Intent(this, ProgressActivity::class.java).apply {
559-
putExtra("DECOMPILE_PATH", sourcePath)
565+
// Use Compose dialog for proper theming
566+
val dialog = androidx.appcompat.app.AlertDialog.Builder(this)
567+
.create()
568+
569+
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
570+
571+
dialog.setOnShowListener {
572+
val composeView = androidx.compose.ui.platform.ComposeView(this).apply {
573+
setContent {
574+
val themeMode = ThemeUtils.getThemeMode(this@MainActivity)
575+
val darkTheme = ThemeUtils.shouldUseDarkTheme(themeMode)
576+
577+
RenpytoolTheme(darkTheme = darkTheme) {
578+
DecompileOptionsDialogContent(
579+
onStart = { tryHarder, dontShowAgain ->
580+
// Save preference if user checked "Don't show again"
581+
if (dontShowAgain) {
582+
prefs.edit().putBoolean("dont_show_decompile_dialog", true).apply()
583+
}
584+
585+
dialog.dismiss()
586+
val intent = Intent(this@MainActivity, ProgressActivity::class.java).apply {
587+
putExtra("DECOMPILE_PATH", sourcePath)
588+
putExtra("OPERATION_TYPE", "decompile")
589+
}
590+
startActivity(intent)
591+
viewModel.performDecompile(sourcePath, tryHarder)
592+
},
593+
onCancel = { dialog.dismiss() }
594+
)
595+
}
560596
}
561-
startActivity(intent)
562-
viewModel.performDecompile(sourcePath, tryHarder)
563597
}
564-
.setNegativeButton("Cancel", null)
565-
.show()
598+
dialog.setContentView(composeView)
599+
}
600+
601+
dialog.show()
566602
}
567603

568604
/**
@@ -802,6 +838,9 @@ class MainActivity : ComponentActivity() {
802838
val keystoreManager = KeystoreManager(this)
803839
val availableKeystores = keystoreManager.listKeystores()
804840

841+
// Set transparent window background for proper theming
842+
window.setBackgroundDrawableResource(android.R.color.transparent)
843+
805844
setContent {
806845
RenpytoolTheme(
807846
darkTheme = when (viewModel.themeMode.value) {
@@ -854,6 +893,9 @@ class MainActivity : ComponentActivity() {
854893
}
855894

856895
private fun showRandomKeyWarningDialog() {
896+
// Set transparent window background for proper theming
897+
window.setBackgroundDrawableResource(android.R.color.transparent)
898+
857899
setContent {
858900
RenpytoolTheme(
859901
darkTheme = when (viewModel.themeMode.value) {
@@ -888,6 +930,9 @@ class MainActivity : ComponentActivity() {
888930
// Load saved settings
889931
val settings = CompressionSettings.load(this)
890932

933+
// Set transparent window background for proper theming
934+
window.setBackgroundDrawableResource(android.R.color.transparent)
935+
891936
// Show settings dialog using Compose
892937
setContent {
893938
RenpytoolTheme(
@@ -903,7 +948,9 @@ class MainActivity : ComponentActivity() {
903948
// Save settings
904949
newSettings.save(this@MainActivity)
905950
// Start compression
906-
val intent = Intent(this@MainActivity, ProgressActivity::class.java)
951+
val intent = Intent(this@MainActivity, ProgressActivity::class.java).apply {
952+
putExtra("OPERATION_TYPE", "compress")
953+
}
907954
startActivity(intent)
908955
viewModel.performCompression(sourcePath, outputPath, newSettings, signingOption)
909956
// Reset to main UI
@@ -1025,6 +1072,8 @@ class MainActivity : ComponentActivity() {
10251072

10261073
override fun onResume() {
10271074
super.onResume()
1075+
// Recreate activity if theme changed while in settings
1076+
checkThemeChange()
10281077
// Update edit status when returning from editor
10291078
viewModel.updateEditStatus()
10301079
}

0 commit comments

Comments
 (0)