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 7fa7808..4a4dade 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 @@ -4,19 +4,31 @@ import androidx.annotation.StringRes import com.meowarex.rlmobile.R enum class KnownPatch( + /** + * Numeric display order in the patch options list. Lower = higher up. + * + * Convention: main patches use multiples of 10 (10, 20, 30, …). Patches + * that act as helpers/dependencies of a main patch get offsets adjacent to + * the requirer (e.g. main at 40, helpers at 41, 42, 43). DebugMenuUnlock + * is pinned to 100 to keep it at the bottom of the list. + */ + val order: Int, val fileNames: List, @StringRes val titleRes: Int, @StringRes val descRes: Int, val requires: List = emptyList(), val disables: List = emptyList(), ) { - // Dependency-first order (later refs need backward resolution) + // Dependency-first order (later refs need backward resolution). + // The `order` field controls display order; declaration order doesn't matter. LyricsDisableCover( + order = 41, fileNames = listOf("lyrics-disable-cover.patch"), titleRes = R.string.patch_lyrics_disable_cover_title, descRes = R.string.patch_lyrics_disable_cover_desc, ), LyricsReplaceLyricsButton( + order = 42, fileNames = listOf( "lyrics-replace-lyrics-button.patch", "lyrics-sparkle-conditional-visibility.patch", @@ -25,21 +37,40 @@ enum class KnownPatch( descRes = R.string.patch_lyrics_replace_button_desc, ), LyricsReplaceShareButton( + order = 43, 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, ), + LyricsRlApi( + order = 20, + fileNames = listOf( + "lyrics-rl-api.patch", + "lyrics-rl-api-observer.patch", + ), + titleRes = R.string.patch_lyrics_rl_api_title, + descRes = R.string.patch_lyrics_rl_api_desc, + ), + LyricsKeepControlsVisible( + order = 60, + fileNames = listOf("lyrics-keep-controls-visible.patch"), + titleRes = R.string.patch_lyrics_keep_controls_title, + descRes = R.string.patch_lyrics_keep_controls_desc, + ), PlayerBackdrop( + order = 30, fileNames = listOf("player-backdrop.patch"), titleRes = R.string.patch_player_backdrop_title, descRes = R.string.patch_player_backdrop_desc, ), DebugMenuUnlock( + order = 100, fileNames = listOf("debug-menu-unlock.patch"), titleRes = R.string.patch_debug_menu_unlock_title, descRes = R.string.patch_debug_menu_unlock_desc, ), LyricsProgressPill( + order = 40, fileNames = listOf( "lyrics-progress-pill.patch", "lyrics-fade-region.patch", @@ -49,6 +80,7 @@ enum class KnownPatch( requires = listOf(LyricsDisableCover, LyricsReplaceShareButton), ), EnableLegacyUi( + order = 10, fileNames = listOf("enable-legacy-ui.patch"), titleRes = R.string.patch_enable_legacy_ui_title, descRes = R.string.patch_enable_legacy_ui_desc, @@ -57,15 +89,20 @@ enum class KnownPatch( LyricsDisableCover, LyricsReplaceLyricsButton, LyricsReplaceShareButton, + LyricsRlApi, + LyricsKeepControlsVisible, PlayerBackdrop, LyricsProgressPill, ), ); companion object { - // Alphabetical by first filename, but pin DebugMenuUnlock to the bottom + /** + * Sorted by `order` ascending. Tie-breaks fall back to the first filename + * (alphabetical) so the order is always deterministic. + */ val All: List = entries.sortedWith( - compareBy({ it == DebugMenuUnlock }, { it.fileNames.first() }) + compareBy({ it.order }, { it.fileNames.first() }) ) } } diff --git a/Manager/app/src/main/res/values/strings.xml b/Manager/app/src/main/res/values/strings.xml index 5fbbd1a..1a1befc 100644 --- a/Manager/app/src/main/res/values/strings.xml +++ b/Manager/app/src/main/res/values/strings.xml @@ -259,6 +259,14 @@ Restores the old Track Progress Pill in the top of the Lyrics screen! Replace Lyrics Button Replaces the Lyrics button with the RL Sparkle! + Radiant Lyrics API + Use Radiant Lyrics API to fetch Lyrics (Higher quality & More providers) + Sticky Lyrics + Always Forces the Lyrics page to be opened (aslong as the track has lyrics) + Keep Controls Visible + Inverts the auto-hide behavior on the lyrics screen, playback controls stay visible by default and only hide when you tap them off. Player Backdrop Restores the legacy translucent backdrop blur behind the player. Replace Share Button diff --git a/patches/extension/radiant/RLAPILyricsHook.smali b/patches/extension/radiant/RLAPILyricsHook.smali new file mode 100644 index 0000000..abac4a2 --- /dev/null +++ b/patches/extension/radiant/RLAPILyricsHook.smali @@ -0,0 +1,210 @@ +.class public final Lradiant/RLAPILyricsHook; +.super Ljava/lang/Object; + + +# static fields +.field public static volatile currentKey:Ljava/lang/String; + +.field public static volatile isRlState:Z + + +# direct methods +.method static constructor ()V + .locals 1 + + const/4 v0, 0x0 + + sput-object v0, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String; + + sput-boolean v0, Lradiant/RLAPILyricsHook;->isRlState:Z + + return-void +.end method + +.method private constructor ()V + .locals 0 + + invoke-direct {p0}, Ljava/lang/Object;->()V + + return-void +.end method + +.method public static dlog(Ljava/lang/String;)V + .locals 1 + + const-string v0, "RLLyrics" + + invoke-static {v0, p0}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I + + return-void +.end method + +.method private static isBlank(Ljava/lang/String;)Z + .locals 2 + + if-nez p0, :not_null + + const/4 v0, 0x1 + + return v0 + + :not_null + invoke-virtual {p0}, Ljava/lang/String;->trim()Ljava/lang/String; + + move-result-object v0 + + invoke-virtual {v0}, Ljava/lang/String;->isEmpty()Z + + move-result v1 + + return v1 +.end method + +.method public static onWampTrack(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Lcom/aspiro/wamp/model/Track;)V + .locals 11 + + const/4 v3, 0x0 + + sput-boolean v3, Lradiant/RLAPILyricsHook;->isRlState:Z + + const-string v3, "onWampTrack: hook entered" + + invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + if-eqz p1, :null_track + + if-eqz p0, :done + + goto :have_track + + :null_track + const-string v3, "onWampTrack: wamp Track is null, skip" + + invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + return-void + + :have_track + invoke-virtual {p1}, Lcom/aspiro/wamp/model/MediaItem;->getTitle()Ljava/lang/String; + + move-result-object v1 + + if-nez v1, :title_present + + const-string v3, "bail: getTitle() returned null" + + invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + return-void + + :title_present + invoke-static {v1}, Lradiant/RLAPILyricsHook;->isBlank(Ljava/lang/String;)Z + + move-result v2 + + if-eqz v2, :title_ok + + const-string v3, "bail: title is blank" + + invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + return-void + + :title_ok + invoke-virtual {p1}, Lcom/aspiro/wamp/model/MediaItem;->getArtistNames()Ljava/lang/String; + + move-result-object v2 + + if-nez v2, :artist_present + + const-string v3, "bail: getArtistNames() returned null" + + invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + return-void + + :artist_present + invoke-static {v2}, Lradiant/RLAPILyricsHook;->isBlank(Ljava/lang/String;)Z + + move-result v3 + + if-eqz v3, :artist_ok + + const-string v3, "bail: artist is blank" + + invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + return-void + + :artist_ok + const-string v3, "" + + new-instance v4, Ljava/lang/StringBuilder; + + invoke-direct {v4}, Ljava/lang/StringBuilder;->()V + + invoke-virtual {v4, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const-string v5, "|" + + invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v4, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v4 + + sput-object v4, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String; + + new-instance v5, Ljava/lang/StringBuilder; + + const-string v6, "onWampTrack: fetching for title='" + + invoke-direct {v5, v6}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V + + invoke-virtual {v5, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const-string v6, "' artist='" + + invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const-string v6, "'" + + invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v5 + + invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + new-instance v5, Lradiant/RLAPILyricsWorker; + + move-object v6, p0 + + move-object v7, v1 + + move-object v8, v2 + + move-object v9, v4 + + move-object v10, v3 + + invoke-direct/range {v5 .. v10}, Lradiant/RLAPILyricsWorker;->(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + + new-instance v6, Ljava/lang/Thread; + + invoke-direct {v6, v5}, Ljava/lang/Thread;->(Ljava/lang/Runnable;)V + + const/4 v7, 0x1 + + invoke-virtual {v6, v7}, Ljava/lang/Thread;->setDaemon(Z)V + + invoke-virtual {v6}, Ljava/lang/Thread;->start()V + + :done + return-void +.end method diff --git a/patches/extension/radiant/RLAPILyricsWorker.smali b/patches/extension/radiant/RLAPILyricsWorker.smali new file mode 100644 index 0000000..c966672 --- /dev/null +++ b/patches/extension/radiant/RLAPILyricsWorker.smali @@ -0,0 +1,545 @@ +.class public final Lradiant/RLAPILyricsWorker; +.super Ljava/lang/Object; +.implements Ljava/lang/Runnable; + + +# instance fields +.field public final vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + +.field public final title:Ljava/lang/String; + +.field public final artist:Ljava/lang/String; + +.field public final key:Ljava/lang/String; + +.field public final lyricsId:Ljava/lang/String; + + +# direct methods +.method public constructor (Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + .locals 0 + + invoke-direct {p0}, Ljava/lang/Object;->()V + + iput-object p1, p0, Lradiant/RLAPILyricsWorker;->vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + iput-object p2, p0, Lradiant/RLAPILyricsWorker;->title:Ljava/lang/String; + + iput-object p3, p0, Lradiant/RLAPILyricsWorker;->artist:Ljava/lang/String; + + iput-object p4, p0, Lradiant/RLAPILyricsWorker;->key:Ljava/lang/String; + + iput-object p5, p0, Lradiant/RLAPILyricsWorker;->lyricsId:Ljava/lang/String; + + return-void +.end method + +.method public static fetch(Ljava/lang/String;Z)Ljava/lang/String; + .locals 7 + + :try_start + new-instance v0, Ljava/net/URL; + + invoke-direct {v0, p0}, Ljava/net/URL;->(Ljava/lang/String;)V + + invoke-virtual {v0}, Ljava/net/URL;->openConnection()Ljava/net/URLConnection; + + move-result-object v0 + + check-cast v0, Ljava/net/HttpURLConnection; + + const-string v1, "GET" + + invoke-virtual {v0, v1}, Ljava/net/HttpURLConnection;->setRequestMethod(Ljava/lang/String;)V + + const v1, 0x2710 + + invoke-virtual {v0, v1}, Ljava/net/URLConnection;->setConnectTimeout(I)V + + invoke-virtual {v0, v1}, Ljava/net/URLConnection;->setReadTimeout(I)V + + if-eqz p1, :no_auth + + const-string v1, "P-Access-Token-Id" + + const-string v2, "58hy4s86" + + invoke-virtual {v0, v1, v2}, Ljava/net/URLConnection;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V + + const-string v1, "P-Access-Token" + + const-string v2, "xjehy2lfg5h5mjwotoxrcqugam" + + invoke-virtual {v0, v1, v2}, Ljava/net/URLConnection;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V + + const-string v1, "x-client-ip" + + const-string v2, "null" + + invoke-virtual {v0, v1, v2}, Ljava/net/URLConnection;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V + + :no_auth + invoke-virtual {v0}, Ljava/net/HttpURLConnection;->getResponseCode()I + + move-result v1 + + new-instance v3, Ljava/lang/StringBuilder; + + const-string v4, "fetch status=" + + invoke-direct {v3, v4}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V + + invoke-virtual {v3, v1}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; + + const-string v4, " url=" + + invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v3, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v3 + + invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + const/16 v2, 0xc8 + + if-eq v1, v2, :status_ok + + invoke-virtual {v0}, Ljava/net/HttpURLConnection;->disconnect()V + + const/4 v1, 0x0 + + return-object v1 + + :status_ok + invoke-virtual {v0}, Ljava/net/HttpURLConnection;->getInputStream()Ljava/io/InputStream; + + move-result-object v1 + + new-instance v2, Ljava/io/InputStreamReader; + + const-string v3, "UTF-8" + + invoke-direct {v2, v1, v3}, Ljava/io/InputStreamReader;->(Ljava/io/InputStream;Ljava/lang/String;)V + + new-instance v3, Ljava/io/BufferedReader; + + invoke-direct {v3, v2}, Ljava/io/BufferedReader;->(Ljava/io/Reader;)V + + new-instance v4, Ljava/lang/StringBuilder; + + invoke-direct {v4}, Ljava/lang/StringBuilder;->()V + + :read_loop + invoke-virtual {v3}, Ljava/io/BufferedReader;->readLine()Ljava/lang/String; + + move-result-object v5 + + if-eqz v5, :read_done + + invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const/16 v5, 0xa + + invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder; + + goto :read_loop + + :read_done + invoke-virtual {v3}, Ljava/io/BufferedReader;->close()V + + invoke-virtual {v0}, Ljava/net/HttpURLConnection;->disconnect()V + + invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v6 + + return-object v6 + + :try_end + .catchall {:try_start .. :try_end} :catch_all + + :catch_all + move-exception v0 + + new-instance v1, Ljava/lang/StringBuilder; + + const-string v2, "fetch exception url=" + + invoke-direct {v1, v2}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V + + invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const-string v2, " err=" + + invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v0}, Ljava/lang/Throwable;->toString()Ljava/lang/String; + + move-result-object v0 + + invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v1 + + invoke-static {v1}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + const/4 v1, 0x0 + + return-object v1 +.end method + +.method public static parseLines(Ljava/lang/String;)Ljava/util/ArrayList; + .locals 11 + + :try_start + new-instance v0, Lorg/json/JSONObject; + + invoke-direct {v0, p0}, Lorg/json/JSONObject;->(Ljava/lang/String;)V + + const-string v1, "type" + + const-string v2, "" + + invoke-virtual {v0, v1, v2}, Lorg/json/JSONObject;->optString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + + move-result-object v1 + + const-string v2, "Line" + + invoke-virtual {v2, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z + + move-result v2 + + if-nez v2, :type_ok + + const-string v2, "Word" + + invoke-virtual {v2, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z + + move-result v2 + + if-nez v2, :type_ok + + const/4 v0, 0x0 + + return-object v0 + + :type_ok + const-string v1, "data" + + invoke-virtual {v0, v1}, Lorg/json/JSONObject;->optJSONArray(Ljava/lang/String;)Lorg/json/JSONArray; + + move-result-object v0 + + if-nez v0, :data_ok + + const/4 v0, 0x0 + + return-object v0 + + :data_ok + invoke-virtual {v0}, Lorg/json/JSONArray;->length()I + + move-result v1 + + if-nez v1, :nonempty + + const/4 v0, 0x0 + + return-object v0 + + :nonempty + new-instance v2, Ljava/util/ArrayList; + + invoke-direct {v2, v1}, Ljava/util/ArrayList;->(I)V + + const/4 v3, 0x0 + + :loop + if-ge v3, v1, :loop_done + + invoke-virtual {v0, v3}, Lorg/json/JSONArray;->getJSONObject(I)Lorg/json/JSONObject; + + move-result-object v4 + + const-string v5, "text" + + const-string v6, "" + + invoke-virtual {v4, v5, v6}, Lorg/json/JSONObject;->optString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + + move-result-object v5 + + const-string v6, "startTime" + + const-wide/16 v7, 0x0 + + invoke-virtual {v4, v6, v7, v8}, Lorg/json/JSONObject;->optDouble(Ljava/lang/String;D)D + + move-result-wide v7 + + const-wide v9, 0x408f400000000000L + + mul-double/2addr v7, v9 + + double-to-long v7, v7 + + new-instance v4, Lcom/tidal/android/feature/playerscreen/ui/f; + + invoke-direct {v4, v5, v7, v8}, Lcom/tidal/android/feature/playerscreen/ui/f;->(Ljava/lang/String;J)V + + invoke-virtual {v2, v4}, Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z + + add-int/lit8 v3, v3, 0x1 + + goto :loop + + :loop_done + return-object v2 + + :try_end + .catchall {:try_start .. :try_end} :catch_all + + :catch_all + move-exception v0 + + const/4 v1, 0x0 + + return-object v1 +.end method + +.method private runImpl()V + .locals 11 + + iget-object v0, p0, Lradiant/RLAPILyricsWorker;->title:Ljava/lang/String; + + const-string v1, "UTF-8" + + invoke-static {v0, v1}, Ljava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + + move-result-object v0 + + iget-object v2, p0, Lradiant/RLAPILyricsWorker;->artist:Ljava/lang/String; + + invoke-static {v2, v1}, Ljava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + + move-result-object v1 + + new-instance v2, Ljava/lang/StringBuilder; + + const-string v3, "?title=" + + invoke-direct {v2, v3}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V + + invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const-string v0, "&artist=" + + invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v2, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + const-string v0, "&platform=Radiant%20Lyrics" + + invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v2 + + new-instance v3, Ljava/lang/StringBuilder; + + const-string v4, "https://api.atomix.one/rl-api" + + invoke-direct {v3, v4}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V + + invoke-virtual {v3, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v3 + + const/4 v4, 0x1 + + invoke-static {v3, v4}, Lradiant/RLAPILyricsWorker;->fetch(Ljava/lang/String;Z)Ljava/lang/String; + + move-result-object v3 + + if-nez v3, :got_body + + new-instance v3, Ljava/lang/StringBuilder; + + const-string v4, "https://rl-api.kineticsand.net/lyrics" + + invoke-direct {v3, v4}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V + + invoke-virtual {v3, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v3 + + const/4 v4, 0x0 + + invoke-static {v3, v4}, Lradiant/RLAPILyricsWorker;->fetch(Ljava/lang/String;Z)Ljava/lang/String; + + move-result-object v3 + + if-nez v3, :got_body + + const-string v4, "both primary and fallback fetches failed" + + invoke-static {v4}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + sget-boolean v4, Lradiant/StickyLyrics;->enabled:Z + + if-nez v4, :no_close + + iget-object v4, p0, Lradiant/RLAPILyricsWorker;->vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + iget-object v5, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->N:Lkotlinx/coroutines/flow/MutableStateFlow; + + sget-object v6, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean; + + invoke-interface {v5, v6}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + + :no_close + return-void + + :got_body + invoke-static {v3}, Lradiant/RLAPILyricsWorker;->parseLines(Ljava/lang/String;)Ljava/util/ArrayList; + + move-result-object v3 + + if-nez v3, :parse_ok + + const-string v4, "parse returned null (bad JSON / unsupported type)" + + invoke-static {v4}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + return-void + + :parse_ok + invoke-virtual {v3}, Ljava/util/ArrayList;->size()I + + move-result v4 + + if-nez v4, :nonempty + + const-string v5, "parsed 0 lines" + + invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + return-void + + :nonempty + new-instance v5, Ljava/lang/StringBuilder; + + const-string v6, "parsed " + + invoke-direct {v5, v6}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V + + invoke-virtual {v5, v4}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; + + const-string v6, " lines" + + invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object v5 + + invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + iget-object v4, p0, Lradiant/RLAPILyricsWorker;->key:Ljava/lang/String; + + sget-object v5, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String; + + invoke-virtual {v4, v5}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z + + move-result v4 + + if-nez v4, :key_ok + + const-string v5, "race-check failed (user skipped tracks)" + + invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + return-void + + :key_ok + iget-object v4, p0, Lradiant/RLAPILyricsWorker;->vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + iget-object v5, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->M:Lkotlinx/coroutines/flow/MutableStateFlow; + + invoke-static {v3}, Ltn0/a;->c(Ljava/lang/Iterable;)Ltn0/b; + + move-result-object v6 + + iget-object v7, p0, Lradiant/RLAPILyricsWorker;->lyricsId:Ljava/lang/String; + + if-nez v7, :have_id + + const-string v7, "" + + :have_id + new-instance v8, Lcom/tidal/android/feature/playerscreen/ui/g$c; + + const/4 v9, -0x1 + + const/4 v10, 0x0 + + invoke-direct {v8, v7, v6, v9, v10}, Lcom/tidal/android/feature/playerscreen/ui/g$c;->(Ljava/lang/String;Ltn0/b;IZ)V + + const-string v6, "publishing g$c -> J=true N=true M=g$c" + + invoke-static {v6}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + const/4 v6, 0x1 + + sput-boolean v6, Lradiant/RLAPILyricsHook;->isRlState:Z + + iget-object v6, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->J:Lkotlinx/coroutines/flow/MutableStateFlow; + + sget-object v7, Ljava/lang/Boolean;->TRUE:Ljava/lang/Boolean; + + invoke-interface {v6, v7}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + + sget-boolean v9, Lradiant/StickyLyrics;->enabled:Z + + if-eqz v9, :skip_n + + iget-object v6, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->N:Lkotlinx/coroutines/flow/MutableStateFlow; + + invoke-interface {v6, v7}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + + :skip_n + invoke-interface {v5, v8}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + + :done + return-void +.end method + + +# virtual methods +.method public run()V + .locals 1 + + :try_start + invoke-direct {p0}, Lradiant/RLAPILyricsWorker;->runImpl()V + + :try_end + .catchall {:try_start .. :try_end} :catch_all + + return-void + + :catch_all + move-exception v0 + + return-void +.end method diff --git a/patches/extension/radiant/StickyLyrics.smali b/patches/extension/radiant/StickyLyrics.smali new file mode 100644 index 0000000..89340de --- /dev/null +++ b/patches/extension/radiant/StickyLyrics.smali @@ -0,0 +1,26 @@ +.class public final Lradiant/StickyLyrics; +.super Ljava/lang/Object; + + +# static fields +.field public static volatile enabled:Z + + +# direct methods +.method static constructor ()V + .locals 1 + + const/4 v0, 0x0 + + sput-boolean v0, Lradiant/StickyLyrics;->enabled:Z + + return-void +.end method + +.method private constructor ()V + .locals 0 + + invoke-direct {p0}, Ljava/lang/Object;->()V + + return-void +.end method diff --git a/patches/lyrics-keep-controls-visible.patch b/patches/lyrics-keep-controls-visible.patch new file mode 100644 index 0000000..e6be6b5 --- /dev/null +++ b/patches/lyrics-keep-controls-visible.patch @@ -0,0 +1,15 @@ +--- a/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt$PlayerScreenPortrait$2$1.smali ++++ b/com/tidal/android/feature/playerscreen/ui/PlayerScreenKt$PlayerScreenPortrait$2$1.smali +@@ -228,11 +228,11 @@ + .line 23 + .line 24 + iget-boolean p1, p0, Lcom/tidal/android/feature/playerscreen/ui/PlayerScreenKt$PlayerScreenPortrait$2$1;->$isLyricsVisible:Z + + .line 25 + .line 26 +- if-eqz p1, :cond_3 ++ goto :cond_3 # skip auto-hide branch + + .line 27 + .line 28 + iput v2, p0, Lcom/tidal/android/feature/playerscreen/ui/PlayerScreenKt$PlayerScreenPortrait$2$1;->label:I diff --git a/patches/lyrics-rl-api-observer.patch b/patches/lyrics-rl-api-observer.patch new file mode 100644 index 0000000..53d72ec --- /dev/null +++ b/patches/lyrics-rl-api-observer.patch @@ -0,0 +1,17 @@ +--- a/com/tidal/android/feature/playerscreen/ui/PlayerViewModel$observeLyricsProgress$1$a.smali ++++ b/com/tidal/android/feature/playerscreen/ui/PlayerViewModel$observeLyricsProgress$1$a.smali +@@ -190,7 +190,14 @@ + goto :goto_0 + + .line 67 + :cond_3 ++ sget-boolean v6, Lradiant/RLAPILyricsHook;->isRlState:Z # read RL flag ++ ++ if-eqz v6, :radiant_skip # skip if not RL ++ ++ const/4 v5, -0x1 # RL no-match default = -1 ++ ++ :radiant_skip + if-gez v4, :cond_4 + + .line 68 diff --git a/patches/lyrics-rl-api.patch b/patches/lyrics-rl-api.patch new file mode 100644 index 0000000..bf1582f --- /dev/null +++ b/patches/lyrics-rl-api.patch @@ -0,0 +1,21 @@ +--- a/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali ++++ b/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali +@@ -1277,7 +1277,9 @@ + .line 104 + .line 105 + check-cast v1, Lcom/aspiro/wamp/model/Track; + ++ invoke-static {v0, v1}, Lradiant/RLAPILyricsHook;->onWampTrack(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Lcom/aspiro/wamp/model/Track;)V # RL API lyrics hook ++ + .line 106 + .line 107 + invoke-virtual {v1}, Lcom/aspiro/wamp/model/MediaItem;->getId()I +@@ -1551,7 +1553,7 @@ + .line 235 + .line 236 + .line 237 +- if-nez v9, :cond_a ++ goto :cond_a # skip TIDAL N=false setter + + .line 238 + .line 239 diff --git a/patches/lyrics-sticky-lyrics.patch.disabled b/patches/lyrics-sticky-lyrics.patch.disabled new file mode 100644 index 0000000..bb077db --- /dev/null +++ b/patches/lyrics-sticky-lyrics.patch.disabled @@ -0,0 +1,32 @@ +--- a/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali ++++ b/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali +@@ -1060,9 +1060,13 @@ + move-object/from16 v0, p0 + + .line 2 + .line 3 + move-object/from16 v1, p1 + ++ const/4 v2, 0x1 # true literal ++ ++ sput-boolean v2, Lradiant/StickyLyrics;->enabled:Z # arm sticky flag ++ + .line 4 + .line 5 + invoke-virtual {v0}, Ljava/lang/Object;->getClass()Ljava/lang/Class; +@@ -1568,9 +1572,15 @@ + .line 244 + .line 245 + .line 246 + :cond_a ++ iget-object v9, v0, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->N:Lkotlinx/coroutines/flow/MutableStateFlow; # N flow ++ ++ sget-object v10, Ljava/lang/Boolean;->TRUE:Ljava/lang/Boolean; # TRUE literal ++ ++ invoke-interface {v9, v10}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V # force N=true ++ + iget-object v9, v0, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->K:Lkotlinx/coroutines/flow/MutableStateFlow; + + .line 247 + .line 248 + iget-object v10, v1, Lcom/tidal/android/tidalapi/domain/model/o;->j:Lcom/tidal/android/tidalapi/domain/model/a;