feat: add explicit Root Mode button, restore backup dialog, and fix Beta category parity
This commit is contained in:
1 parent
e7ca8d26af
commit
fefa57678d
4 files changed
+291
-89
No files matched your search
@@ -14,10 +14,28 @@ object SceneRepository {
|
||||
|
||||
suspend fun loadScenes(context: Context): List<PlanetItem> = withContext(Dispatchers.IO) {
|
||||
val cachedFile = File(context.filesDir, CACHED_SCENES_FILENAME)
|
||||
val defaultJson = context.assets.open("scenes.json").bufferedReader().use { it.readText() }
|
||||
|
||||
val jsonContent = if (cachedFile.exists()) {
|
||||
cachedFile.readText()
|
||||
try {
|
||||
val cachedText = cachedFile.readText()
|
||||
val cachedObj = JSONObject(cachedText)
|
||||
val defaultObj = JSONObject(defaultJson)
|
||||
val defaultBeta = defaultObj.optInt("total_beta_scenes", 0)
|
||||
val cachedBeta = cachedObj.optInt("total_beta_scenes", 0)
|
||||
// Discard stale cache if bundled asset has more beta scene definitions
|
||||
if (defaultBeta > cachedBeta) {
|
||||
cachedFile.delete()
|
||||
defaultJson
|
||||
} else {
|
||||
cachedText
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
cachedFile.delete()
|
||||
defaultJson
|
||||
}
|
||||
} else {
|
||||
context.assets.open("scenes.json").bufferedReader().use { it.readText() }
|
||||
defaultJson
|
||||
}
|
||||
parseJson(jsonContent)
|
||||
}
|
||||
@@ -81,7 +99,10 @@ object SceneRepository {
|
||||
val calyxPropId = sObj.optLong("calyx_prop_id", 808)
|
||||
|
||||
val combatReady = sObj.optBoolean("calyx_supported", false)
|
||||
val isBeta = sObj.optBoolean("is_beta", false)
|
||||
val isBeta = sObj.optBoolean("is_beta", false) ||
|
||||
sObj.optString("channel", "").equals("beta", ignoreCase = true) ||
|
||||
sObj.optString("client_version", "").contains("Beta", ignoreCase = true) ||
|
||||
planeId == 20571L || planeId == 20572L
|
||||
|
||||
val descEn = sObj.optString("desc", "")
|
||||
val descTh = sObj.optString("desc_th", descEn)
|
||||
|
||||
@@ -76,6 +76,38 @@ object StorageManager {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun connectRootMode(): Result<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val hasRoot = checkRootAccess()
|
||||
if (!hasRoot) {
|
||||
return@withContext Result.failure(Exception("Root permission denied / กรุณากดอนุญาต Superuser ใน Magisk/KernelSU"))
|
||||
}
|
||||
|
||||
// Ensure ppSR directories exist
|
||||
val dirCmd = "mkdir -p \"/data/data/$PPSR_PACKAGE_NAME/files/server/build/build/misc\""
|
||||
Runtime.getRuntime().exec(arrayOf("su", "-c", dirCmd)).waitFor()
|
||||
|
||||
// Check if persistent.json exists; create default template if not found
|
||||
val checkCmd = "[ -f \"$PPSR_INTERNAL_PERSISTENT_PATH\" ] && echo 'EXISTS'"
|
||||
val p = Runtime.getRuntime().exec(arrayOf("su", "-c", checkCmd))
|
||||
val output = p.inputStream.bufferedReader().use { it.readText().trim() }
|
||||
p.waitFor()
|
||||
|
||||
if (output != "EXISTS") {
|
||||
val initJson = "{\n \"scene\": {\n \"plane_id\": 20001,\n \"floor_id\": 20001001,\n \"entry_id\": 2000101,\n \"pos_x\": 0,\n \"pos_y\": 0,\n \"pos_z\": 0\n }\n}"
|
||||
val createCmd = "echo '$initJson' > \"$PPSR_INTERNAL_PERSISTENT_PATH\" && chmod 666 \"$PPSR_INTERNAL_PERSISTENT_PATH\""
|
||||
Runtime.getRuntime().exec(arrayOf("su", "-c", createCmd)).waitFor()
|
||||
} else {
|
||||
val permCmd = "chmod 666 \"$PPSR_INTERNAL_PERSISTENT_PATH\""
|
||||
Runtime.getRuntime().exec(arrayOf("su", "-c", permCmd)).waitFor()
|
||||
}
|
||||
|
||||
Result.success(PPSR_INTERNAL_PERSISTENT_PATH)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun readConfigViaRoot(): SceneConfig? = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val cmd = "cat \"$PPSR_INTERNAL_PERSISTENT_PATH\""
|
||||
@@ -96,13 +128,17 @@ object StorageManager {
|
||||
suspend fun writeConfigViaRoot(context: Context, newConfig: SceneConfig): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
// Ensure directory exists
|
||||
val dirCmd = "mkdir -p \"/data/data/$PPSR_PACKAGE_NAME/files/server/build/build/misc\""
|
||||
Runtime.getRuntime().exec(arrayOf("su", "-c", dirCmd)).waitFor()
|
||||
|
||||
// Read original content to preserve avatar, key, send_hotfix, etc.
|
||||
val readCmd = "cat \"$PPSR_INTERNAL_PERSISTENT_PATH\""
|
||||
val readProcess = Runtime.getRuntime().exec(arrayOf("su", "-c", readCmd))
|
||||
val originalContent = readProcess.inputStream.bufferedReader().use { it.readText().trim() }
|
||||
readProcess.waitFor()
|
||||
|
||||
// Save backup both inside ppSR folder and in local app cache
|
||||
// Save backup both inside ppSR folder (.bak) and in local app cache
|
||||
if (originalContent.isNotEmpty()) {
|
||||
try {
|
||||
val bakCmd = "cp \"$PPSR_INTERNAL_PERSISTENT_PATH\" \"$PPSR_INTERNAL_BACKUP_PATH\""
|
||||
|
||||
@@ -30,6 +30,8 @@ object UpdateManager {
|
||||
"https://api.github.com/repos/CAtlas0784/Escape-penacony/releases/latest"
|
||||
|
||||
private const val GITHUB_RAW_SCENES_URL =
|
||||
"https://raw.githubusercontent.com/CAtlas0784/Escape-penacony/testing/scenes.json"
|
||||
private const val GITHUB_RAW_SCENES_FALLBACK =
|
||||
"https://raw.githubusercontent.com/CAtlas0784/Escape-penacony/main/scenes.json"
|
||||
|
||||
suspend fun checkForAppUpdate(currentVersion: String = BuildConfig.VERSION_NAME): Result<ReleaseInfo?> =
|
||||
@@ -151,12 +153,20 @@ object UpdateManager {
|
||||
|
||||
suspend fun fetchLatestScenesJson(): Result<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val url = URL(GITHUB_RAW_SCENES_URL)
|
||||
val conn = url.openConnection() as HttpURLConnection
|
||||
val primaryUrl = URL(GITHUB_RAW_SCENES_URL)
|
||||
var conn = primaryUrl.openConnection() as HttpURLConnection
|
||||
conn.connectTimeout = 8000
|
||||
conn.readTimeout = 8000
|
||||
conn.setRequestProperty("User-Agent", "Escape-Penacony-Android-App")
|
||||
|
||||
if (conn.responseCode != 200) {
|
||||
val fallbackUrl = URL(GITHUB_RAW_SCENES_FALLBACK)
|
||||
conn = fallbackUrl.openConnection() as HttpURLConnection
|
||||
conn.connectTimeout = 8000
|
||||
conn.readTimeout = 8000
|
||||
conn.setRequestProperty("User-Agent", "Escape-Penacony-Android-App")
|
||||
}
|
||||
|
||||
if (conn.responseCode == 200) {
|
||||
val content = conn.inputStream.bufferedReader().use { it.readText() }
|
||||
Result.success(content)
|
||||
|
||||
@@ -55,6 +55,8 @@ fun MainScreen(
|
||||
var isRootMode by remember { mutableStateOf(false) }
|
||||
var currentActiveConfig by remember { mutableStateOf<SceneConfig?>(null) }
|
||||
var showCustomDialog by remember { mutableStateOf(false) }
|
||||
var showRestoreDialog by remember { mutableStateOf(false) }
|
||||
var isConnectingRoot by remember { mutableStateOf(false) }
|
||||
|
||||
// Update States
|
||||
var showUpdateDialog by remember { mutableStateOf(false) }
|
||||
@@ -69,7 +71,7 @@ fun MainScreen(
|
||||
language = AppLanguage.values().firstOrNull { it.code == savedLang } ?: AppLanguage.EN
|
||||
planets = SceneRepository.loadScenes(context)
|
||||
|
||||
// Check Root Mode
|
||||
// Check Root Mode silently if su permission already granted
|
||||
val hasRoot = StorageManager.checkRootAccess()
|
||||
val hasPpsr = if (hasRoot) StorageManager.checkPpsrInternalFileExists() else false
|
||||
isRootMode = hasRoot && hasPpsr
|
||||
@@ -108,7 +110,13 @@ fun MainScreen(
|
||||
// Filter scenes
|
||||
// When BETA category is selected, search globally across all planets so Starforster & Fleeting Joy always appear!
|
||||
val displayedScenes = remember(planetTabs, activePlanet, selectedCategory, searchQuery, language) {
|
||||
val baseScenes = if (selectedCategory == FilterCategory.BETA || selectedPlanetId == PLANET_ID_ALL) {
|
||||
val baseScenes = if (selectedCategory == FilterCategory.BETA) {
|
||||
if (activePlanet != null && activePlanet.id != PLANET_ID_ALL && activePlanet.scenes.any { it.isBeta }) {
|
||||
activePlanet.scenes
|
||||
} else {
|
||||
planets.flatMap { it.scenes }
|
||||
}
|
||||
} else if (selectedPlanetId == PLANET_ID_ALL) {
|
||||
planets.flatMap { it.scenes }
|
||||
} else {
|
||||
activePlanet?.scenes ?: emptyList()
|
||||
@@ -117,7 +125,7 @@ fun MainScreen(
|
||||
baseScenes.filter { scene ->
|
||||
val matchCategory = when (selectedCategory) {
|
||||
FilterCategory.ALL -> true
|
||||
FilterCategory.COMBAT -> scene.combatReady
|
||||
FilterCategory.COMBAT -> scene.combatReady && !scene.isBeta
|
||||
FilterCategory.EXPLORE -> !scene.combatReady && !scene.isBeta
|
||||
FilterCategory.BETA -> scene.isBeta
|
||||
}
|
||||
@@ -139,9 +147,9 @@ fun MainScreen(
|
||||
val list = if (selectedPlanetId == PLANET_ID_ALL) planets.flatMap { it.scenes } else activePlanet?.scenes ?: emptyList()
|
||||
mapOf(
|
||||
FilterCategory.ALL to list.size,
|
||||
FilterCategory.COMBAT to list.count { it.combatReady },
|
||||
FilterCategory.COMBAT to list.count { it.combatReady && !it.isBeta },
|
||||
FilterCategory.EXPLORE to list.count { !it.combatReady && !it.isBeta },
|
||||
FilterCategory.BETA to globalBetaCount
|
||||
FilterCategory.BETA to if (selectedPlanetId == PLANET_ID_ALL) globalBetaCount else list.count { it.isBeta }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -248,121 +256,188 @@ fun MainScreen(
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
color = DarkSurface
|
||||
) {
|
||||
Row(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
if (isRootMode) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Badge(text = "⚡ ROOT DIRECT", color = CombatGreen)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = "ppSR Auto-Synced",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp,
|
||||
color = CombatGreen
|
||||
)
|
||||
}
|
||||
if (currentActiveConfig != null) {
|
||||
Text(
|
||||
text = "Current: Plane ${currentActiveConfig?.planeId} (Layer ${currentActiveConfig?.mapLayer})",
|
||||
fontSize = 11.sp,
|
||||
color = NeonCyan
|
||||
)
|
||||
// Top Row: Status Badge & Target Info
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
if (isRootMode) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Badge(text = "⚡ ROOT DIRECT", color = CombatGreen)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = "ppSR Auto-Synced",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp,
|
||||
color = CombatGreen
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
if (currentActiveConfig != null) {
|
||||
Text(
|
||||
text = "Current: Plane ${currentActiveConfig?.planeId} (Layer ${currentActiveConfig?.mapLayer})",
|
||||
fontSize = 11.sp,
|
||||
color = NeonCyan
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "/data/data/com.afn.ppSR/files/server/build/build/misc/persistent.json",
|
||||
fontSize = 10.sp,
|
||||
color = TextMuted,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = "Connected to com.afn.ppSR internal storage",
|
||||
fontSize = 11.sp,
|
||||
color = TextMuted
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Badge(text = "📁 SAF MODE", color = ExploreAmber)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = if (currentPersistentUri != null) "persistent.json (Selected)" else "Target: Not Selected",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp,
|
||||
color = if (currentPersistentUri != null) CombatGreen else TextMuted
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
if (currentActiveConfig != null) {
|
||||
Text(
|
||||
text = "Current Plane: ${currentActiveConfig?.planeId} (Layer ${currentActiveConfig?.mapLayer})",
|
||||
fontSize = 11.sp,
|
||||
color = NeonCyan
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "เลือก persistent.json หรือกดเปิด '⚡ Root Mode' เพื่อซิงก์ ppSR อัตโนมัติ",
|
||||
fontSize = 11.sp,
|
||||
color = TextMuted
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = if (currentPersistentUri != null) "Target: persistent.json" else "Target: Not Selected (SAF Mode)",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp,
|
||||
color = if (currentPersistentUri != null) CombatGreen else TextMuted
|
||||
}
|
||||
|
||||
// Restore / Revert Backup Button (Always available at top right!)
|
||||
IconButton(
|
||||
onClick = { showRestoreDialog = true },
|
||||
modifier = Modifier.size(36.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Restore,
|
||||
contentDescription = "Restore Backup",
|
||||
tint = StarGold,
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
if (currentActiveConfig != null) {
|
||||
Text(
|
||||
text = "Current Plane: ${currentActiveConfig?.planeId} (Layer ${currentActiveConfig?.mapLayer})",
|
||||
fontSize = 11.sp,
|
||||
color = NeonCyan
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "Select persistent.json or grant Root for 1-tap teleport",
|
||||
fontSize = 11.sp,
|
||||
color = TextMuted
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
// Restore / Revert Backup Button
|
||||
IconButton(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
val res = StorageManager.restoreBackup(context, isRootMode, currentPersistentUri)
|
||||
res.onSuccess {
|
||||
if (isRootMode) {
|
||||
currentActiveConfig = StorageManager.readConfigViaRoot()
|
||||
} else if (currentPersistentUri != null) {
|
||||
currentActiveConfig = StorageManager.readCurrentConfig(context, currentPersistentUri)
|
||||
}
|
||||
Toast.makeText(context, "Restored backup persistent.json successfully!", Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { err ->
|
||||
Toast.makeText(context, "Restore failed: ${err.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.size(34.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Restore, contentDescription = "Restore Backup", tint = StarGold, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
// Bottom Row: Action Buttons
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (isRootMode) {
|
||||
// Restart ppSR Server button
|
||||
OutlinedButton(
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
Toast.makeText(context, "Restarting ppSR Server...", Toast.LENGTH_SHORT).show()
|
||||
val res = StorageManager.restartPpsrServerViaRoot()
|
||||
res.onSuccess {
|
||||
Toast.makeText(context, "ppSR Server restarted successfully!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, "รีสตาร์ท ppSR Server สำเร็จ!", Toast.LENGTH_SHORT).show()
|
||||
}.onFailure {
|
||||
Toast.makeText(context, "Restart failed: ${it.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
|
||||
modifier = Modifier.height(32.dp)
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = DarkSurfaceElevated
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp),
|
||||
modifier = Modifier.height(34.dp).weight(1f)
|
||||
) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Restart", modifier = Modifier.size(13.dp), tint = NeonCyan)
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Restart", modifier = Modifier.size(14.dp), tint = NeonCyan)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Restart ppSR", fontSize = 11.sp, color = NeonCyan)
|
||||
}
|
||||
|
||||
// Switch to SAF button
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
isRootMode = false
|
||||
coroutineScope.launch {
|
||||
if (currentPersistentUri != null) {
|
||||
currentActiveConfig = StorageManager.readCurrentConfig(context, currentPersistentUri)
|
||||
} else {
|
||||
currentActiveConfig = null
|
||||
}
|
||||
}
|
||||
Toast.makeText(context, "สลับเป็นโหมด SAF", Toast.LENGTH_SHORT).show()
|
||||
},
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp),
|
||||
modifier = Modifier.height(34.dp)
|
||||
) {
|
||||
Text("Switch to SAF", fontSize = 11.sp, color = TextMuted)
|
||||
}
|
||||
} else {
|
||||
// ⚡ Explicit Root Mode Button
|
||||
Button(
|
||||
onClick = onPickPersistentFile,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
isConnectingRoot = true
|
||||
val res = StorageManager.connectRootMode()
|
||||
isConnectingRoot = false
|
||||
res.onSuccess {
|
||||
isRootMode = true
|
||||
currentActiveConfig = StorageManager.readConfigViaRoot()
|
||||
Toast.makeText(context, "⚡ เชื่อมต่อ Root Direct (ppSR) สำเร็จ!", Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { err ->
|
||||
Toast.makeText(context, "เชื่อมต่อ Root ไม่สำเร็จ: ${err.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (currentPersistentUri != null) DarkSurfaceElevated else PurplePrimary
|
||||
containerColor = CombatGreen
|
||||
),
|
||||
enabled = !isConnectingRoot,
|
||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp),
|
||||
modifier = Modifier.height(32.dp)
|
||||
modifier = Modifier.height(34.dp).weight(1f)
|
||||
) {
|
||||
Icon(Icons.Default.Bolt, contentDescription = "Root", modifier = Modifier.size(16.dp), tint = Color.Black)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = if (currentPersistentUri != null) "Change" else "Choose File",
|
||||
fontSize = 11.sp
|
||||
if (isConnectingRoot) "Connecting..." else "⚡ Root Mode (ppSR)",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.Black
|
||||
)
|
||||
}
|
||||
|
||||
// SAF File Chooser
|
||||
OutlinedButton(
|
||||
onClick = onPickPersistentFile,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = if (currentPersistentUri != null) NeonCyan else TextMuted
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp),
|
||||
modifier = Modifier.height(34.dp)
|
||||
) {
|
||||
Icon(Icons.Default.FolderOpen, contentDescription = "Choose File", modifier = Modifier.size(14.dp))
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(if (currentPersistentUri != null) "Change File" else "Choose File", fontSize = 11.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -629,4 +704,64 @@ fun MainScreen(
|
||||
containerColor = DarkSurfaceElevated
|
||||
)
|
||||
}
|
||||
|
||||
// Restore Backup Confirmation Dialog
|
||||
if (showRestoreDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRestoreDialog = false },
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
Icons.Default.Restore,
|
||||
contentDescription = null,
|
||||
tint = StarGold,
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "ย้อนกลับไฟล์สำรอง (Restore)",
|
||||
color = TextLight,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = "ต้องการกู้คืน persistent.json จากไฟล์สำรอง (.bak) ล่าสุดหรือไม่?\n\nระบบจะนำข้อมูลพิกัดฉากและ Calyx ก่อนการแก้ไขล่าสุดกลับมาใช้งาน",
|
||||
fontSize = 13.sp,
|
||||
color = TextMuted
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
showRestoreDialog = false
|
||||
coroutineScope.launch {
|
||||
val res = StorageManager.restoreBackup(context, isRootMode, currentPersistentUri)
|
||||
res.onSuccess {
|
||||
if (isRootMode) {
|
||||
currentActiveConfig = StorageManager.readConfigViaRoot()
|
||||
} else if (currentPersistentUri != null) {
|
||||
currentActiveConfig = StorageManager.readCurrentConfig(context, currentPersistentUri)
|
||||
}
|
||||
Toast.makeText(context, "↩️ ย้อนกลับไฟล์สำรอง (.bak) สำเร็จ!", Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { err ->
|
||||
Toast.makeText(context, "ไม่สามารถย้อนกลับได้: ${err.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = StarGold)
|
||||
) {
|
||||
Text("ย้อนกลับไฟล์ (Restore)", color = Color.Black, fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showRestoreDialog = false }) {
|
||||
Text("ยกเลิก (Cancel)", color = TextMuted, fontSize = 12.sp)
|
||||
}
|
||||
},
|
||||
containerColor = DarkSurfaceElevated
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user