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