Merge pull request #19 from meowarex/dev

New Patches: Debug Menu & Legacy UI
This commit is contained in:
2026-05-24 01:22:49 +10:00
committed by GitHub
29 changed files with 415 additions and 205 deletions
+3 -3
View File
@@ -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.
+1 -1
View File
@@ -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\"")
@@ -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<PreferencesManager>().autoUpdateCheck) {
UpdateCheckWorker.schedule(this)
} else {
@@ -47,8 +47,6 @@ fun Scope.provideHttpClient() = HttpClient(OkHttp) {
override fun lookup(hostname: String): List<InetAddress> {
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<Inet4Address>()
} 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)
@@ -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) }
@@ -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)
}
@@ -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"
/**
@@ -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"
@@ -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
@@ -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
@@ -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
)
}
}
}
@@ -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<InstallStep>().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,
)
}
}
@@ -8,37 +8,64 @@ enum class KnownPatch(
@StringRes val titleRes: Int,
@StringRes val descRes: Int,
val requires: List<KnownPatch> = emptyList(),
val disables: List<KnownPatch> = 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<KnownPatch> = entries.sortedBy { it.fileNames.first() }
// Alphabetical by first filename, but pin DebugMenuUnlock to the bottom
val All: List<KnownPatch> = entries.sortedWith(
compareBy({ it == DebugMenuUnlock }, { it.fileNames.first() })
)
}
}
@@ -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<KnownPatch>): Set<KnownPatch> =
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<KnownPatch>
val disableUnits: Set<KnownPatch>
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<PatchComponent?>(null)
private set
var customPatches by mutableStateOf<PatchComponent?>(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)
@@ -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
@@ -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 -> {
@@ -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") ?: "")
@@ -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" />
</group>
</vector>
aight
+66 -29
View File
@@ -57,7 +57,9 @@
<string name="permissions_install_title">Install from Unknown Sources</string>
<string name="permissions_install_desc">Permissions are required to initiate an installation from this app.</string>
<string name="permissions_storage_title">External Storage</string>
<string name="permissions_storage_desc">Radiant Lyrics Manager stores shared data in ~/RadiantLyrics, which requires full storage permissions. Scoped storage is not currently supported.</string>
<string
name="permissions_storage_desc"
>Radiant Lyrics Manager stores shared data in ~/RadiantLyrics, which requires full storage permissions. Scoped storage is not currently supported.</string>
<string name="permissions_notifs_title">Notifications</string>
<string name="permissions_battery_title">Background Battery</string>
<string name="permissions_battery_desc">Ensures the installation process does not get automatically cancelled if the app is minimized.</string>
@@ -83,7 +85,9 @@
<string name="setting_installer">Installation Method</string>
<string name="setting_installer_desc">Various methods are supported to interface with the system\'s package installer.</string>
<string name="setting_keep_patched_apks">Keep patched APKs</string>
<string name="setting_keep_patched_apks_desc">Keep all patched files and APKs after installation for debugging purposes. Note that exporting the APK is only useful after a successful patching session.</string>
<string
name="setting_keep_patched_apks_desc"
>Keep all patched files and APKs after installation for debugging purposes. Note that exporting the APK is only useful after a successful patching session.</string>
<string name="settings_clear_cache">Clear cache</string>
<string name="settings_export_apk">Export APK</string>
@@ -94,7 +98,9 @@
<string name="installer_intent_desc">Launches an intent to handle installation through the system\'s configured default installer.</string>
<string name="installer_root_desc">Invokes PackageManager directly using device root such as Magisk or KernelSU.</string>
<string name="installer_shizuku">Shizuku</string>
<string name="installer_shizuku_desc">Invokes the PackageInstaller API remotely through Shizuku. ADB, Wireless ADB, and Root backends are all supported.</string>
<string
name="installer_shizuku_desc"
>Invokes the PackageInstaller API remotely through Shizuku. ADB, Wireless ADB, and Root backends are all supported.</string>
<string name="installer_dhizuku">Dhizuku</string>
<string name="installer_dhizuku_desc">Invokes the PackageInstaller API remotely through Dhizuku using DeviceOwner permissions.</string>
@@ -119,13 +125,15 @@
<string name="navigation_settings">Settings</string>
<string name="navigation_refresh">Refresh</string>
<string name="contributors_lead">Lead</string>
<string name="contributors_lead">Developer</string>
<string name="contributors">Contributors</string>
<string name="contributors_contributions">%s contributions</string>
<string name="network_load_fail">Failed to load</string>
<string name="network_warning_title">Metered network</string>
<string name="network_warning_body">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?</string>
<string
name="network_warning_body"
>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?</string>
<string name="network_warning_disable">Don\'t show this again</string>
<string name="version_none">None</string>
@@ -144,15 +152,21 @@
<string name="installer_uninstall_aborted">Cancelled uninstallation</string>
<string name="installer_dl_verify_fail">Failed to verify download</string>
<string name="installer_insufficient_storage">Insufficient storage space!</string>
<string name="installer_banner_minimization">Since battery optimizations have not been disabled, minimizing this screen may abort the installation process!</string>
<string name="installer_banner_failure">Installation failed! You can either retry or click this banner to open the GitHub repository for help.</string>
<string name="installer_banner_success">Successfully installed Radiant Lyrics! Do *NOT* uninstall the manager (this app) as it is required to perform certain types of updates.</string>
<string
name="installer_banner_minimization"
>Since battery optimizations have not been disabled, minimizing this screen may abort the installation process!</string>
<string
name="installer_banner_failure"
>Installation failed! You can either retry or click this banner to open the GitHub repository for help.</string>
<string
name="installer_banner_success"
>Successfully installed Radiant Lyrics! Do *NOT* uninstall the manager (this app) as it is required to perform certain types of updates.</string>
<string name="install_error_unknown">Installer failure (Unknown reason)</string>
<string name="install_error_unhandled_intent">No handlers available for %s!</string>
<string name="install_error_blocked">Installation was blocked</string>
<string name="install_error_invalid">One or more APKs were invalid or corrupt</string>
<string name="install_error_conflict">Conflicts with an existing app, usually due to mismatched signatures</string>
<string name="install_error_conflict">Conflicts with an existing app (Change Name &amp; Com values)</string>
<string name="install_error_storage">Not enough storage available to install</string>
<string name="install_error_incompatible">Application is incompatible with this device</string>
<string name="install_error_timeout">Installer timed out</string>
@@ -162,16 +176,16 @@
<string name="install_group_patch">Patch APK</string>
<string name="install_group_install">Install APK</string>
<string name="patch_step_fetch_info">Fetching target TIDAL version</string>
<string name="patch_step_fetch_info">Fetching TIDAL version</string>
<string name="patch_step_downgrade_check">Checking for newer installations</string>
<string name="patch_step_restore_cache">Restoring from cache</string>
<string name="patch_step_dl_tidal_apk">Downloading TIDAL APK</string>
<string name="patch_step_dl_smali">Downloading smali patches</string>
<string name="patch_step_dl_smali">Downloading Patches</string>
<string name="patch_step_copy_deps">Copying dependencies</string>
<string name="patch_step_patch_manifests">Patching APK manifest</string>
<string name="patch_step_patch_certs">Adding modern root certificates</string>
<string name="patch_step_patch_icon">Patching app icon</string>
<string name="patch_step_patch_smali">Applying smali patches</string>
<string name="patch_step_patch_smali">Applying Patches</string>
<string name="patch_step_reorganize_dex">Reorganizing dex files</string>
<string name="patch_step_save_metadata">Store metadata</string>
<string name="patch_step_alignment">Aligning APK</string>
@@ -189,7 +203,9 @@
<string name="installer_abort_body">Are you sure you really want to abort an in-progress installation?</string>
<string name="updater_title">Update to v%s</string>
<string name="updater_body">A new update has been released for Radiant Lyrics Manager! It may be required in order to function properly. Would you like to update?</string>
<string
name="updater_body"
>A new update has been released for Radiant Lyrics Manager! It may be required in order to function properly. Would you like to update?</string>
<string name="updater_open_github">View release on GitHub</string>
<string name="updater_check_fail">Failed to check for updates!</string>
<string name="updater_update_fail">Failed to update! Please download and install the update manually!</string>
@@ -203,14 +219,20 @@
<string name="home_network_fail">Failed to fetch data!</string>
<string name="patchopts_title">Radiant Lyrics provides several customizations at installation time that are not able to be changed once installed.</string>
<string
name="patchopts_title"
>Radiant Lyrics provides several customizations at installation time that are not able to be changed once installed.</string>
<string name="patchopts_pkgname_title">Package Name</string>
<string name="patchopts_pkgname_desc">The package name is a unique identifier for all apps. Using different ones can allow for multiple installations of Radiant Lyrics.</string>
<string
name="patchopts_pkgname_desc"
>The package name is a unique identifier for all apps. Using different ones can allow for multiple installations of Radiant Lyrics.</string>
<string name="patchopts_pkgname_invalid">Invalid package name!</string>
<string name="patchopts_pkgname_taken">Target app will be overwritten!</string>
<string name="patchopts_pkgname_ok">Valid package name!</string>
<string name="patchopts_appname_title">App Name</string>
<string name="patchopts_appname_desc">The app name is what\'s displayed in your home launcher. This should be changed on secondary installations for ease of use.</string>
<string
name="patchopts_appname_desc"
>The app name is what\'s displayed in your home launcher. This should be changed on secondary installations for ease of use.</string>
<string name="patchopts_debuggable_title">Debuggable</string>
<string name="patchopts_debuggable_desc">Enable the debuggable manifest flag. Only use this if you know what you are doing!</string>
<string name="patchopts_custom_injector_title">Custom Injector</string>
@@ -224,13 +246,25 @@
<string name="patchopts_patches_summary">%1$d of %2$d enabled</string>
<string name="patch_lyrics_disable_cover_title">Disable Lyrics Cover</string>
<string name="patch_lyrics_disable_cover_desc">Prevents the album cover from being hidden when lyrics are showing.</string>
<string name="patch_lyrics_disable_cover_desc">Removes the mini track cover from the lyrics screen.</string>
<string name="patch_lyrics_progress_pill_title">Lyrics Progress Pill</string>
<string name="patch_lyrics_progress_pill_desc">Shows a progress pill while the upcoming lyric line loads in, with a soft fade above and below the lyrics list.</string>
<string name="patch_lyrics_progress_pill_desc">Restores the old Track Progress Pill in the top of the Lyrics screen!</string>
<string name="patch_lyrics_replace_button_title">Replace Lyrics Button</string>
<string name="patch_lyrics_replace_button_desc">Replaces the Lyrics button with the RL Sparkle!</string>
<string name="patch_player_backdrop_title">Player Backdrop</string>
<string name="patch_player_backdrop_desc">Restores the legacy translucent backdrop blur behind the player.</string>
<string name="patch_lyrics_replace_share_button_title">Replace Share Button</string>
<string
name="patch_lyrics_replace_share_button_desc"
>Replaces the share button with the Tidal Connect button from the top of the screen. (it\'s in the menu already)</string>
<string name="patch_debug_menu_unlock_title">Unlock Debug Menu</string>
<string
name="patch_debug_menu_unlock_desc"
>Reveals TIDAL\'s hidden Debug Menu in Settings. (Unlocks Feature Flags &amp; the legacy Debug Options)</string>
<string name="patch_enable_legacy_ui_title">Enable Legacy UI</string>
<string
name="patch_enable_legacy_ui_desc"
>[This patch will stop working soon] - Replaces the New Compose based UI with the Legacy UI (disables player-market-ui feature flag)</string>
<string name="componentopts_screen_title">Custom Component (%s)</string>
<string name="componentopts_screen_desc">Select a custom build that was imported by Manager.</string>
@@ -261,24 +295,27 @@
<string name="notif_install_fail_desc">Click to view more info…</string>
<string name="play_protect_warning_title">Play Protect</string>
<string name="play_protect_warning_desc">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\"</string>
<string
name="play_protect_warning_desc"
>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\"</string>
<string name="play_protect_warning_ok">Continue</string>
<string name="play_protect_warning_open_gpp">Open Play Protect</string>
<string name="play_protect_warning_disable">Don\'t show this again</string>
<string name="fun_fact_prefix">Fun Fact: %s</string>
<string name="fun_fact_1">Did you know that Google Play Protect is useless?</string>
<string name="fun_fact_2">Radiant Lyrics restores the blurred album art lyrics dialog from TIDAL\'s legacy player.</string>
<string name="fun_fact_3">The legacy LyricsDialog uses a BlurTransformation with radius 5 via RenderScript.</string>
<string name="fun_fact_4">i am in your walls</string>
<string name="fun_fact_5">The TIDAL app has two coexisting player implementations — old View-based and new Compose.</string>
<string name="fun_fact_1">Did you know that TIDAL no longer like blur!</string>
<string name="fun_fact_2">Radiant Lyrics is also available for DESKTOP!!!</string>
<string name="fun_fact_3">i am in your walls!</string>
<string name="fun_fact_4">TIDAL is Rewriting the UI with Compose</string>
<string name="fun_fact_5">The TIDAL app has a hidden Debug Menu!</string>
<string name="fun_fact_6">Having issues? Open an issue on GitHub!</string>
<string name="fun_fact_7">The lyricsButton was hidden because TIDAL rerouted lyrics data to the new Compose player.</string>
<string name="fun_fact_8" translatable="false">\"just patch it lol\"</string>
<string name="fun_fact_9">Radiant Lyrics targets TIDAL v2.192.0</string>
<string name="fun_fact_7">This installer/manager was forked from Aliucord</string>
<string name="fun_fact_8">I have CATS!!</string>
<string name="fun_fact_9">Radiant Lyrics is made by JUST meoware.exe</string>
<string name="fun_fact_10">Smali patches are distributed as unified diffs inside patches.zip</string>
<string name="fun_fact_11">The blurred background uses Coil + BlurTransformation(radius=5) crossfaded onto an ImageView.</string>
<string name="fun_fact_12" translatable="false">meowarex made this</string>
<string name="fun_fact_11">I wrote these facts while under the influence of brain damage..</string>
<string name="fun_fact_12" translatable="false">i love you!</string>
<string name="fun_fact_13" translatable="false">TIDAL devs if you\'re reading this, please add all this to the app</string>
<string name="time_elapsed_seconds">%.2fs</string>
</resources>
+3 -2
View File
@@ -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" |
+43
View File
@@ -0,0 +1,43 @@
--- a/com/tidal/android/core/debug/DebugFeatureInteractorDefault.smali
+++ b/com/tidal/android/core/debug/DebugFeatureInteractorDefault.smali
@@ -53,6 +53,11 @@
.method public final a()Z
.locals 2
+ # rl-debug-unlock: force a() to always return true, bypassing the
+ # "debug-menu" feature flag check that normally hides the Settings entry.
+ const/4 v0, 0x1
+ return v0
+
.line 1
iget-object v0, p0, Lcom/tidal/android/core/debug/DebugFeatureInteractorDefault;->a:Lcom/tidal/android/featureflags/l;
@@ -97,6 +102,14 @@
}
.end annotation
+ # rl-debug-unlock: short-circuit b() to always emit MutableStateFlow(TRUE).
+ # Mirrors the original ":cond_1 :goto_0" path the app uses for internal/test builds.
+ sget-object v0, Ljava/lang/Boolean;->TRUE:Ljava/lang/Boolean;
+
+ invoke-static {v0}, Lkotlinx/coroutines/flow/StateFlowKt;->MutableStateFlow(Ljava/lang/Object;)Lkotlinx/coroutines/flow/MutableStateFlow;
+
+ move-result-object v0
+
+ return-object v0
+
.line 1
sget-object v0, Lh50/a;->a:Ljava/lang/String;
@@ -261,6 +274,11 @@
.method public final c()Z
.locals 3
+ # rl-debug-unlock: force c() to always return true, bypassing the
+ # applicationId-substring / in-app-bug-reports / export-logs flag checks.
+ const/4 v0, 0x1
+ return v0
+
.line 1
sget-object v0, Lh50/a;->a:Ljava/lang/String;
+12
View File
@@ -0,0 +1,12 @@
--- a/ig/d.smali
+++ b/ig/d.smali
@@ -26,8 +26,8 @@
new-instance v0, Lig/d;
.line 2
.line 3
- const-string v1, "player-market-ui"
+ const-string v1, "player-market-ui-rl-forced-off" # break flag key lookup
.line 4
.line 5
-30
View File
@@ -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
+39 -27
View File
@@ -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
@@ -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
@@ -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;-><init>(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
@@ -0,0 +1,39 @@
# 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
@@ -5838,6 +5838,22 @@
:cond_51
check-cast v4, Ltl0/a;
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
+
+ new-instance v74, Lc8/j; # lyrics-toggle lambda
+
+ move-object/from16 v75, p5 # lambda receiver
+
+ const/16 v76, 0x1 # lyrics action
+
+ invoke-direct/range {v74 .. v76}, Lc8/j;-><init>(Ljava/lang/Object;I)V # build lambda
+
+ const/16 v71, 0x0 # changed flags
+
+ move-object/from16 v72, v7 # composer
+
+ const/16 v73, 0x0 # null modifier
+
+ invoke-static/range {v71 .. v74}, Lradiant/SparkleButton;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V # render sparkle button
+51
View File
@@ -0,0 +1,51 @@
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
@@ -5086,7 +5086,11 @@
const/4 v13, 0x0
.line 247
- invoke-static {v9, v0, v13, v2, v10}, Lcom/tidal/android/feature/playerscreen/ui/composables/BroadcastButtonKt;->b(Lcom/tidal/android/feature/playerscreen/ui/b;Ltl0/a;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
+ const v9, 0x52414244 # empty group key
+
+ invoke-interface {v2, v9}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty
+
+ invoke-interface {v2}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close empty
.line 248
invoke-interface {v2}, Landroidx/compose/runtime/Composer;->endNode()V
@@ -5758,17 +5758,19 @@
:cond_53
- new-instance v4, Landroidx/compose/foundation/text/input/internal/selection/l;
-
- const/4 v6, 0x2
-
- invoke-direct {v4, v11, v6}, Landroidx/compose/foundation/text/input/internal/selection/l;-><init>(Ljava/lang/Object;I)V
-
- .line 336
- invoke-interface {v7, v4}, Landroidx/compose/runtime/Composer;->updateRememberedValue(Ljava/lang/Object;)V
-
- .line 337
- :cond_54
- check-cast v4, Ltl0/a;
-
- const/4 v2, 0x0
-
- invoke-static {v13, v7, v2, v4}, Lcom/tidal/android/feature/playerscreen/ui/composables/u4;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V
+ new-instance v4, Lcom/tidal/android/feature/playerscreen/ui/f0; # connect click lambda
+
+ move-object/from16 v6, p5 # action dispatcher
+
+ const/4 v8, 0x0 # connect-clicked discriminator
+
+ invoke-direct {v4, v6, v8}, Lcom/tidal/android/feature/playerscreen/ui/f0;-><init>(Ljava/lang/Object;I)V # build lambda
+
+ invoke-interface {v7, v4}, Landroidx/compose/runtime/Composer;->updateRememberedValue(Ljava/lang/Object;)V # cache lambda
+
+ :cond_54
+ check-cast v4, Ltl0/a;
+
+ iget-object v8, v10, Lcom/tidal/android/feature/playerscreen/ui/r$a;->d:Lcom/tidal/android/feature/playerscreen/ui/b; # broadcast state
+
+ const/4 v2, 0x0 # changed flags
+
+ invoke-static {v8, v4, v13, v7, v2}, Lcom/tidal/android/feature/playerscreen/ui/composables/BroadcastButtonKt;->b(Lcom/tidal/android/feature/playerscreen/ui/b;Ltl0/a;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V # render connect button
@@ -0,0 +1,41 @@
--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
+++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt.smali
@@ -5854,17 +5854,38 @@
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
+
+ iget-boolean v2, v10, Lcom/tidal/android/feature/playerscreen/ui/r$a;->i:Z # hasLyrics flag
+
+ if-eqz v2, :cond_sparkle_no_lyrics # branch when no lyrics
+
+ const v2, 0x30551b59 # group key (lyrics)
+
+ invoke-interface {v7, v2}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open lyrics branch
new-instance v74, Lc8/j; # lyrics-toggle lambda
move-object/from16 v75, p5 # lambda receiver
const/16 v76, 0x1 # lyrics action
invoke-direct/range {v74 .. v76}, Lc8/j;-><init>(Ljava/lang/Object;I)V # build lambda
const/16 v71, 0x0 # changed flags
move-object/from16 v72, v7 # composer
const/16 v73, 0x0 # null modifier
invoke-static/range {v71 .. v74}, Lradiant/SparkleButton;->a(ILandroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;Ltl0/a;)V # render sparkle button
+
+ invoke-interface {v7}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close lyrics branch
+
+ goto :goto_sparkle_done # skip empty branch
+
+ :cond_sparkle_no_lyrics
+ const v2, 0x3057f75c # group key (no lyrics)
+
+ invoke-interface {v7, v2}, Landroidx/compose/runtime/Composer;->startReplaceGroup(I)V # open empty
+
+ invoke-interface {v7}, Landroidx/compose/runtime/Composer;->endReplaceGroup()V # close empty
+
+ :goto_sparkle_done