mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-09-20 17:51:13 +02:00
Kotlin: Code cleanup
This commit is contained in:
@@ -2,12 +2,12 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Android 11+ Package Visibility: was diese App sehen/ansprechen darf -->
|
||||
<!-- Android 11+ Package Visibility: What this app is allowed to see/access -->
|
||||
<queries>
|
||||
<!-- Falls andere Flavors/Starter beteiligt sind, hier sichtbar machen -->
|
||||
<!-- If other flavors/starters are involved, make them visible here -->
|
||||
<package android:name="org.kenjinx.android"/>
|
||||
|
||||
<!-- Allgemein: LAUNCHER-Activities und ACTION_VIEW-Dateien auflösbar machen -->
|
||||
<!-- General: Make LAUNCHER activities and ACTION_VIEW files resolvable -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
@@ -189,12 +188,12 @@ class GameController2(var activity: Activity) : IGameController {
|
||||
}
|
||||
|
||||
// --- Local copy of the config generation (name = generateConfig2, to avoid conflicts) ---
|
||||
private fun generateConfig2(isLeft: Boolean, useSwitchLayout: Boolean): RadialGamePadConfig {
|
||||
private fun generateConfig2(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return RadialGamePadConfig(
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (Stick) */
|
||||
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
|
||||
@@ -300,7 +299,7 @@ private fun generateConfig2(isLeft: Boolean, useSwitchLayout: Boolean): RadialGa
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return RadialGamePadConfig(
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
@@ -182,12 +181,12 @@ class GameController3(var activity: Activity) : IGameController {
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConfig3(isLeft: Boolean, useSwitchLayout: Boolean): RadialGamePadConfig {
|
||||
private fun generateConfig3(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return RadialGamePadConfig(
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (D-Pad) */
|
||||
PrimaryDialConfig.Cross(
|
||||
@@ -293,7 +292,7 @@ private fun generateConfig3(isLeft: Boolean, useSwitchLayout: Boolean): RadialGa
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return RadialGamePadConfig(
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
@@ -178,12 +177,12 @@ class GameController4(var activity: Activity) : IGameController {
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConfig4(isLeft: Boolean, useSwitchLayout: Boolean): RadialGamePadConfig {
|
||||
private fun generateConfig4(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return RadialGamePadConfig(
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (Stick) */
|
||||
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
|
||||
@@ -271,7 +270,7 @@ private fun generateConfig4(isLeft: Boolean, useSwitchLayout: Boolean): RadialGa
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return RadialGamePadConfig(
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
@@ -178,12 +177,12 @@ class GameController5(var activity: Activity) : IGameController {
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConfig5(isLeft: Boolean, useSwitchLayout: Boolean): RadialGamePadConfig {
|
||||
private fun generateConfig5(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return RadialGamePadConfig(
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (Stick) */
|
||||
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
|
||||
@@ -273,7 +272,7 @@ private fun generateConfig5(isLeft: Boolean, useSwitchLayout: Boolean): RadialGa
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return RadialGamePadConfig(
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
|
||||
@@ -177,7 +177,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||
|
||||
override fun onWindowVisibilityChanged(visibility: Int) {
|
||||
super.onWindowVisibilityChanged(visibility)
|
||||
if (visibility != android.view.View.VISIBLE) {
|
||||
if (visibility != VISIBLE) {
|
||||
ghLog("window not visible → shutdownBinding()")
|
||||
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
|
||||
shutdownBinding()
|
||||
@@ -370,7 +370,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||
val isSideFlip = (old == 1 && rotation == 3) || (old == 3 && rotation == 1)
|
||||
|
||||
if (isSideFlip) {
|
||||
try { KenjinxNative.setSurfaceRotationByAndroidRotation(rotation ?: 0) } catch (_: Throwable) {}
|
||||
try { KenjinxNative.setSurfaceRotationByAndroidRotation(rotation) } catch (_: Throwable) {}
|
||||
rebindNativeWindow(force = true)
|
||||
val now = android.os.SystemClock.uptimeMillis()
|
||||
if (now - lastKickAt >= 300L && _inputInitialized && MainActivity.mainViewModel?.rendererReady == true) {
|
||||
@@ -466,12 +466,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||
val act = mainViewModel.activity
|
||||
val intent = Intent(act, EmulationService::class.java)
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
act.startForegroundService(intent)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
act.startService(intent)
|
||||
}
|
||||
} catch (_: Throwable) { }
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.kenjinx.android
|
||||
|
||||
import android.os.Build
|
||||
import com.sun.jna.JNIEnv
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Native
|
||||
@@ -7,6 +8,7 @@ import org.kenjinx.android.viewmodels.GameInfo
|
||||
import java.util.Collections
|
||||
import android.view.Surface
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
interface KenjinxNativeJna : Library {
|
||||
fun deviceInitialize(
|
||||
@@ -173,7 +175,7 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
|
||||
backendThreading: Int
|
||||
): Boolean {
|
||||
val requested = backendThreading
|
||||
val isQcom = "qcom".equals(android.os.Build.HARDWARE, true)
|
||||
val isQcom = "qcom".equals(Build.HARDWARE, true)
|
||||
|
||||
// Heuristic: On QCOM, when "Auto" is selected, first try single-threaded mode;
|
||||
// explicitly set values remain unchanged.
|
||||
@@ -182,7 +184,7 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
|
||||
|
||||
Log.i(
|
||||
"KenjinxNative",
|
||||
"graphicsInitialize: request=$requested firstChoice=$firstChoice hw=${android.os.Build.HARDWARE}"
|
||||
"graphicsInitialize: request=$requested firstChoice=$firstChoice hw=${Build.HARDWARE}"
|
||||
)
|
||||
|
||||
return try {
|
||||
@@ -234,6 +236,7 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance {
|
||||
}
|
||||
// ----------------------------------------------------
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@JvmStatic
|
||||
fun frameEnded() = MainActivity.frameEnded()
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.WindowManager
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
@@ -43,7 +42,10 @@ import androidx.lifecycle.Lifecycle
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.IntentFilter
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.kenjinx.android.service.EmulationService
|
||||
import androidx.core.content.edit
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
private var physicalControllerManager: PhysicalControllerManager =
|
||||
@@ -151,7 +153,7 @@ class MainActivity : BaseActivity() {
|
||||
override fun run() {
|
||||
val isReallyResumed = lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) && isActive
|
||||
val hasFocusNow = hasWindowFocus()
|
||||
val rendererReady = MainActivity.mainViewModel?.rendererReady == true
|
||||
val rendererReady = mainViewModel?.rendererReady == true
|
||||
|
||||
if (!isReallyResumed || !hasFocusNow || !rendererReady) {
|
||||
handler.postDelayed(this, ENABLE_PRESENT_DELAY_MS)
|
||||
@@ -231,6 +233,7 @@ class MainActivity : BaseActivity() {
|
||||
const val EXTRA_TITLE_ID = "titleId"
|
||||
const val EXTRA_TITLE_NAME = "titleName"
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@JvmStatic
|
||||
fun frameEnded() {
|
||||
mainViewModel?.activity?.apply {
|
||||
@@ -276,6 +279,7 @@ class MainActivity : BaseActivity() {
|
||||
_isInit = KenjinxNative.javaInitialize(appPath, JNIEnv.CURRENT)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ensureNotificationPermission()
|
||||
@@ -283,16 +287,10 @@ class MainActivity : BaseActivity() {
|
||||
motionSensorManager = MotionSensorManager(this)
|
||||
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
|
||||
|
||||
if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
!Environment.isExternalStorageManager()
|
||||
} else {
|
||||
!Environment.isExternalStorageLegacy()
|
||||
}
|
||||
if (!Environment.isExternalStorageManager()
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
storageHelper?.storage?.requestFullStorageAccess()
|
||||
}
|
||||
}
|
||||
|
||||
AppPath = this.getExternalFilesDir(null)!!.absolutePath
|
||||
|
||||
@@ -335,10 +333,8 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
storedIntent = intent
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
rotLog("onCreate: initial display.rotation=${display?.rotation} → ${deg(display?.rotation)}°")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
@@ -379,13 +375,13 @@ class MainActivity : BaseActivity() {
|
||||
super.onStart()
|
||||
coldResetIfZombie("onStart")
|
||||
|
||||
if (isGameRunning && MainActivity.mainViewModel?.rendererReady == true) {
|
||||
if (isGameRunning && mainViewModel?.rendererReady == true) {
|
||||
try {
|
||||
KenjinxNative.graphicsSetPresentEnabled(true)
|
||||
Log.d(TAG_FG, "present=ENABLED (onStart)")
|
||||
} catch (_: Throwable) {}
|
||||
} else {
|
||||
Log.d(TAG_FG, "skip enable present (onStart) — rendererReady=${MainActivity.mainViewModel?.rendererReady}")
|
||||
Log.d(TAG_FG, "skip enable present (onStart) — rendererReady=${mainViewModel?.rendererReady}")
|
||||
setPresentEnabled(false, "cold reset: onStart (no game)")
|
||||
}
|
||||
}
|
||||
@@ -405,15 +401,15 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN && isGameRunning) {
|
||||
if (level >= TRIM_MEMORY_UI_HIDDEN && isGameRunning) {
|
||||
setAudioForegroundState(false)
|
||||
if (MainActivity.mainViewModel?.rendererReady == true) {
|
||||
if (mainViewModel?.rendererReady == true) {
|
||||
try {
|
||||
setPresentEnabled(false, "onTrimMemory:$level")
|
||||
KenjinxNative.detachWindow()
|
||||
} catch (_: Throwable) {}
|
||||
} else {
|
||||
Log.d(TAG_FG, "skip disable present (onTrimMemory) — rendererReady=${MainActivity.mainViewModel?.rendererReady}")
|
||||
Log.d(TAG_FG, "skip disable present (onTrimMemory) — rendererReady=${mainViewModel?.rendererReady}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,7 +437,7 @@ class MainActivity : BaseActivity() {
|
||||
registerReceiver(
|
||||
serviceStopReceiver,
|
||||
IntentFilter(EmulationService.ACTION_STOPPED),
|
||||
Context.RECEIVER_EXPORTED
|
||||
RECEIVER_EXPORTED
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -495,6 +491,7 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
isActive = false
|
||||
@@ -583,6 +580,7 @@ class MainActivity : BaseActivity() {
|
||||
try { KenjinxNative.setSurfaceRotationByAndroidRotation(rot) } catch (_: Throwable) {}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
val rot = this.display?.rotation
|
||||
@@ -614,7 +612,7 @@ class MainActivity : BaseActivity() {
|
||||
) { /* optional: Log/Toast */ }
|
||||
|
||||
private fun ensureNotificationPermission() {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 33) {
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
val granted = ContextCompat.checkSelfPermission(
|
||||
this, Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
@@ -674,6 +672,7 @@ class MainActivity : BaseActivity() {
|
||||
} catch (_: Exception) { null }
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun shutdownAndRestart() {
|
||||
val packageManager = packageManager
|
||||
val intent = packageManager.getLaunchIntentForPackage(packageName)
|
||||
@@ -696,10 +695,10 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
private fun setEmuRunningFlag(value: Boolean) {
|
||||
try {
|
||||
getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(KEY_EMU_RUNNING, value)
|
||||
.apply()
|
||||
getSharedPreferences(PREFS, MODE_PRIVATE)
|
||||
.edit {
|
||||
putBoolean(KEY_EMU_RUNNING, value)
|
||||
}
|
||||
} catch (_: Throwable) { }
|
||||
}
|
||||
|
||||
@@ -723,7 +722,7 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
private fun coldResetIfZombie(phase: String) {
|
||||
try {
|
||||
val zombie = getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
val zombie = getSharedPreferences(PREFS, MODE_PRIVATE)
|
||||
.getBoolean(KEY_EMU_RUNNING, false)
|
||||
if (zombie) {
|
||||
clearEmuRunningFlag()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package org.kenjinx.android
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import kotlin.math.abs
|
||||
|
||||
class PerformanceManager(private val activity: MainActivity) {
|
||||
companion object {
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun force60HzRefreshRate(enable: Boolean, activity: MainActivity) {
|
||||
// Hack for MIUI devices since they don't support the standard Android APIs
|
||||
try {
|
||||
@@ -25,6 +28,7 @@ class PerformanceManager(private val activity: MainActivity) {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun setTurboMode(enable: Boolean) {
|
||||
NativeHelpers.instance.setTurboMode(enable)
|
||||
force60HzRefreshRate(enable, activity)
|
||||
|
||||
@@ -6,12 +6,12 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.net.toUri
|
||||
|
||||
object ShortcutHelper {
|
||||
|
||||
@@ -33,7 +33,7 @@ object ShortcutHelper {
|
||||
gridIconBitmap: Bitmap? = null,
|
||||
gridIconBase64: String? = null
|
||||
) {
|
||||
val uri = runCatching { Uri.parse(bootPathUri) }.getOrNull()
|
||||
val uri = runCatching { bootPathUri.toUri() }.getOrNull()
|
||||
|
||||
// --- Select icon (Grid bitmap > Base64 > App icon)
|
||||
var icon = IconCompat.createWithResource(context, R.mipmap.ic_launcher)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.kenjinx.android
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
@@ -16,9 +17,8 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
object ShortcutUtils {
|
||||
|
||||
fun suggestLabelFromUri(uri: Uri): String {
|
||||
@@ -47,7 +47,6 @@ object ShortcutUtils {
|
||||
iconBitmap: Bitmap? = null,
|
||||
onCompleted: (() -> Unit)? = null
|
||||
): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
|
||||
val sm = activity.getSystemService(ShortcutManager::class.java) ?: return false
|
||||
|
||||
val rw = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
@@ -118,7 +117,7 @@ object ShortcutUtils {
|
||||
}
|
||||
val filter = IntentFilter(ACTION_PIN_RESULT)
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
activity.registerReceiver(pinResultReceiver!!, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||
activity.registerReceiver(pinResultReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||
} else {
|
||||
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||
activity.registerReceiver(pinResultReceiver, filter)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.kenjinx.android
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
@@ -14,6 +15,7 @@ import android.util.Base64
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.kenjinx.android.viewmodels.GameInfo
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
class ShortcutWizardActivity : Activity() {
|
||||
|
||||
private val REQ_PICK_GAME = 1
|
||||
|
||||
@@ -100,7 +100,7 @@ class UiHandler {
|
||||
if (inputText.value.isEmpty()) {
|
||||
validation.value = "Must be between $minLength and $maxLength characters"
|
||||
} else {
|
||||
return inputText.value.length < minLength || inputText.value.length > maxLength
|
||||
return inputText.value.length !in minLength..maxLength
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -116,7 +116,7 @@ class UiHandler {
|
||||
|
||||
fun submit() {
|
||||
if (type == 2) {
|
||||
if (inputListener.value.length < minLength || inputListener.value.length > maxLength) return
|
||||
if (inputListener.value.length !in minLength..maxLength) return
|
||||
}
|
||||
KenjinxNative.uiHandlerSetResponse(true, if (type == 2) inputListener.value else "")
|
||||
showMessageListener.value = false
|
||||
|
||||
@@ -30,7 +30,7 @@ private fun allCheatDirs(activity: Activity, titleId: String): List<File> {
|
||||
private fun parseCheatNames(text: String): List<String> {
|
||||
// Trim BOM, CRLF tolerant
|
||||
val clean = text.replace("\uFEFF", "")
|
||||
val rx = Regex("""(?m)^\s*\[(.+?)\]\s*$""")
|
||||
val rx = Regex("""(?m)^\s*\[(.+?)]\s*$""")
|
||||
return rx.findAll(clean)
|
||||
.map { it.groupValues[1].trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.kenjinx.android.cheats
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
|
||||
class CheatPrefs(private val context: Context) {
|
||||
private fun key(titleId: String) = "cheats_$titleId"
|
||||
@@ -11,6 +12,6 @@ class CheatPrefs(private val context: Context) {
|
||||
}
|
||||
|
||||
fun setEnabled(titleId: String, keys: Set<String>) {
|
||||
prefs.edit().putStringSet(key(titleId), keys.toSet()).apply()
|
||||
prefs.edit { putStringSet(key(titleId), keys.toSet()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
/* -------- Paths -------- */
|
||||
|
||||
@@ -246,7 +246,7 @@ fun importSaveFromZip(
|
||||
}
|
||||
|
||||
if (titleIdFromZip == null) return ImportResult(false, "error importing save. missing TITLEID folder")
|
||||
val tidZip = titleIdFromZip!!.lowercase(Locale.ROOT)
|
||||
val tidZip = titleIdFromZip.lowercase(Locale.ROOT)
|
||||
|
||||
// Sum the size only under /<TITLEID>/…
|
||||
runCatching {
|
||||
|
||||
+2
-9
@@ -5,10 +5,8 @@ import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import org.kenjinx.android.MainActivity
|
||||
@@ -138,10 +136,7 @@ class EmulationService : Service() {
|
||||
|
||||
private fun buildNotification(): Notification {
|
||||
val openIntent = Intent(this, MainActivity::class.java)
|
||||
val flags = if (Build.VERSION.SDK_INT >= 23)
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
else
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
val pi = PendingIntent.getActivity(this, 0, openIntent, flags)
|
||||
|
||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
@@ -156,8 +151,7 @@ class EmulationService : Service() {
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
val mgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val mgr = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
val ch = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"Emulation",
|
||||
@@ -167,4 +161,3 @@ class EmulationService : Service() {
|
||||
mgr.createNotificationChannel(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,11 +212,9 @@ class DlcViewModel(val titleId: String) {
|
||||
}
|
||||
|
||||
var data: MutableList<DlcContainerList>? = null
|
||||
private var jsonPath: String
|
||||
private var jsonPath: String = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) + "/dlc.json"
|
||||
|
||||
init {
|
||||
jsonPath =
|
||||
MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) + "/dlc.json"
|
||||
storageHelper = MainActivity.StorageHelper!!
|
||||
|
||||
reloadFromDisk()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.kenjinx.android.viewmodels
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
@@ -10,10 +11,15 @@ import org.kenjinx.android.KenjinxNative
|
||||
|
||||
class GameModel(var file: DocumentFile, val context: Context) {
|
||||
private var updateDescriptor: ParcelFileDescriptor? = null
|
||||
var type: FileType
|
||||
var type: FileType = when {
|
||||
(file.extension == "xci") -> FileType.Xci
|
||||
(file.extension == "nsp") -> FileType.Nsp
|
||||
(file.extension == "nro") -> FileType.Nro
|
||||
else -> FileType.None
|
||||
}
|
||||
var descriptor: ParcelFileDescriptor? = null
|
||||
var fileName: String?
|
||||
var fileBaseName: String?
|
||||
var fileName = file.name
|
||||
var fileBaseName: String? = fileName?.substringBeforeLast(".", fileName!!)
|
||||
var fileSize = 0.0
|
||||
var titleName: String? = null
|
||||
var titleId: String? = null
|
||||
@@ -21,17 +27,6 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
||||
var version: String? = null
|
||||
var icon: String? = null
|
||||
|
||||
init {
|
||||
fileName = file.name
|
||||
fileBaseName = fileName?.substringBeforeLast(".", fileName!!)
|
||||
type = when {
|
||||
(file.extension == "xci") -> FileType.Xci
|
||||
(file.extension == "nsp") -> FileType.Nsp
|
||||
(file.extension == "nro") -> FileType.Nro
|
||||
else -> FileType.None
|
||||
}
|
||||
}
|
||||
|
||||
fun getGameInfo() {
|
||||
val pid = open()
|
||||
val gameInfo = GameInfo()
|
||||
@@ -60,6 +55,7 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
||||
return descriptor?.fd ?: 0
|
||||
}
|
||||
|
||||
@SuppressLint("UseKtx")
|
||||
fun openUpdate(): Int {
|
||||
if (titleId?.isNotEmpty() == true) {
|
||||
val vm = TitleUpdateViewModel(titleId ?: "")
|
||||
|
||||
+4
-2
@@ -27,6 +27,8 @@ import org.kenjinx.android.UiHandler
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.util.TimeZone
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
class MainViewModel(val activity: MainActivity) {
|
||||
@@ -62,7 +64,7 @@ class MainViewModel(val activity: MainActivity) {
|
||||
set(value) {
|
||||
field = value
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
prefs.edit().putString("defaultGameFolderUri", value?.toString() ?: "").apply()
|
||||
prefs.edit { putString("defaultGameFolderUri", value?.toString() ?: "") }
|
||||
}
|
||||
var gameHost: GameHost? = null
|
||||
set(value) {
|
||||
@@ -79,7 +81,7 @@ class MainViewModel(val activity: MainActivity) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
val saved = prefs.getString("defaultGameFolderUri", "") ?: ""
|
||||
if (saved.isNotEmpty()) {
|
||||
defaultGameFolderUri = Uri.parse(saved)
|
||||
defaultGameFolderUri = saved.toUri()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -16,7 +16,7 @@ import java.io.File
|
||||
|
||||
class TitleUpdateViewModel(val titleId: String) {
|
||||
private var canClose: MutableState<Boolean>? = null
|
||||
private var basePath: String
|
||||
private var basePath: String = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
|
||||
private var updateJsonName = "updates.json"
|
||||
private var storageHelper: SimpleStorageHelper
|
||||
private var currentPaths: MutableList<String> = mutableListOf()
|
||||
@@ -187,11 +187,9 @@ class TitleUpdateViewModel(val titleId: String) {
|
||||
}
|
||||
|
||||
var data: TitleUpdateMetadata? = null
|
||||
private var jsonPath: String
|
||||
private var jsonPath: String = "${basePath}/${updateJsonName}"
|
||||
|
||||
init {
|
||||
basePath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
|
||||
jsonPath = "${basePath}/${updateJsonName}"
|
||||
|
||||
data = TitleUpdateMetadata()
|
||||
if (File(jsonPath).exists()) {
|
||||
|
||||
@@ -53,7 +53,6 @@ import org.kenjinx.android.viewmodels.VSyncMode
|
||||
import org.kenjinx.android.widgets.SimpleAlertDialog
|
||||
import java.util.Locale
|
||||
import kotlin.math.roundToInt
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -61,6 +60,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import org.kenjinx.android.viewmodels.QuickSettings.VirtualControllerPreset
|
||||
import androidx.core.net.toUri
|
||||
|
||||
|
||||
class GameViews {
|
||||
@@ -338,7 +338,7 @@ class GameViews {
|
||||
Toast.makeText(mainViewModel.activity, "Slot 1 is empty.", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
try {
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(Uri.parse(u))?.use { it.readBytes() }
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(u.toUri())?.use { it.readBytes() }
|
||||
if (bytes != null && bytes.isNotEmpty()) {
|
||||
val ok = KenjinxNative.amiiboLoadBin(bytes, bytes.size)
|
||||
if (ok) Toast.makeText(mainViewModel.activity, "Loaded: $name", Toast.LENGTH_SHORT).show()
|
||||
@@ -346,7 +346,7 @@ class GameViews {
|
||||
} else Toast.makeText(mainViewModel.activity, "File not readable.", Toast.LENGTH_SHORT).show()
|
||||
} catch (t: Throwable) { Toast.makeText(mainViewModel.activity, "Error: ${t.message}", Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
}) { androidx.compose.material3.Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo1Name, 1)) }
|
||||
}) { Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo1Name, 1)) }
|
||||
|
||||
androidx.compose.material3.Button(onClick = {
|
||||
val qs = QuickSettings(mainViewModel.activity)
|
||||
@@ -356,7 +356,7 @@ class GameViews {
|
||||
Toast.makeText(mainViewModel.activity, "Slot 2 is empty.", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
try {
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(Uri.parse(u))?.use { it.readBytes() }
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(u.toUri())?.use { it.readBytes() }
|
||||
if (bytes != null && bytes.isNotEmpty()) {
|
||||
val ok = KenjinxNative.amiiboLoadBin(bytes, bytes.size)
|
||||
if (ok) Toast.makeText(mainViewModel.activity, "Loaded: $name", Toast.LENGTH_SHORT).show()
|
||||
@@ -364,7 +364,7 @@ class GameViews {
|
||||
} else Toast.makeText(mainViewModel.activity, "File not readable.", Toast.LENGTH_SHORT).show()
|
||||
} catch (t: Throwable) { Toast.makeText(mainViewModel.activity, "Error: ${t.message}", Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
}) { androidx.compose.material3.Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo2Name, 2)) }
|
||||
}) { Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo2Name, 2)) }
|
||||
|
||||
androidx.compose.material3.Button(onClick = {
|
||||
val qs = QuickSettings(mainViewModel.activity)
|
||||
@@ -374,7 +374,7 @@ class GameViews {
|
||||
Toast.makeText(mainViewModel.activity, "Slot 3 is empty.", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
try {
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(Uri.parse(u))?.use { it.readBytes() }
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(u.toUri())?.use { it.readBytes() }
|
||||
if (bytes != null && bytes.isNotEmpty()) {
|
||||
val ok = KenjinxNative.amiiboLoadBin(bytes, bytes.size)
|
||||
if (ok) Toast.makeText(mainViewModel.activity, "Loaded: $name", Toast.LENGTH_SHORT).show()
|
||||
@@ -382,7 +382,7 @@ class GameViews {
|
||||
} else Toast.makeText(mainViewModel.activity, "File not readable.", Toast.LENGTH_SHORT).show()
|
||||
} catch (t: Throwable) { Toast.makeText(mainViewModel.activity, "Error: ${t.message}", Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
}) { androidx.compose.material3.Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo3Name, 3)) }
|
||||
}) { Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo3Name, 3)) }
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
@@ -394,7 +394,7 @@ class GameViews {
|
||||
Toast.makeText(mainViewModel.activity, "Slot 4 is empty.", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
try {
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(Uri.parse(u))?.use { it.readBytes() }
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(u.toUri())?.use { it.readBytes() }
|
||||
if (bytes != null && bytes.isNotEmpty()) {
|
||||
val ok = KenjinxNative.amiiboLoadBin(bytes, bytes.size)
|
||||
if (ok) Toast.makeText(mainViewModel.activity, "Loaded: $name", Toast.LENGTH_SHORT).show()
|
||||
@@ -402,7 +402,7 @@ class GameViews {
|
||||
} else Toast.makeText(mainViewModel.activity, "File not readable.", Toast.LENGTH_SHORT).show()
|
||||
} catch (t: Throwable) { Toast.makeText(mainViewModel.activity, "Error: ${t.message}", Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
}) { androidx.compose.material3.Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo4Name, 4)) }
|
||||
}) { Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo4Name, 4)) }
|
||||
|
||||
androidx.compose.material3.Button(onClick = {
|
||||
val qs = QuickSettings(mainViewModel.activity)
|
||||
@@ -412,7 +412,7 @@ class GameViews {
|
||||
Toast.makeText(mainViewModel.activity, "Slot 5 is empty.", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
try {
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(Uri.parse(u))?.use { it.readBytes() }
|
||||
val bytes = mainViewModel.activity.contentResolver.openInputStream(u.toUri())?.use { it.readBytes() }
|
||||
if (bytes != null && bytes.isNotEmpty()) {
|
||||
val ok = KenjinxNative.amiiboLoadBin(bytes, bytes.size)
|
||||
if (ok) Toast.makeText(mainViewModel.activity, "Loaded: $name", Toast.LENGTH_SHORT).show()
|
||||
@@ -420,12 +420,12 @@ class GameViews {
|
||||
} else Toast.makeText(mainViewModel.activity, "File not readable.", Toast.LENGTH_SHORT).show()
|
||||
} catch (t: Throwable) { Toast.makeText(mainViewModel.activity, "Error: ${t.message}", Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
}) { androidx.compose.material3.Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo5Name, 5)) }
|
||||
}) { Text(qsLabel(QuickSettings(mainViewModel.activity).amiibo5Name, 5)) }
|
||||
|
||||
androidx.compose.material3.OutlinedButton(onClick = {
|
||||
KenjinxNative.amiiboClear()
|
||||
Toast.makeText(mainViewModel.activity, "Amiibo cleared", Toast.LENGTH_SHORT).show()
|
||||
}) { androidx.compose.material3.Text("Clear") }
|
||||
}) { Text("Clear") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,12 +92,12 @@ import org.kenjinx.android.viewmodels.HomeViewModel
|
||||
import org.kenjinx.android.viewmodels.QuickSettings
|
||||
import org.kenjinx.android.widgets.SimpleAlertDialog
|
||||
import org.kenjinx.android.ShortcutUtils
|
||||
import org.kenjinx.android.ShortcutWizardActivity
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
|
||||
// Cheats
|
||||
@@ -162,19 +162,19 @@ class HomeViews {
|
||||
var isFabVisible by remember { mutableStateOf(true) }
|
||||
val isNavigating = remember { mutableStateOf(false) }
|
||||
|
||||
// NEW: Amiibo slot picker state
|
||||
// Amiibo slot picker state
|
||||
val showAmiiboSlotDialog = remember { mutableStateOf(false) }
|
||||
val pendingSlot = remember { mutableStateOf(1) }
|
||||
|
||||
// NEW: Cheats UI state
|
||||
// Cheats UI state
|
||||
val openCheatsDialog = remember { mutableStateOf(false) }
|
||||
val cheatsForSelected = remember { mutableStateOf(listOf<CheatItem>()) }
|
||||
val enabledCheatKeys = remember { mutableStateOf(mutableSetOf<String>()) }
|
||||
|
||||
// NEW: Mods UI state
|
||||
// Mods UI state
|
||||
val openModsDialog = remember { mutableStateOf(false) }
|
||||
val modsForSelected = remember { mutableStateOf(listOf<String>()) }
|
||||
val modsImportProgress = remember { mutableStateOf(0f) }
|
||||
val modsImportProgress = remember { mutableFloatStateOf(0f) }
|
||||
val modsImportBusy = remember { mutableStateOf(false) }
|
||||
val modsImportStatusText = remember { mutableStateOf("") }
|
||||
|
||||
@@ -182,8 +182,8 @@ class HomeViews {
|
||||
val openSavesDialog = remember { mutableStateOf(false) }
|
||||
val saveImportBusy = remember { mutableStateOf(false) }
|
||||
val saveExportBusy = remember { mutableStateOf(false) }
|
||||
val saveImportProgress = remember { mutableStateOf(0f) }
|
||||
val saveExportProgress = remember { mutableStateOf(0f) }
|
||||
val saveImportProgress = remember { mutableFloatStateOf(0f) }
|
||||
val saveExportProgress = remember { mutableFloatStateOf(0f) }
|
||||
val saveImportStatus = remember { mutableStateOf("") }
|
||||
val saveExportStatus = remember { mutableStateOf("") }
|
||||
|
||||
@@ -198,13 +198,13 @@ class HomeViews {
|
||||
val tIdNow = viewModel.mainViewModel?.selected?.titleId.orEmpty()
|
||||
if (uri != null && act != null && tIdNow.isNotEmpty()) {
|
||||
saveImportBusy.value = true
|
||||
saveImportProgress.value = 0f
|
||||
saveImportProgress.floatValue = 0f
|
||||
saveImportStatus.value = "Starting…"
|
||||
|
||||
thread {
|
||||
val res = importSaveFromZip(act, uri) { prog ->
|
||||
val frac = if (prog.total > 0) prog.bytes.toFloat() / prog.total else 0f
|
||||
saveImportProgress.value = frac.coerceIn(0f, 1f)
|
||||
saveImportProgress.floatValue = frac.coerceIn(0f, 1f)
|
||||
saveImportStatus.value = "Importing: ${prog.currentEntry}"
|
||||
}
|
||||
saveImportBusy.value = false
|
||||
@@ -223,13 +223,13 @@ class HomeViews {
|
||||
val tIdNow = viewModel.mainViewModel?.selected?.titleId.orEmpty()
|
||||
if (uri != null && act != null && tIdNow.isNotEmpty()) {
|
||||
saveExportBusy.value = true
|
||||
saveExportProgress.value = 0f
|
||||
saveExportProgress.floatValue = 0f
|
||||
saveExportStatus.value = "Starting…"
|
||||
|
||||
thread {
|
||||
val res = exportSaveToZip(act, tIdNow, uri) { prog ->
|
||||
val frac = if (prog.total > 0) prog.bytes.toFloat() / prog.total else 0f
|
||||
saveExportProgress.value = frac.coerceIn(0f, 1f)
|
||||
saveExportProgress.floatValue = frac.coerceIn(0f, 1f)
|
||||
saveExportStatus.value = "Exporting: ${prog.currentPath}"
|
||||
}
|
||||
saveExportBusy.value = false
|
||||
@@ -250,7 +250,7 @@ class HomeViews {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
// NEW: Amiibo Launcher (OpenDocument)
|
||||
// Amiibo Launcher (OpenDocument)
|
||||
val pickAmiiboLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument()
|
||||
) { uri: Uri? ->
|
||||
@@ -275,7 +275,7 @@ class HomeViews {
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Cheats Import (.txt)
|
||||
// Cheats Import (.txt)
|
||||
val importCheatLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument()
|
||||
) { uri: Uri? ->
|
||||
@@ -316,7 +316,7 @@ class HomeViews {
|
||||
} catch (_: Exception) {}
|
||||
|
||||
modsImportBusy.value = true
|
||||
modsImportProgress.value = 0f
|
||||
modsImportProgress.floatValue = 0f
|
||||
modsImportStatusText.value = "Starting…"
|
||||
|
||||
thread {
|
||||
@@ -325,7 +325,7 @@ class HomeViews {
|
||||
titleId,
|
||||
uri
|
||||
) { prog ->
|
||||
modsImportProgress.value = prog.fraction
|
||||
modsImportProgress.floatValue = prog.fraction
|
||||
modsImportStatusText.value = if (prog.currentEntry.isNotEmpty())
|
||||
"Copying: ${prog.currentEntry}"
|
||||
else
|
||||
@@ -642,7 +642,7 @@ class HomeViews {
|
||||
|
||||
// Amiibo slot chooser dialog (outside of game)
|
||||
if (showAmiiboSlotDialog.value) {
|
||||
androidx.compose.material3.AlertDialog(
|
||||
AlertDialog(
|
||||
onDismissRequest = { showAmiiboSlotDialog.value = false },
|
||||
title = { Text("Import Amiibo") },
|
||||
text = {
|
||||
@@ -732,7 +732,7 @@ class HomeViews {
|
||||
if (viewModel.mainViewModel?.selected != null) {
|
||||
|
||||
// Press the cheat buttons before starting (Run button).
|
||||
val gmSel = viewModel.mainViewModel!!.selected!!
|
||||
val gmSel = viewModel.mainViewModel.selected!!
|
||||
val tId = gmSel.titleId ?: ""
|
||||
val act = viewModel.activity
|
||||
|
||||
@@ -1125,7 +1125,7 @@ class HomeViews {
|
||||
.padding(top = 12.dp),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
androidx.compose.material3.TextButton(
|
||||
TextButton(
|
||||
onClick = { openSavesDialog.value = false }
|
||||
) { Text("Close") }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user