From 691bcaa2b89b4a8284791d2dd4301a8a7dd6b08f Mon Sep 17 00:00:00 2001 From: meowarex Date: Sun, 24 May 2026 01:20:42 +1000 Subject: [PATCH] Manager UI Updates & New patch logic <3 --- Manager/.github/ISSUE_TEMPLATE/config.yml | 6 +- Manager/app/build.gradle.kts | 2 +- .../meowarex/rlmobile/ManagerApplication.kt | 2 - .../kotlin/com/meowarex/rlmobile/di/Http.kt | 4 +- .../installers/dhizuku/DhizukuInstaller.kt | 3 +- .../installers/pm/PMResultReceiver.kt | 2 - .../rlmobile/installers/root/RootInstaller.kt | 2 +- .../installers/shizuku/ShizukuInstaller.kt | 2 +- .../steps/download/CopyDependenciesStep.kt | 2 +- .../patcher/steps/patch/SmaliPatchStep.kt | 4 +- .../rlmobile/patcher/util/ManifestPatcher.kt | 53 +++++++++-- .../screens/patching/PatchingScreenModel.kt | 7 +- .../ui/screens/patchopts/KnownPatch.kt | 55 ++++++++--- .../ui/screens/patchopts/PatchOptionsModel.kt | 46 ++++----- .../components/PatchSelectionAccordion.kt | 1 + .../settings/components/InstallersDialog.kt | 2 +- .../ui/widgets/updater/UpdaterViewModel.kt | 2 +- .../res/drawable/ic_launcher_foreground.xml | 1 + Manager/app/src/main/res/values/strings.xml | 95 +++++++++++++------ Manager/gradlew | 5 +- patches/lyrics-active-line-only.patch | 30 ------ patches/lyrics-progress-pill.patch | 66 +++++++------ ...lyrics-replace-lyric-button-1-remove.patch | 16 ---- ...yrics-replace-lyric-button-2-sparkle.patch | 26 ----- 24 files changed, 229 insertions(+), 205 deletions(-) delete mode 100644 patches/lyrics-active-line-only.patch delete mode 100644 patches/lyrics-replace-lyric-button-1-remove.patch delete mode 100644 patches/lyrics-replace-lyric-button-2-sparkle.patch diff --git a/Manager/.github/ISSUE_TEMPLATE/config.yml b/Manager/.github/ISSUE_TEMPLATE/config.yml index 2bccfc3..ae45795 100644 --- a/Manager/.github/ISSUE_TEMPLATE/config.yml +++ b/Manager/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - - name: Radiant Lyrics Support Server - url: https://tidal.gg/EsNDvBaHVU - about: If you need help regarding Radiant Lyrics or plugins for Radiant Lyrics, please join our support server. + - name: Tidaluna Discord + url: https://discord.gg/yxZWK5AeZw + about: If you need help regarding Radiant Lyrics or patches for Radiant Lyrics, please join the discord and make a post in the help forum. diff --git a/Manager/app/build.gradle.kts b/Manager/app/build.gradle.kts index e7ccb05..c34fa15 100644 --- a/Manager/app/build.gradle.kts +++ b/Manager/app/build.gradle.kts @@ -36,7 +36,7 @@ android { } buildConfigField("String", "TAG", "\"RLManager\"") - buildConfigField("String", "SUPPORT_SERVER", "\"\"") // no support server yet + buildConfigField("String", "SUPPORT_SERVER", "\"\"") buildConfigField("String", "PATCHES_REPO_OWNER", "\"meowarex\"") buildConfigField("String", "PATCHES_REPO_NAME", "\"rl-mobile\"") diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ManagerApplication.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ManagerApplication.kt index a190155..b37863e 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ManagerApplication.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ManagerApplication.kt @@ -102,8 +102,6 @@ class ManagerApplication : Application() { .build() } - // Schedule periodic update check only when the user has opted in, - // so the disabled state survives app restarts instead of being re-enqueued. if (get().autoUpdateCheck) { UpdateCheckWorker.schedule(this) } else { diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/di/Http.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/di/Http.kt index 8abce47..e76cd83 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/di/Http.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/di/Http.kt @@ -47,8 +47,6 @@ fun Scope.provideHttpClient() = HttpClient(OkHttp) { override fun lookup(hostname: String): List { val addresses = Dns.SYSTEM.lookup(hostname) - // Github's nameservers do not respond to IPv6 requests for raw.githubusercontent.com, - // which causes CIO, Android and OkHTTP to all hang return if (hostname == "raw.githubusercontent.com") { addresses.filterIsInstance() } else { @@ -77,7 +75,7 @@ fun Scope.provideHttpClient() = HttpClient(OkHttp) { // Default storage is in-memory } - // Custom plugin to allow overriding response cache headers, and force caching + // Custom plugin to allow overriding response cache headers (force caching) install("OverrideCacheControl") { receivePipeline.intercept(HttpReceivePipeline.Before) { response -> val customCacheControl = response.call.attributes.getOrNull(CustomCacheControl) diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/dhizuku/DhizukuInstaller.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/dhizuku/DhizukuInstaller.kt index 028601d..4875c20 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/dhizuku/DhizukuInstaller.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/dhizuku/DhizukuInstaller.kt @@ -107,8 +107,7 @@ class DhizukuInstaller( ) } - // Unregister PMResultReceiver when this coroutine finishes or errors - // Explicitly cancel the install session if it did not finish. + // cancel the install session if it did not finish. continuation.invokeOnCancellation { context.unregisterReceiver(relayReceiver) sessionCallback?.let { packageInstaller.unregisterSessionCallback(it) } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/pm/PMResultReceiver.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/pm/PMResultReceiver.kt index 0b0db7c..dcdbb3c 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/pm/PMResultReceiver.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/pm/PMResultReceiver.kt @@ -39,8 +39,6 @@ class PMResultReceiver( context.showToast(if (!isUninstall) R.string.installer_install_success else R.string.installer_uninstall_success) } - // The reason we don't do this in PMIntentReceiver is we can't tell whether it was - // an old session that for which `abandonSession(...)` was called is InstallerResult.Cancelled -> { context.showToast(if (!isUninstall) R.string.installer_install_aborted else R.string.installer_uninstall_aborted) } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/root/RootInstaller.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/root/RootInstaller.kt index 9d15496..1e9dee2 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/root/RootInstaller.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/root/RootInstaller.kt @@ -116,7 +116,7 @@ class RootInstaller(private val context: Context) : Installer { } private companion object { - // We spoof Google Play Store to prevent unnecessary checks + // spoof Google Play Store to prevent unnecessary checks const val PLAY_PACKAGE_NAME = "com.android.vending" /** diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/shizuku/ShizukuInstaller.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/shizuku/ShizukuInstaller.kt index af9ca8b..509760b 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/shizuku/ShizukuInstaller.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/installers/shizuku/ShizukuInstaller.kt @@ -18,7 +18,7 @@ import kotlin.coroutines.resume /** * The package name of Google Play Store. - * We spoof our installer to this when installing through Shizuku to prevent + * spoof our installer to this when installing through Shizuku to prevent * potentially unnecessary scans/checks. */ private const val PLAY_PACKAGE_NAME = "com.android.vending" diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/download/CopyDependenciesStep.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/download/CopyDependenciesStep.kt index 87ed772..b70c218 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/download/CopyDependenciesStep.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/download/CopyDependenciesStep.kt @@ -43,7 +43,7 @@ class CopyDependenciesStep : Step(), KoinComponent { val targetFileStorageId = storageManager.getUuidForPath(apk) val fileSize = srcApk.length() - // We request 3.5x the size of the APK, to give space for the following: + // request 3.5x the size of the APK, to give space for the following: // 1) A copy of the APK // 2) Modifying the copied APK (whether this is necessary I'm not sure) // 2) Extracting native libs and other various operations diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/SmaliPatchStep.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/SmaliPatchStep.kt index e59c200..80a94fe 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/SmaliPatchStep.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/SmaliPatchStep.kt @@ -272,7 +272,7 @@ class SmaliPatchStep( val matchPos = findContextMatch(result, sourceLines, delta.source.position) ?: throw Exception( "Fuzzy match failed: could not locate context for hunk near line " + - "${delta.source.position + 1} (${sourceLines.size} lines)" + "${delta.source.position + 1} (${sourceLines.size} lines)" ) repeat(sourceLines.size) { result.removeAt(matchPos) } @@ -288,7 +288,7 @@ class SmaliPatchStep( // Try at the exact hint first. if (matchesAt(target, sourceLines, hint)) return hint - // Walk outward from the hint, alternating below/above, until we find a unique match. + // Walk outward from the hint, alternating below/above, until finds a unique match. val maxRadius = target.size for (offset in 1..maxRadius) { val below = hint + offset diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt index c725c72..bc53f2f 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt @@ -25,7 +25,7 @@ object ManifestPatcher { appName: String, debuggable: Boolean, ): ByteArray { - // Extract original package name so we can rewrite every reference to it + // Extract original package name to rewrite every reference to it // (permissions, provider authorities) to the new packageName. var originalPackage: String? = null AxmlReader(manifestBytes).accept(object : AxmlVisitor() { @@ -58,11 +58,12 @@ object ManifestPatcher { COMPILE_SDK_VERSION_CODENAME to "6.0-2438415" ) ) { - // Drop split-only manifest attributes — we merged all splits into one APK + // Drop split-only manifest attributes because merged all splits into one APK override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { if (name == IS_SPLIT_REQUIRED || name == REQUIRED_SPLIT_TYPES || name == SPLIT_TYPES) return super.attr(ns, name, resourceId, type, value) } + private var addExternalStoragePerm = false override fun child(ns: String?, name: String): NodeVisitor { @@ -72,7 +73,13 @@ object ManifestPatcher { if (addExternalStoragePerm) { super .child(null, "uses-permission") - .attr(ANDROID_NAMESPACE, "name", android.R.attr.name, TYPE_STRING, Manifest.permission.MANAGE_EXTERNAL_STORAGE) + .attr( + ANDROID_NAMESPACE, + "name", + android.R.attr.name, + TYPE_STRING, + Manifest.permission.MANAGE_EXTERNAL_STORAGE + ) addExternalStoragePerm = false } @@ -141,7 +148,13 @@ object ManifestPatcher { if (addMetadata) { addMetadata = false super.child(ANDROID_NAMESPACE, "meta-data").apply { - attr(ANDROID_NAMESPACE, "name", android.R.attr.name, TYPE_STRING, "isRadiantLyrics") + attr( + ANDROID_NAMESPACE, + "name", + android.R.attr.name, + TYPE_STRING, + "isRadiantLyrics" + ) attr(ANDROID_NAMESPACE, "value", android.R.attr.value, TYPE_INT_BOOLEAN, 1) } } @@ -149,7 +162,13 @@ object ManifestPatcher { return when (name) { "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName)) "provider" -> object : NodeVisitor(visitor) { - override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { + override fun attr( + ns: String?, + name: String, + resourceId: Int, + type: Int, + value: Any? + ) { super.attr( ns, name, resourceId, type, if (name == "authorities") { @@ -173,11 +192,23 @@ object ManifestPatcher { TYPE_INT_BOOLEAN, 1 ) - if (addDebuggable) super.attr(ANDROID_NAMESPACE, DEBUGGABLE, android.R.attr.debuggable, TYPE_INT_BOOLEAN, 1) + if (addDebuggable) super.attr( + ANDROID_NAMESPACE, + DEBUGGABLE, + android.R.attr.debuggable, + TYPE_INT_BOOLEAN, + 1 + ) // Disable AOT (Necessary for AOSP Android 15) if (Build.VERSION.SDK_INT >= 29 && addUseEmbeddedDex) { - super.attr(ANDROID_NAMESPACE, USE_EMBEDDED_DEX, android.R.attr.useEmbeddedDex, TYPE_INT_BOOLEAN, 1) + super.attr( + ANDROID_NAMESPACE, + USE_EMBEDDED_DEX, + android.R.attr.useEmbeddedDex, + TYPE_INT_BOOLEAN, + 1 + ) } if (addExtractNativeLibs) super.attr( @@ -209,7 +240,13 @@ object ManifestPatcher { val replace = attrs.containsKey(name) val newValue = attrs[name] - super.attr(ns, name, resourceId, if (newValue is String) TYPE_STRING else type, if (replace) newValue else value) + super.attr( + ns, + name, + resourceId, + if (newValue is String) TYPE_STRING else type, + if (replace) newValue else value + ) } } } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patching/PatchingScreenModel.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patching/PatchingScreenModel.kt index 905cdd3..761d955 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patching/PatchingScreenModel.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patching/PatchingScreenModel.kt @@ -150,15 +150,13 @@ class PatchingScreenModel( .toUnsafeImmutable() mainThread { steps = newSteps } - // Intentionally delay to show the state change of the first step when it runs in the UI. - // Without this, on a fast internet connection the step just immediately shows as "Success". + // Tiny delay so the first step's progress is visible on fast connections delay(400) // Execute all the steps and catch any errors val error = when (val error = runner.executeAll()) { null -> { - // If install step is marked skipped then the installation was manually aborted - // and if so, immediately close install screen + // Skipped install step means user aborted, close screen if (runner.getStep().state == StepState.Skipped) { mutableState.value = PatchingScreenState.CloseScreen @@ -215,6 +213,7 @@ class PatchingScreenModel( R.string.fun_fact_10, R.string.fun_fact_11, R.string.fun_fact_12, + R.string.fun_fact_13, ) } } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt index df972f6..7fa7808 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt @@ -8,37 +8,64 @@ enum class KnownPatch( @StringRes val titleRes: Int, @StringRes val descRes: Int, val requires: List = emptyList(), + val disables: List = emptyList(), ) { + // Dependency-first order (later refs need backward resolution) LyricsDisableCover( fileNames = listOf("lyrics-disable-cover.patch"), titleRes = R.string.patch_lyrics_disable_cover_title, descRes = R.string.patch_lyrics_disable_cover_desc, ), - LyricsProgressPill( + LyricsReplaceLyricsButton( fileNames = listOf( - "lyrics-progress-pill.patch", - "lyrics-fade-region.patch", - "lyrics-active-line-only.patch", - ), - titleRes = R.string.patch_lyrics_progress_pill_title, - descRes = R.string.patch_lyrics_progress_pill_desc, - requires = listOf(LyricsDisableCover), - ), - LyricsReplaceLyricButton( - fileNames = listOf( - "lyrics-replace-lyric-button-1-remove.patch", - "lyrics-replace-lyric-button-2-sparkle.patch", + "lyrics-replace-lyrics-button.patch", + "lyrics-sparkle-conditional-visibility.patch", ), titleRes = R.string.patch_lyrics_replace_button_title, descRes = R.string.patch_lyrics_replace_button_desc, ), + LyricsReplaceShareButton( + fileNames = listOf("lyrics-replace-share-button.patch"), + titleRes = R.string.patch_lyrics_replace_share_button_title, + descRes = R.string.patch_lyrics_replace_share_button_desc, + ), PlayerBackdrop( fileNames = listOf("player-backdrop.patch"), titleRes = R.string.patch_player_backdrop_title, descRes = R.string.patch_player_backdrop_desc, + ), + DebugMenuUnlock( + fileNames = listOf("debug-menu-unlock.patch"), + titleRes = R.string.patch_debug_menu_unlock_title, + descRes = R.string.patch_debug_menu_unlock_desc, + ), + LyricsProgressPill( + fileNames = listOf( + "lyrics-progress-pill.patch", + "lyrics-fade-region.patch", + ), + titleRes = R.string.patch_lyrics_progress_pill_title, + descRes = R.string.patch_lyrics_progress_pill_desc, + requires = listOf(LyricsDisableCover, LyricsReplaceShareButton), + ), + EnableLegacyUi( + fileNames = listOf("enable-legacy-ui.patch"), + titleRes = R.string.patch_enable_legacy_ui_title, + descRes = R.string.patch_enable_legacy_ui_desc, + requires = listOf(DebugMenuUnlock), + disables = listOf( + LyricsDisableCover, + LyricsReplaceLyricsButton, + LyricsReplaceShareButton, + PlayerBackdrop, + LyricsProgressPill, + ), ); companion object { - val All: List = entries.sortedBy { it.fileNames.first() } + // Alphabetical by first filename, but pin DebugMenuUnlock to the bottom + val All: List = entries.sortedWith( + compareBy({ it == DebugMenuUnlock }, { it.fileNames.first() }) + ) } } 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 f561165..49ebf76 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 @@ -18,7 +18,6 @@ class PatchOptionsModel( private val context: Context, private val prefs: PreferencesManager, ) : ScreenModel { - // ---------- Package name state ---------- var packageName by mutableStateOf(prefilledOptions.packageName) private set @@ -30,7 +29,6 @@ class PatchOptionsModel( fetchPkgNameStateDebounced() } - // ---------- App name state ---------- var appName by mutableStateOf(prefilledOptions.appName) private set @@ -42,7 +40,6 @@ class PatchOptionsModel( appNameIsError = newAppName.length !in (1..150) } - // ---------- Debuggable state ---------- var debuggable by mutableStateOf(prefilledOptions.debuggable) private set @@ -50,7 +47,6 @@ class PatchOptionsModel( debuggable = value } - // ---------- Patch selection state ---------- var disabledPatches by mutableStateOf(prefilledOptions.disabledPatches) private set @@ -58,38 +54,33 @@ class PatchOptionsModel( patch.fileNames.none { it in disabledPatches } fun setPatchEnabled(patch: KnownPatch, enabled: Boolean) { - val units = if (enabled) { + fun closure(seed: KnownPatch, step: (KnownPatch) -> List): Set = buildSet { - fun addWithDeps(p: KnownPatch) { - if (add(p)) p.requires.forEach(::addWithDeps) - } - addWithDeps(patch) + fun walk(p: KnownPatch) { if (add(p)) step(p).forEach(::walk) } + walk(seed) } + + val enableUnits: Set + val disableUnits: Set + if (enabled) { + enableUnits = closure(patch) { it.requires } + disableUnits = enableUnits.flatMap { it.disables } + .flatMapTo(mutableSetOf()) { d -> + closure(d) { dep -> KnownPatch.All.filter { dep in it.requires } } + } } else { - buildSet { - fun addWithDependents(p: KnownPatch) { - if (add(p)) { - KnownPatch.All - .filter { p in it.requires } - .forEach(::addWithDependents) - } - } - addWithDependents(patch) - } + enableUnits = emptySet() + disableUnits = closure(patch) { p -> KnownPatch.All.filter { p in it.requires } } } - val affectedFiles = units.flatMap { it.fileNames }.toSet() - disabledPatches = if (enabled) { - disabledPatches - affectedFiles - } else { - disabledPatches + affectedFiles - } + val enableFiles = enableUnits.flatMap { it.fileNames }.toSet() + val disableFiles = disableUnits.flatMap { it.fileNames }.toSet() + disabledPatches = (disabledPatches - enableFiles) + disableFiles } val enabledPatchCount: Int get() = KnownPatch.All.count { isPatchEnabled(it) } - // ---------- Custom components state ---------- var customInjector by mutableStateOf(null) private set var customPatches by mutableStateOf(null) @@ -113,7 +104,6 @@ class PatchOptionsModel( ) } - // ---------- Config generation ---------- val isConfigValid by derivedStateOf { val invalidChecks = arrayOf( packageNameState == PackageNameState.Invalid, @@ -136,11 +126,9 @@ class PatchOptionsModel( ) } - // ---------- Other ---------- val isDevMode: Boolean get() = prefs.devMode - // A throttled variant of fetchPkgNameState() private val fetchPkgNameStateDebounced: () -> Unit = screenModelScope.debounce(100L, function = ::fetchPkgNameState) diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/components/PatchSelectionAccordion.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/components/PatchSelectionAccordion.kt index e0ecba9..724c316 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/components/PatchSelectionAccordion.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/components/PatchSelectionAccordion.kt @@ -1,5 +1,6 @@ package com.meowarex.rlmobile.ui.screens.patchopts.components + import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/settings/components/InstallersDialog.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/settings/components/InstallersDialog.kt index 2e0bd7f..ebb97e7 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/settings/components/InstallersDialog.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/settings/components/InstallersDialog.kt @@ -62,7 +62,7 @@ fun InstallersDialog( } InstallerSetting.Intent -> { - // We don't know whether this device supports this method until we try. + // don't know whether this device supports this method } InstallerSetting.Shizuku -> { diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/widgets/updater/UpdaterViewModel.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/widgets/updater/UpdaterViewModel.kt index 35c7e2c..9b2089f 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/widgets/updater/UpdaterViewModel.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/widgets/updater/UpdaterViewModel.kt @@ -147,7 +147,7 @@ class UpdaterViewModel( // Fetch releases from GitHub (60s local cache) val releases = github.getManagerReleases().getOrThrow() - // Find the latest release — version is parsed from the release title (e.g. "v1.0.5") + // Find the latest release by parsed version val (version, release, apkUrl) = releases .mapNotNull { release -> val version = SemVer.parseOrNull(release.name?.removePrefix("v") ?: "") diff --git a/Manager/app/src/main/res/drawable/ic_launcher_foreground.xml b/Manager/app/src/main/res/drawable/ic_launcher_foreground.xml index b63058a..20aadb8 100644 --- a/Manager/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/Manager/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -21,3 +21,4 @@ android:pathData="M5.209,1.737C5.313,1.473 5.687,1.473 5.791,1.737L6.157,2.667C6.189,2.747 6.253,2.811 6.333,2.843L7.263,3.209C7.527,3.313 7.527,3.687 7.263,3.791L6.333,4.157C6.253,4.189 6.189,4.253 6.157,4.333L5.791,5.263C5.687,5.527 5.313,5.527 5.209,5.263L4.843,4.333C4.811,4.253 4.747,4.189 4.667,4.157L3.737,3.791C3.473,3.687 3.473,3.313 3.737,3.209L4.667,2.843C4.747,2.811 4.811,2.747 4.843,2.667L5.209,1.737Z" /> +aight diff --git a/Manager/app/src/main/res/values/strings.xml b/Manager/app/src/main/res/values/strings.xml index 52b3744..a7efbf1 100644 --- a/Manager/app/src/main/res/values/strings.xml +++ b/Manager/app/src/main/res/values/strings.xml @@ -57,7 +57,9 @@ Install from Unknown Sources Permissions are required to initiate an installation from this app. External Storage - Radiant Lyrics Manager stores shared data in ~/RadiantLyrics, which requires full storage permissions. Scoped storage is not currently supported. + Radiant Lyrics Manager stores shared data in ~/RadiantLyrics, which requires full storage permissions. Scoped storage is not currently supported. Notifications Background Battery Ensures the installation process does not get automatically cancelled if the app is minimized. @@ -83,7 +85,9 @@ Installation Method Various methods are supported to interface with the system\'s package installer. Keep patched APKs - Keep all patched files and APKs after installation for debugging purposes. Note that exporting the APK is only useful after a successful patching session. + Keep all patched files and APKs after installation for debugging purposes. Note that exporting the APK is only useful after a successful patching session. Clear cache Export APK @@ -94,7 +98,9 @@ Launches an intent to handle installation through the system\'s configured default installer. Invokes PackageManager directly using device root such as Magisk or KernelSU. Shizuku - Invokes the PackageInstaller API remotely through Shizuku. ADB, Wireless ADB, and Root backends are all supported. + Invokes the PackageInstaller API remotely through Shizuku. ADB, Wireless ADB, and Root backends are all supported. Dhizuku Invokes the PackageInstaller API remotely through Dhizuku using DeviceOwner permissions. @@ -119,13 +125,15 @@ Settings Refresh - Lead + Developer Contributors %s contributions Failed to load Metered network - It appears you are connected to mobile data or a potentially metered network. Continuing with the installation may result in several hundred megabytes being downloaded. Are you sure you want to continue? + It appears you are connected to mobile data or a potentially metered network. Continuing with the installation may result in several hundred megabytes being downloaded. Are you sure you want to continue? Don\'t show this again None @@ -144,15 +152,21 @@ Cancelled uninstallation Failed to verify download Insufficient storage space! - Since battery optimizations have not been disabled, minimizing this screen may abort the installation process! - Installation failed! You can either retry or click this banner to open the GitHub repository for help. - Successfully installed Radiant Lyrics! Do *NOT* uninstall the manager (this app) as it is required to perform certain types of updates. + Since battery optimizations have not been disabled, minimizing this screen may abort the installation process! + Installation failed! You can either retry or click this banner to open the GitHub repository for help. + Successfully installed Radiant Lyrics! Do *NOT* uninstall the manager (this app) as it is required to perform certain types of updates. Installer failure (Unknown reason) No handlers available for %s! Installation was blocked One or more APKs were invalid or corrupt - Conflicts with an existing app, usually due to mismatched signatures + Conflicts with an existing app (Change Name & Com values) Not enough storage available to install Application is incompatible with this device Installer timed out @@ -162,16 +176,16 @@ Patch APK Install APK - Fetching target TIDAL version + Fetching TIDAL version Checking for newer installations Restoring from cache Downloading TIDAL APK - Downloading smali patches + Downloading Patches Copying dependencies Patching APK manifest Adding modern root certificates Patching app icon - Applying smali patches + Applying Patches Reorganizing dex files Store metadata Aligning APK @@ -189,7 +203,9 @@ Are you sure you really want to abort an in-progress installation? Update to v%s - A new update has been released for Radiant Lyrics Manager! It may be required in order to function properly. Would you like to update? + A new update has been released for Radiant Lyrics Manager! It may be required in order to function properly. Would you like to update? View release on GitHub Failed to check for updates! Failed to update! Please download and install the update manually! @@ -203,14 +219,20 @@ Failed to fetch data! - Radiant Lyrics provides several customizations at installation time that are not able to be changed once installed. + Radiant Lyrics provides several customizations at installation time that are not able to be changed once installed. Package Name - The package name is a unique identifier for all apps. Using different ones can allow for multiple installations of Radiant Lyrics. + The package name is a unique identifier for all apps. Using different ones can allow for multiple installations of Radiant Lyrics. Invalid package name! Target app will be overwritten! Valid package name! App Name - The app name is what\'s displayed in your home launcher. This should be changed on secondary installations for ease of use. + 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 @@ -224,13 +246,25 @@ %1$d of %2$d enabled Disable Lyrics Cover - Prevents the album cover from being hidden when lyrics are showing. + Removes the mini track cover from the lyrics screen. Lyrics Progress Pill - Shows a progress pill while the upcoming lyric line loads in, with a soft fade above and below the lyrics list. + Restores the old Track Progress Pill in the top of the Lyrics screen! Replace Lyrics Button Replaces the Lyrics button with the RL Sparkle! Player Backdrop Restores the legacy translucent backdrop blur behind the player. + Replace Share Button + Replaces the share button with the Tidal Connect button from the top of the screen. (it\'s in the menu already) + Unlock Debug Menu + Reveals TIDAL\'s hidden Debug Menu in Settings. (Unlocks Feature Flags & the legacy Debug Options) + Enable Legacy UI + [This patch will stop working soon] - Replaces the New Compose based UI with the Legacy UI (disables player-market-ui feature flag) Custom Component (%s) Select a custom build that was imported by Manager. @@ -261,24 +295,27 @@ Click to view more info… Play Protect - Google Play Protect appears to be enabled on your device. It may attempt to interfere with a new installation due to the usage of a unique signing key. You can disable it in Play Protect\'s settings.\n\nIf it does show a warning dialog, press\n\"More Details\" -> \"Install anyway\" + Google Play Protect appears to be enabled on your device. It may attempt to interfere with a new installation due to the usage of a unique signing key. You can disable it in Play Protect\'s settings.\n\nIf it does show a warning dialog, press\n\"More Details\" -> \"Install anyway\" Continue Open Play Protect Don\'t show this again Fun Fact: %s - Did you know that Google Play Protect is useless? - Radiant Lyrics restores the blurred album art lyrics dialog from TIDAL\'s legacy player. - The legacy LyricsDialog uses a BlurTransformation with radius 5 via RenderScript. - i am in your walls - The TIDAL app has two coexisting player implementations — old View-based and new Compose. + Did you know that TIDAL no longer like blur! + Radiant Lyrics is also available for DESKTOP!!! + i am in your walls! + TIDAL is Rewriting the UI with Compose + The TIDAL app has a hidden Debug Menu! Having issues? Open an issue on GitHub! - The lyricsButton was hidden because TIDAL rerouted lyrics data to the new Compose player. - \"just patch it lol\" - Radiant Lyrics targets TIDAL v2.192.0 + This installer/manager was forked from Aliucord + I have CATS!! + Radiant Lyrics is made by JUST meoware.exe Smali patches are distributed as unified diffs inside patches.zip - The blurred background uses Coil + BlurTransformation(radius=5) crossfaded onto an ImageView. - meowarex made this + I wrote these facts while under the influence of brain damage.. + i love you! + TIDAL devs if you\'re reading this, please add all this to the app %.2fs diff --git a/Manager/gradlew b/Manager/gradlew index adff685..4386b80 100755 --- a/Manager/gradlew +++ b/Manager/gradlew @@ -223,12 +223,12 @@ fi # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # -# In Bash we could simply go: +# In Bash, could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # -# but POSIX shell has neither arrays nor command substitution, so instead we +# but POSIX shell has neither arrays nor command substitution, so instead # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap @@ -237,6 +237,7 @@ fi # This will of course break if any of these variables contains a newline or # an unmatched quote. # +# This is a not Opus made when fixing smthn <3 eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | diff --git a/patches/lyrics-active-line-only.patch b/patches/lyrics-active-line-only.patch deleted file mode 100644 index de22c9d..0000000 --- a/patches/lyrics-active-line-only.patch +++ /dev/null @@ -1,30 +0,0 @@ ---- a/com/tidal/android/feature/playerscreen/ui/composables/LyricsKt.smali -+++ b/com/tidal/android/feature/playerscreen/ui/composables/LyricsKt.smali -@@ -110,16 +110,18 @@ - .line 36 - move-result-wide v6 - -- .line 37 -- sget-object v5, Lcom/tidal/android/feature/playerscreen/ui/composables/LyricsKt;->b:[F -+ add-int/lit8 v8, v3, -0xa # i - 10 (10 = active line index) - -- .line 38 -- .line 39 -- rsub-int/lit8 v8, v3, 0xa -- -- .line 40 -- .line 41 -- aget v8, v5, v8 -+ if-nez v8, :radiant_alpha_zero # not active line -> hide -+ -+ const/high16 v8, 0x3f800000 # alpha 1.0 (visible) -+ -+ goto :radiant_alpha_done -+ -+ :radiant_alpha_zero -+ const/4 v8, 0x0 # alpha 0.0 (hidden) -+ -+ :radiant_alpha_done - - .line 42 - .line 43 diff --git a/patches/lyrics-progress-pill.patch b/patches/lyrics-progress-pill.patch index e99db13..8d6431f 100644 --- a/patches/lyrics-progress-pill.patch +++ b/patches/lyrics-progress-pill.patch @@ -1,63 +1,75 @@ --- a/com/tidal/android/feature/playerscreen/ui/b0.smali +++ b/com/tidal/android/feature/playerscreen/ui/b0.smali @@ -45,7 +45,7 @@ - + # virtual methods .method public final invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - .locals 17 -+ .locals 26 # extra regs for the pill AndroidView call - ++ .locals 26 # extra regs for AndroidView + .line 1 move-object/from16 v0, p0 -@@ -460,6 +460,52 @@ +@@ -460,6 +460,64 @@ .line 200 invoke-static/range {v2 .. v9}, Lcom/tidal/android/feature/playerscreen/ui/composables/LyricsKt;->a(Lcom/tidal/android/feature/playerscreen/ui/g;Ltl0/l;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/PaddingValues;Ltl0/l;Landroidx/compose/runtime/Composer;II)V - -+ sget-object v17, Lradiant/SpvFactory;->a:Lradiant/SpvFactory; # factory that builds the legacy progress view + ++ sget-object v17, Lradiant/SpvFactory;->a:Lradiant/SpvFactory; # progress view factory + -+ sget-object v18, Landroidx/compose/ui/Modifier;->Companion:Landroidx/compose/ui/Modifier$Companion; ++ sget-object v18, Landroidx/compose/ui/Modifier;->Companion:Landroidx/compose/ui/Modifier$Companion; # base modifier + -+ const/16 v19, 0x0 ++ const/16 v19, 0x0 # clickable enabled=true default + -+ const/16 v20, 0x0 ++ const/16 v20, 0x0 # null role + -+ const/16 v21, 0x0 ++ const/16 v21, 0x0 # null onClickLabel + -+ sget-object v22, Lradiant/NoOp;->a:Lradiant/NoOp; # swallow taps so lyrics behind don't trigger ++ sget-object v22, Lradiant/NoOp;->a:Lradiant/NoOp; # swallow taps + -+ const/16 v23, 0xe ++ const/16 v23, 0xe # default-flag bitmask + -+ const/16 v24, 0x0 ++ const/16 v24, 0x0 # synthetic null + -+ invoke-static/range {v18 .. v24}, Landroidx/compose/foundation/ClickableKt;->clickable-XHw0xAI$default(Landroidx/compose/ui/Modifier;ZLjava/lang/String;Landroidx/compose/ui/semantics/Role;Ltl0/a;ILjava/lang/Object;)Landroidx/compose/ui/Modifier; ++ invoke-static/range {v18 .. v24}, Landroidx/compose/foundation/ClickableKt;->clickable-XHw0xAI$default(Landroidx/compose/ui/Modifier;ZLjava/lang/String;Landroidx/compose/ui/semantics/Role;Ltl0/a;ILjava/lang/Object;)Landroidx/compose/ui/Modifier; # apply clickable + -+ move-result-object v23 ++ move-result-object v23 # clickable modifier + -+ const/high16 v24, 0x41800000 # 16f (horizontal padding dp) ++ move-object/from16 v0, v23 # mod to low reg + -+ invoke-static/range {v24 .. v24}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F ++ const/4 v1, 0x0 # ignored fraction + -+ move-result v24 ++ const/4 v2, 0x1 # use default fraction + -+ const/high16 v25, 0x42a00000 # 80f (vertical padding dp) ++ const/4 v3, 0x0 # synthetic null + -+ invoke-static/range {v25 .. v25}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F ++ invoke-static {v0, v1, v2, v3}, Landroidx/compose/foundation/layout/SizeKt;->fillMaxWidth$default(Landroidx/compose/ui/Modifier;FILjava/lang/Object;)Landroidx/compose/ui/Modifier; # apply fillMaxWidth + -+ move-result v25 ++ move-result-object v23 # width-filled modifier + -+ invoke-static/range {v23 .. v25}, Landroidx/compose/foundation/layout/PaddingKt;->padding-VpY3zN4(Landroidx/compose/ui/Modifier;FF)Landroidx/compose/ui/Modifier; ++ const/high16 v24, 0x41800000 # 16f horizontal dp + -+ move-result-object v18 ++ invoke-static/range {v24 .. v24}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F # to Dp + -+ const/16 v19, 0x0 ++ move-result v24 # horiz padding dp ++ ++ const/high16 v25, 0x42a00000 # 80f vertical dp ++ ++ invoke-static/range {v25 .. v25}, Landroidx/compose/ui/unit/Dp;->constructor-impl(F)F # to Dp ++ ++ move-result v25 # vert padding dp ++ ++ invoke-static/range {v23 .. v25}, Landroidx/compose/foundation/layout/PaddingKt;->padding-VpY3zN4(Landroidx/compose/ui/Modifier;FF)Landroidx/compose/ui/Modifier; # apply padding ++ ++ move-result-object v18 # final modifier ++ ++ const/16 v19, 0x0 # null update lambda + + move-object/from16 v20, v7 # composer + -+ const/16 v21, 0x0 ++ const/16 v21, 0x0 # changed flags + -+ const/16 v22, 0x4 ++ const/16 v22, 0x4 # default mask + -+ invoke-static/range {v17 .. v22}, Landroidx/compose/ui/viewinterop/AndroidView_androidKt;->AndroidView(Ltl0/l;Landroidx/compose/ui/Modifier;Ltl0/l;Landroidx/compose/runtime/Composer;II)V # mount the progress pill on top of the lyrics ++ invoke-static/range {v17 .. v22}, Landroidx/compose/ui/viewinterop/AndroidView_androidKt;->AndroidView(Ltl0/l;Landroidx/compose/ui/Modifier;Ltl0/l;Landroidx/compose/runtime/Composer;II)V # mount progress pill + .line 201 .line 202 diff --git a/patches/lyrics-replace-lyric-button-1-remove.patch b/patches/lyrics-replace-lyric-button-1-remove.patch deleted file mode 100644 index 946bb16..0000000 --- a/patches/lyrics-replace-lyric-button-1-remove.patch +++ /dev/null @@ -1,16 +0,0 @@ -# rl-locals: com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali e( 79 ---- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali -+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali -@@ -4931,7 +4931,11 @@ - const/4 v10, 0x0 - - .line 226 -- invoke-static {v10, v9, v4, v2, v7}, Lcom/tidal/android/feature/playerscreen/ui/composables/h1;->a(Landroidx/compose/ui/Modifier;Ltl0/a;ZLandroidx/compose/runtime/Composer;I)V -+ const v10, 0x52414448 # empty group key -+ -+ invoke-interface {v2, v10}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty -+ -+ invoke-interface {v2}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close empty - - .line 227 - invoke-interface {v2}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V diff --git a/patches/lyrics-replace-lyric-button-2-sparkle.patch b/patches/lyrics-replace-lyric-button-2-sparkle.patch deleted file mode 100644 index 3d990fb..0000000 --- a/patches/lyrics-replace-lyric-button-2-sparkle.patch +++ /dev/null @@ -1,26 +0,0 @@ -# rl-locals: com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali e( 79 ---- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali -+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali -@@ -5838,6 +5838,22 @@ - :cond_51 - check-cast v4, Ltl0/a; - -+ new-instance v74, Lc8/j; # build lyrics-toggle lambda (same one h1 used) -+ -+ move-object/from16 v75, p5 # p5 holds the lambda receiver -+ -+ const/16 v76, 0x1 # discriminator 1 = lyrics action -+ -+ invoke-direct/range {v74 .. v76}, Lc8/j;->(Ljava/lang/Object;I)V # construct lambda -+ -+ const/16 v71, 0x0 # $$changed flags -+ -+ move-object/from16 v72, v7 # composer -+ -+ const/16 v73, 0x0 # modifier (null -> Companion) -+ -+ invoke-static/range {v71 .. v74}, Lradiant/SparkleButton;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V # render bottom-left sparkle -+ - const/4 v2, 0x0 - - invoke-static {v13, v7, v2, v4}, Lcom/tidal/android/feature/playerscreen/ui/composables/h3;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V