diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/MainActivity.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/MainActivity.kt index 21c2dfe..ee5f305 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/MainActivity.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/MainActivity.kt @@ -142,7 +142,7 @@ class MainActivity : ComponentActivity() { } val targetDir = when (componentType) { - "injector" -> paths.customInjectorsDir + "tidal" -> paths.customTidalApksDir "patches" -> paths.customPatchesDir else -> { Log.w(BuildConfig.TAG, "Extra $EXTRA_COMPONENT_TYPE is not a valid value!") diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/manager/PathManager.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/manager/PathManager.kt index cf91260..303a287 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/manager/PathManager.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/manager/PathManager.kt @@ -26,7 +26,7 @@ class PathManager( val customComponentsDir = patchingDir.resolve("custom") - val customInjectorsDir = customComponentsDir.resolve("injector") + val customTidalApksDir = customComponentsDir.resolve("tidal") val customPatchesDir = customComponentsDir.resolve("patches") @@ -54,7 +54,7 @@ class PathManager( .resolve("patches") .resolve("$version.zip") - fun customInjectors() = customInjectorsDir.listFiles()?.asList() ?: emptyList() + fun customTidalApks() = customTidalApksDir.listFiles()?.asList() ?: emptyList() fun customSmaliPatches() = customPatchesDir.listFiles()?.asList() ?: emptyList() } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/manager/PreferencesManager.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/manager/PreferencesManager.kt index 73c5e74..5c449b1 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/manager/PreferencesManager.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/manager/PreferencesManager.kt @@ -15,4 +15,8 @@ class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager var showNetworkWarning by booleanPreference("show_network_warning", true) var showPlayProtectWarning by booleanPreference("show_play_protect_warning", true) var autoUpdateCheck by booleanPreference("auto_update_check", true) + + var lastSeenManagerVersion by stringPreference("last_seen_manager_version", "") + var lastSeenPatchesVersion by stringPreference("last_seen_patches_version", "") + var lastSeenTidalVersionCode by intPreference("last_seen_tidal_version_code", -1) } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/TidalPatchRunner.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/TidalPatchRunner.kt index 87ffe0e..5c9907e 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/TidalPatchRunner.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/TidalPatchRunner.kt @@ -17,7 +17,7 @@ class TidalPatchRunner( RestoreDownloadsStep(), // Download - DownloadTidalStep(), + DownloadTidalStep(options.customTidalApk), DownloadPatchesStep(options.customPatches), CopyDependenciesStep(), diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/download/DownloadTidalStep.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/download/DownloadTidalStep.kt index e08b754..3f47c0a 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/download/DownloadTidalStep.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/download/DownloadTidalStep.kt @@ -5,12 +5,17 @@ import com.meowarex.rlmobile.R import com.meowarex.rlmobile.manager.PathManager import com.meowarex.rlmobile.patcher.StepRunner import com.meowarex.rlmobile.patcher.steps.base.DownloadStep +import com.meowarex.rlmobile.patcher.steps.base.StepState import com.meowarex.rlmobile.patcher.steps.prepare.FetchInfoStep +import com.meowarex.rlmobile.ui.screens.componentopts.PatchComponent import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import java.io.FileNotFoundException @Stable -class DownloadTidalStep : DownloadStep(), KoinComponent { +class DownloadTidalStep( + private val custom: PatchComponent?, +) : DownloadStep(), KoinComponent { private val paths: PathManager by inject() override val localizedName = R.string.patch_step_dl_tidal_apk @@ -22,5 +27,23 @@ class DownloadTidalStep : DownloadStep(), KoinComponent { container.getStep().data.tidalApkUrl override fun getStoredFile(container: StepRunner) = - paths.cachedTidalApk(getVersion(container)) + custom?.getFile(paths) ?: paths.cachedTidalApk(getVersion(container)) + + override suspend fun execute(container: StepRunner) { + if (custom != null) { + container.log("Using custom TIDAL APK with version ${custom.version} imported ${custom.timestamp}") + + if (!custom.getFile(paths).exists()) { + throw FileNotFoundException( + "Selected custom TIDAL APK does not exist on disk! If this is an update, " + + "updates cannot occur when the originally selected custom component has been deleted." + ) + } + + state = StepState.Skipped + return + } + + super.execute(container) + } } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/components/Tag.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/components/Tag.kt new file mode 100644 index 0000000..2faffc8 --- /dev/null +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/components/Tag.kt @@ -0,0 +1,25 @@ +package com.meowarex.rlmobile.ui.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun Tag(text: String) { + Surface( + color = MaterialTheme.colorScheme.primaryContainer, + shape = RoundedCornerShape(6.dp), + ) { + Text( + text = text, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp), + ) + } +} diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/previews/screens/ComponentOptionsScreenPreview.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/previews/screens/ComponentOptionsScreenPreview.kt index 7c3f56a..4aec8d9 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/previews/screens/ComponentOptionsScreenPreview.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/previews/screens/ComponentOptionsScreenPreview.kt @@ -44,27 +44,27 @@ private data class ComponentOptionsParameters( private class ComponentOptionsParametersProvider : PreviewParameterProvider { private val components = persistentListOf( PatchComponent( - type = PatchComponent.Type.Injector, + type = PatchComponent.Type.TidalApk, version = SemVer(1, 2, 3), timestamp = Clock.System.now(), ), PatchComponent( - type = PatchComponent.Type.Injector, + type = PatchComponent.Type.TidalApk, version = SemVer(2, 3, 1), timestamp = Clock.System.now() - 10.minutes, ), PatchComponent( - type = PatchComponent.Type.Injector, + type = PatchComponent.Type.TidalApk, version = SemVer(2, 3, 1), timestamp = Clock.System.now() - 1.days, ), PatchComponent( - type = PatchComponent.Type.Injector, + type = PatchComponent.Type.TidalApk, version = SemVer(0, 0, 1), timestamp = Clock.System.now() - 10.hours, ), PatchComponent( - type = PatchComponent.Type.Injector, + type = PatchComponent.Type.TidalApk, version = SemVer(3, 0, 2), timestamp = Clock.System.now() - 7.days, ), @@ -72,7 +72,7 @@ private class ComponentOptionsParametersProvider : PreviewParameterProvider paths.customInjectors() + PatchComponent.Type.TidalApk -> paths.customTidalApks() PatchComponent.Type.Patches -> paths.customSmaliPatches() } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/componentopts/PatchComponent.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/componentopts/PatchComponent.kt index c087813..fe12907 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/componentopts/PatchComponent.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/componentopts/PatchComponent.kt @@ -34,8 +34,8 @@ data class PatchComponent( @Parcelize @Serializable enum class Type : Parcelable { - @SerialName("injector") - Injector, + @SerialName("tidal") + TidalApk, @SerialName("patches") Patches, @@ -47,11 +47,11 @@ data class PatchComponent( */ fun getFile(paths: PathManager): File { val dir = when (type) { - Type.Injector -> paths.customInjectorsDir + Type.TidalApk -> paths.customTidalApksDir Type.Patches -> paths.customPatchesDir } val ext = when (type) { - Type.Injector -> "dex" + Type.TidalApk -> "apk" Type.Patches -> "zip" } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/HomeModel.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/HomeModel.kt index 228baa3..94f55b7 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/HomeModel.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/HomeModel.kt @@ -19,6 +19,7 @@ import cafe.adriel.voyager.core.model.screenModelScope import com.github.diamondminer88.zip.ZipReader import com.meowarex.rlmobile.BuildConfig import com.meowarex.rlmobile.R +import com.meowarex.rlmobile.manager.PreferencesManager import com.meowarex.rlmobile.network.models.GithubCommit import com.meowarex.rlmobile.network.models.RLBuildInfo import com.meowarex.rlmobile.network.services.RadiantLyricsGithubService @@ -28,6 +29,7 @@ import com.meowarex.rlmobile.patcher.InstallMetadata import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen import com.meowarex.rlmobile.ui.util.TidalVersion +import com.meowarex.rlmobile.ui.widgets.managerupdate.VersionDelta import com.meowarex.rlmobile.util.* import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex @@ -39,11 +41,15 @@ class HomeModel( private val application: Application, private val github: RadiantLyricsGithubService, private val json: Json, + private val prefs: PreferencesManager, ) : ScreenModel { var state by mutableStateOf(HomeState.Loading) private set + var managerUpdateDeltas by mutableStateOf?>(null) + private set + val commits = Pager(PagingConfig(pageSize = 30)) { CommitsPagingSource(github) }.flow.cachedIn(screenModelScope) @@ -51,10 +57,103 @@ class HomeModel( private val refreshingLock = Mutex() private var remoteDataJson: RLBuildInfo? = null + private val initialPrefManagerVersion: String = prefs.lastSeenManagerVersion + private val initialPrefPatchesVersion: String = prefs.lastSeenPatchesVersion + private val initialPrefTidalVersionCode: Int = prefs.lastSeenTidalVersionCode + + private var managerUpdateChecked = false + init { refresh() } + fun dismissManagerUpdate() { + managerUpdateDeltas = null + commitVersionPrefs() + } + + private fun commitVersionPrefs() { + prefs.lastSeenManagerVersion = BuildConfig.VERSION_NAME + remoteDataJson?.let { + prefs.lastSeenPatchesVersion = it.patchesVersion.toString() + prefs.lastSeenTidalVersionCode = it.tidalVersionCode + } + } + + private fun maybeCheckManagerUpdate(installedPkg: PackageInfo?) { + if (managerUpdateChecked) return + managerUpdateChecked = true + + val installMetadata = installedPkg?.packageName?.let(::loadInstallMetadata) + val current = BuildConfig.VERSION_NAME + + val previousManager = initialPrefManagerVersion.ifEmpty { + installMetadata?.managerVersion?.toString().orEmpty() + } + + when { + previousManager.isEmpty() -> commitVersionPrefs() + previousManager == current -> commitVersionPrefs() + else -> managerUpdateDeltas = + buildDeltas(previousManager, current, installMetadata, installedPkg) + } + } + + private fun buildDeltas( + previousManager: String, + currentManager: String, + installMetadata: InstallMetadata?, + installedPkg: PackageInfo?, + ): List = buildList { + add( + VersionDelta( + label = application.getString(R.string.manager_update_row_manager), + iconRes = R.drawable.ic_sparkle, + from = previousManager, + to = currentManager, + tag = application.getString(R.string.manager_update_tag_complete), + ) + ) + + val remote = remoteDataJson + val currentPatches = remote?.patchesVersion?.toString() + val previousPatches = initialPrefPatchesVersion.ifEmpty { + installMetadata?.patchesVersion?.toString().orEmpty() + } + val patchesFrom = previousPatches.takeIf { it.isNotEmpty() } + val patchesTo = currentPatches ?: previousPatches.ifEmpty { "?" } + add( + VersionDelta( + label = application.getString(R.string.manager_update_row_patches), + iconRes = R.drawable.ic_extension, + from = patchesFrom, + to = patchesTo, + tag = if (patchesFrom != null && patchesFrom != patchesTo) + application.getString(R.string.manager_update_tag_available) else null, + ) + ) + + val currentTidal = remote?.tidalVersionCode + @Suppress("DEPRECATION") + val installedTidalVersionCode = installedPkg?.versionCode ?: -1 + val previousTidal = if (initialPrefTidalVersionCode > 0) initialPrefTidalVersionCode + else installedTidalVersionCode + val tidalFrom = previousTidal.takeIf { it > 0 }?.toString() + val tidalTo = currentTidal?.toString() + ?: previousTidal.takeIf { it > 0 }?.toString() + ?: "?" + add( + VersionDelta( + label = application.getString(R.string.manager_update_row_tidal), + iconRes = R.drawable.ic_music_note, + from = tidalFrom, + to = tidalTo, + tag = if (tidalFrom != null && tidalFrom != tidalTo) + application.getString(R.string.manager_update_tag_available) else null, + ) + ) + } + fun refresh(delay: Boolean = false) = screenModelScope.launchIO { if (refreshingLock.isLocked) return@launchIO if (delay) { @@ -75,6 +174,7 @@ class HomeModel( install = install, latestTidalVersionCode = latest, ) + maybeCheckManagerUpdate(pkg) } } } @@ -89,7 +189,7 @@ class HomeModel( openAppInfo(current.packageName) } - fun createReinstallScreen(): PatchOptionsScreen? { + fun createRepatchScreen(): PatchOptionsScreen? { val current = (state as? HomeState.Loaded)?.install ?: return null return createPrefilledPatchOptsScreen(current.packageName) } @@ -112,20 +212,21 @@ class HomeModel( } fun createPrefilledPatchOptsScreen(packageName: String): PatchOptionsScreen { - val metadata = try { - val applicationInfo = application.packageManager.getApplicationInfo(packageName, 0) - val metadataFile = ZipReader(applicationInfo.publicSourceDir) - .use { it.openEntry("rlmobile.json")?.read() } - metadataFile?.let { json.decodeFromStream(it.inputStream()) } - } catch (t: Throwable) { - Log.w(BuildConfig.TAG, "Failed to parse install metadata for $packageName", t) - null - } - - val patchOptions = metadata?.options ?: PatchOptions.Default.copy(packageName = packageName) + val patchOptions = loadInstallMetadata(packageName)?.options + ?: PatchOptions.Default.copy(packageName = packageName) return PatchOptionsScreen(prefilledOptions = patchOptions) } + private fun loadInstallMetadata(packageName: String): InstallMetadata? = try { + val applicationInfo = application.packageManager.getApplicationInfo(packageName, 0) + val metadataBytes = ZipReader(applicationInfo.publicSourceDir) + .use { it.openEntry("rlmobile.json")?.read() } + metadataBytes?.let { json.decodeFromStream(it.inputStream()) } + } catch (t: Throwable) { + Log.w(BuildConfig.TAG, "Failed to parse install metadata for $packageName", t) + null + } + private fun fetchInstalled(): PackageInfo? = application.packageManager .getInstalledPackages(PackageManager.GET_META_DATA) .firstOrNull { it.applicationInfo?.metaData?.containsKey("isRadiantLyrics") == true } @@ -138,7 +239,8 @@ class HomeModel( return InstallData( name = pm.getApplicationLabel(info).toString(), packageName = packageName, - isUpToDate = isInstallationUpToDate(this), + tidalUpToDate = isTidalUpToDate(this), + patchesUpToDate = isPatchesUpToDate(this), icon = pm.getApplicationIcon(info).toBitmap().asImageBitmap().let(::BitmapPainter), version = TidalVersion.Existing( type = TidalVersion.parseVersionType(versionCode), @@ -170,11 +272,14 @@ class HomeModel( ) } - private fun isInstallationUpToDate(pkg: PackageInfo): Boolean? { + private fun isTidalUpToDate(pkg: PackageInfo): Boolean? { val remote = remoteDataJson ?: return null @Suppress("DEPRECATION") val versionCode = pkg.versionCode - if (remote.tidalVersionCode != versionCode) return false + return remote.tidalVersionCode == versionCode + } + private fun isPatchesUpToDate(pkg: PackageInfo): Boolean? { + val remote = remoteDataJson ?: return null val apkPath = pkg.applicationInfo?.publicSourceDir ?: return false val installMetadata = try { val mf = ZipReader(apkPath).use { it.openEntry("rlmobile.json")?.read() } ?: return false @@ -182,8 +287,6 @@ class HomeModel( } catch (t: Throwable) { return false } - - if (installMetadata.options.customPatches != null) return true return remote.patchesVersion == installMetadata.patchesVersion } } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/HomeScreen.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/HomeScreen.kt index d565ab1..74448ed 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/HomeScreen.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/HomeScreen.kt @@ -34,6 +34,7 @@ import com.meowarex.rlmobile.ui.screens.home.components.CommitList import com.meowarex.rlmobile.ui.screens.logs.LogsListScreen import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen import com.meowarex.rlmobile.ui.screens.settings.SettingsScreen +import com.meowarex.rlmobile.ui.widgets.managerupdate.ManagerUpdateDialog import com.meowarex.rlmobile.util.* import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @@ -106,9 +107,9 @@ class HomeScreen : Screen, Parcelable { state = state, commits = model.commits, onInstall = { navigator.pushOnce(PatchOptionsScreen()) }, - onReinstall = { + onRepatch = { scope.launchIO { - val screen = model.createReinstallScreen() ?: return@launchIO + val screen = model.createRepatchScreen() ?: return@launchIO mainThread { navigator.push(screen) } } }, @@ -116,6 +117,13 @@ class HomeScreen : Screen, Parcelable { onInfo = model::openCurrentAppInfo, ) } + + model.managerUpdateDeltas?.let { deltas -> + ManagerUpdateDialog( + deltas = deltas, + onDismiss = model::dismissManagerUpdate, + ) + } } } } @@ -126,12 +134,13 @@ private fun ColumnScope.HomeContent( state: HomeState.Loaded, commits: kotlinx.coroutines.flow.Flow>, onInstall: () -> Unit, - onReinstall: () -> Unit, + onRepatch: () -> Unit, onLaunch: () -> Unit, onInfo: () -> Unit, ) { val install = state.install - val currentVersionName = install?.version?.let { "v${it.toString()}" } + val currentVersionName = (install?.version as? com.meowarex.rlmobile.ui.util.TidalVersion.Existing) + ?.let { "v${it.name} (build ${it.code})" } val latestVersionName = state.latestTidalVersionCode?.let { "build $it" } val fallbackPainter = if (install?.icon == null) { @@ -181,8 +190,17 @@ private fun ColumnScope.HomeContent( } } + val patchesBehind = install != null && install.patchesUpToDate == false + val tidalBehind = install != null && install.tidalUpToDate == false + AnimatedVisibility(visible = patchesBehind || tidalBehind) { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + if (patchesBehind) UpdateTag(text = "New Patches!") + if (tidalBehind) UpdateTag(text = "TIDAL Update!") + } + } + Button( - onClick = if (install == null) onInstall else onReinstall, + onClick = if (install == null) onInstall else onRepatch, enabled = state.latestTidalVersionCode != null, modifier = Modifier.fillMaxWidth(), ) { @@ -190,7 +208,7 @@ private fun ColumnScope.HomeContent( state.latestTidalVersionCode == null -> "Loading…" install == null -> "Install" install.isUpToDate == false -> "Update" - else -> "Reinstall" + else -> "Repatch" } Text( text = label, @@ -222,5 +240,20 @@ private fun ColumnScope.HomeContent( ElevatedCard(modifier = Modifier.fillMaxSize()) { CommitList(commits = commits.collectAsLazyPagingItems()) + '} +} + +@Composable +private fun UpdateTag(text: String) { + Surface( + color = MaterialTheme.colorScheme.primaryContainer, + shape = RoundedCornerShape(6.dp), + ) { + Text( + text = text, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp), + ) } } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/InstallData.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/InstallData.kt index 89a7c62..a053414 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/InstallData.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/home/InstallData.kt @@ -10,5 +10,12 @@ data class InstallData( val packageName: String, val version: TidalVersion, val icon: BitmapPainter, - val isUpToDate: Boolean?, -) + val tidalUpToDate: Boolean?, + val patchesUpToDate: Boolean?, +) { + val isUpToDate: Boolean? + get() = when { + tidalUpToDate == null || patchesUpToDate == null -> null + else -> tidalUpToDate && patchesUpToDate + } +} diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptions.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptions.kt index f57a81a..168e2e1 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptions.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptions.kt @@ -25,10 +25,7 @@ data class PatchOptions( */ val debuggable: Boolean, - /** - * A custom build of injector that was used rather than the latest. - */ - val customInjector: PatchComponent? = null, + val customTidalApk: PatchComponent? = null, /** * A custom smali patches bundle that was used rather than the latest. @@ -42,9 +39,9 @@ data class PatchOptions( appName = "TIDAL", packageName = "com.aspiro.tidal", debuggable = false, - customInjector = null, + customTidalApk = null, customPatches = null, - disabledPatches = emptySet(), + disabledPatches = (KnownPatch.DebugMenuUnlock.fileNames + KnownPatch.EnableLegacyUi.fileNames).toSet(), ) } } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptionsModel.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptionsModel.kt index 49ebf76..87c6346 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptionsModel.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptionsModel.kt @@ -81,16 +81,16 @@ class PatchOptionsModel( val enabledPatchCount: Int get() = KnownPatch.All.count { isPatchEnabled(it) } - var customInjector by mutableStateOf(null) + var customTidalApk by mutableStateOf(null) private set var customPatches by mutableStateOf(null) private set - fun selectCustomInjector(navigator: Navigator) = screenModelScope.launch { - customInjector = navigator.pushForResult( + fun selectCustomTidalApk(navigator: Navigator) = screenModelScope.launch { + customTidalApk = navigator.pushForResult( ComponentOptionsScreen( - default = customInjector, - componentType = PatchComponent.Type.Injector, + default = customTidalApk, + componentType = PatchComponent.Type.TidalApk, ) ) } @@ -120,7 +120,7 @@ class PatchOptionsModel( appName = appName, packageName = packageName, debuggable = debuggable, - customInjector = customInjector, + customTidalApk = customTidalApk, customPatches = customPatches, disabledPatches = disabledPatches, ) diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptionsScreen.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptionsScreen.kt index 489f520..b437bf3 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptionsScreen.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/PatchOptionsScreen.kt @@ -55,9 +55,9 @@ class PatchOptionsScreen( packageNameState = model.packageNameState, setPackageName = model::changePackageName, - customInjector = model.customInjector, + customTidalApk = model.customTidalApk, customPatches = model.customPatches, - onSelectCustomInjector = { model.selectCustomInjector(navigator) }, + onSelectCustomTidalApk = { model.selectCustomTidalApk(navigator) }, onSelectCustomPatches = { model.selectCustomPatches(navigator) }, enabledPatchCount = model.enabledPatchCount, @@ -88,8 +88,8 @@ fun PatchOptionsScreenContent( packageNameState: PackageNameState, setPackageName: (String) -> Unit, - customInjector: PatchComponent?, - onSelectCustomInjector: () -> Unit, + customTidalApk: PatchComponent?, + onSelectCustomTidalApk: () -> Unit, customPatches: PatchComponent?, onSelectCustomPatches: () -> Unit, @@ -180,14 +180,14 @@ fun PatchOptionsScreenContent( ) IconPatchOption( - icon = painterResource(R.drawable.ic_extension), - name = stringResource(R.string.patchopts_custom_injector_title), - description = stringResource(R.string.patchopts_custom_injector_desc), - modifier = Modifier.clickable(onClick = onSelectCustomInjector), + icon = painterResource(R.drawable.ic_music_note), + name = stringResource(R.string.patchopts_custom_tidal_apk_title), + description = stringResource(R.string.patchopts_custom_tidal_apk_desc), + modifier = Modifier.clickable(onClick = onSelectCustomTidalApk), ) { - FilledTonalButton(onClick = onSelectCustomInjector) { + FilledTonalButton(onClick = onSelectCustomTidalApk) { Text( - text = customInjector?.version?.toString() + text = customTidalApk?.version?.toString() ?: stringResource(R.string.componentopts_selected_none) ) } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/widgets/managerupdate/ManagerUpdateDialog.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/widgets/managerupdate/ManagerUpdateDialog.kt new file mode 100644 index 0000000..6ce8329 --- /dev/null +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/widgets/managerupdate/ManagerUpdateDialog.kt @@ -0,0 +1,140 @@ +package com.meowarex.rlmobile.ui.widgets.managerupdate + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties +import com.meowarex.rlmobile.R +import com.meowarex.rlmobile.ui.components.Tag + +data class VersionDelta( + val label: String, + @DrawableRes val iconRes: Int, + val from: String?, + val to: String, + val tag: String? = null, +) + +@Composable +fun ManagerUpdateDialog( + deltas: List, + onDismiss: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismiss, + confirmButton = { + FilledTonalButton( + onClick = onDismiss, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), + ) { + Text(stringResource(R.string.action_continue)) + } + }, + title = { + Text( + text = stringResource(R.string.manager_update_title), + textAlign = TextAlign.Center, + ) + }, + text = { + Column( + verticalArrangement = Arrangement.spacedBy(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResource(R.string.manager_update_subtitle), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium, + ) + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxWidth(), + ) { + deltas.forEach { delta -> + DeltaCard(delta = delta) + } + } + } + }, + icon = { + Icon( + painter = painterResource(R.drawable.ic_update), + contentDescription = null, + modifier = Modifier.size(36.dp), + ) + }, + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = false, + ), + ) +} + +@Composable +private fun DeltaCard(delta: VersionDelta) { + val changed = delta.from != null && delta.from != delta.to + val subtitle = when { + delta.from == null || delta.from == delta.to -> delta.to + else -> "${delta.from} → ${delta.to}" + } + + Surface( + color = MaterialTheme.colorScheme.surfaceContainerHigh, + shape = RoundedCornerShape(16.dp), + modifier = Modifier.fillMaxWidth(), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(14.dp), + modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp), + ) { + DeltaIcon(delta) + Column(modifier = Modifier.weight(1f)) { + Text( + text = delta.label, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + ) + Text( + text = subtitle, + style = MaterialTheme.typography.bodyMedium, + color = if (changed) MaterialTheme.colorScheme.primary + else LocalContentColor.current.copy(alpha = 0.65f), + fontWeight = if (changed) FontWeight.SemiBold else FontWeight.Normal, + ) + } + delta.tag?.let { Tag(text = it) } + } + } +} + +@Composable +private fun DeltaIcon(delta: VersionDelta) { + Surface( + shape = CircleShape, + color = MaterialTheme.colorScheme.secondaryContainer, + modifier = Modifier.size(40.dp), + ) { + Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { + Icon( + painter = painterResource(delta.iconRes), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier.size(22.dp), + ) + } + } +} diff --git a/Manager/app/src/main/res/drawable/ic_music_note.xml b/Manager/app/src/main/res/drawable/ic_music_note.xml new file mode 100644 index 0000000..5ed1169 --- /dev/null +++ b/Manager/app/src/main/res/drawable/ic_music_note.xml @@ -0,0 +1,9 @@ + + + diff --git a/Manager/app/src/main/res/drawable/ic_sparkle.xml b/Manager/app/src/main/res/drawable/ic_sparkle.xml new file mode 100644 index 0000000..51c3df9 --- /dev/null +++ b/Manager/app/src/main/res/drawable/ic_sparkle.xml @@ -0,0 +1,9 @@ + + + diff --git a/Manager/app/src/main/res/values/strings.xml b/Manager/app/src/main/res/values/strings.xml index a7efbf1..b0b6d19 100644 --- a/Manager/app/src/main/res/values/strings.xml +++ b/Manager/app/src/main/res/values/strings.xml @@ -43,10 +43,18 @@ Open Info Reset to default - Failed to automatically reinstall! Please try doing it manually. + Failed to automatically repatch! Please try doing it manually. Successfully imported %s Failed to import custom component! + Update Complete + Manager was successfully updated! + Manager + Patches + TIDAL + Complete + Available + Grant Permissions Radiant Lyrics Manager requires permissions: %s indicates required permissions! @@ -235,10 +243,10 @@ >The app name is what\'s displayed in your home launcher. This should be changed on secondary installations for ease of use. Debuggable Enable the debuggable manifest flag. Only use this if you know what you are doing! - Custom Injector - A custom injector build that was imported by Manager. + Custom TIDAL APK + Provide your own Stock TIDAL APK to patch instead of the version from Github. Custom Patches - A custom smali patch bundle that was imported by Manager. + Provide your own Patches Zip to use instead of the ones from Github. Basic Advanced Patches diff --git a/patches/data.json b/patches/data.json index 7bcc858..bee144d 100644 --- a/patches/data.json +++ b/patches/data.json @@ -1,5 +1,5 @@ { "tidalVersionCode": 9089, "tidalApkUrl": "https://github.com/meowarex/rl-mobile/releases/download/latest/tidal-stock.apk", - "patchesVersion": "0.5.0" + "patchesVersion": "0.6.0" }