From a8a576c9944b4c3873523f888466251f1c0ffce9 Mon Sep 17 00:00:00 2001 From: meowarex Date: Thu, 16 Jul 2026 23:00:03 +1000 Subject: [PATCH 1/2] Lyrics Button Hotfix <3 --- .../extension/radiant/LyricsReinitGuard.smali | 6 + .../extension/radiant/RLAPILyricsHook.smali | 129 +++++++++++++--- .../extension/radiant/RLAPILyricsWorker.smali | 140 +++++++++++++----- patches/lyrics-keep-controls-visible.patch | 9 +- patches/lyrics-rl-api.patch | 39 +++-- 5 files changed, 255 insertions(+), 68 deletions(-) diff --git a/patches/extension/radiant/LyricsReinitGuard.smali b/patches/extension/radiant/LyricsReinitGuard.smali index 4ed6c37..e20d745 100644 --- a/patches/extension/radiant/LyricsReinitGuard.smali +++ b/patches/extension/radiant/LyricsReinitGuard.smali @@ -8,6 +8,8 @@ # stuff that makes lyrics view stays open.. i forgot what it actually does other than short circuit a call or some shit <3 .field public static lastMediaId:I +.field public static lastVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + # direct methods .method static constructor ()V @@ -17,5 +19,9 @@ sput v0, Lradiant/LyricsReinitGuard;->lastMediaId:I + const/4 v0, 0x0 + + sput-object v0, Lradiant/LyricsReinitGuard;->lastVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + return-void .end method diff --git a/patches/extension/radiant/RLAPILyricsHook.smali b/patches/extension/radiant/RLAPILyricsHook.smali index d7d09c6..af398b6 100644 --- a/patches/extension/radiant/RLAPILyricsHook.smali +++ b/patches/extension/radiant/RLAPILyricsHook.smali @@ -5,8 +5,12 @@ # static fields .field public static volatile currentKey:Ljava/lang/String; +.field public static volatile currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + .field public static volatile isRlState:Z +.field public static volatile keepOpen:Z + # direct methods .method static constructor ()V @@ -16,8 +20,12 @@ sput-object v0, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String; + sput-object v0, Lradiant/RLAPILyricsHook;->currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + sput-boolean v0, Lradiant/RLAPILyricsHook;->isRlState:Z + sput-boolean v0, Lradiant/RLAPILyricsHook;->keepOpen:Z + return-void .end method @@ -60,6 +68,45 @@ return v1 .end method +.method public static isOwner(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;)Z + .locals 2 + + sget-object v0, Lradiant/RLAPILyricsHook;->currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + if-ne p0, v0, :not_owner + + sget-boolean v1, Lradiant/RLAPILyricsHook;->isRlState:Z + + return v1 + + :not_owner + const/4 v1, 0x0 + + return v1 +.end method + +.method public static shouldKeepOpen(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;)Z + .locals 2 + + sget-object v0, Lradiant/RLAPILyricsHook;->currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + if-ne p0, v0, :do_not_keep + + sget-boolean v1, Lradiant/RLAPILyricsHook;->keepOpen:Z + + if-nez v1, :keep + + sget-boolean v1, Lradiant/RLAPILyricsHook;->isRlState:Z + + :keep + return v1 + + :do_not_keep + const/4 v1, 0x0 + + return v1 +.end method + .method public static onWampTrack(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;Lcom/aspiro/wamp/model/Track;)V .locals 11 @@ -80,7 +127,7 @@ invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V - return-void + goto :invalid_track :have_track invoke-virtual {p1}, Lcom/aspiro/wamp/model/MediaItem;->getTitle()Ljava/lang/String; @@ -93,7 +140,7 @@ invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V - return-void + goto :invalid_track :title_present invoke-static {v1}, Lradiant/RLAPILyricsHook;->isBlank(Ljava/lang/String;)Z @@ -106,7 +153,7 @@ invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V - return-void + goto :invalid_track :title_ok invoke-virtual {p1}, Lcom/aspiro/wamp/model/MediaItem;->getArtistNames()Ljava/lang/String; @@ -119,7 +166,7 @@ invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V - return-void + goto :invalid_track :artist_present invoke-static {v2}, Lradiant/RLAPILyricsHook;->isBlank(Ljava/lang/String;)Z @@ -132,24 +179,14 @@ invoke-static {v3}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V - return-void + goto :invalid_track :artist_ok - const-string v3, "" + invoke-virtual {p1}, Lcom/aspiro/wamp/model/MediaItem;->getId()I - new-instance v4, Ljava/lang/StringBuilder; + move-result v3 - 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; + invoke-static {v3}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; move-result-object v4 @@ -161,6 +198,10 @@ if-eqz v5, :rl_new_track + sget-object v5, Lradiant/RLAPILyricsHook;->currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + if-ne p0, v5, :rl_new_track + const-string v5, "onWampTrack: same track re-fired, skipping duplicate fetch" invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V @@ -168,12 +209,51 @@ return-void :rl_new_track + iget-object v5, p0, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->P:Lkotlinx/coroutines/flow/MutableStateFlow; + + invoke-interface {v5}, Lkotlinx/coroutines/flow/MutableStateFlow;->getValue()Ljava/lang/Object; + + move-result-object v5 + + check-cast v5, Ljava/lang/Boolean; + + invoke-virtual {v5}, Ljava/lang/Boolean;->booleanValue()Z + + move-result v5 + + if-nez v5, :store_keep_open + + sget-object v6, Lradiant/RLAPILyricsHook;->currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + if-eqz v6, :store_keep_open + + if-eq p0, v6, :store_keep_open + + iget-object v7, v6, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->P:Lkotlinx/coroutines/flow/MutableStateFlow; + + invoke-interface {v7}, Lkotlinx/coroutines/flow/MutableStateFlow;->getValue()Ljava/lang/Object; + + move-result-object v7 + + check-cast v7, Ljava/lang/Boolean; + + invoke-virtual {v7}, Ljava/lang/Boolean;->booleanValue()Z + + move-result v5 + + :store_keep_open + sput-boolean v5, Lradiant/RLAPILyricsHook;->keepOpen:Z + const/4 v5, 0x0 sput-boolean v5, Lradiant/RLAPILyricsHook;->isRlState:Z sput-object v4, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String; + sput-object p0, Lradiant/RLAPILyricsHook;->currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + move-object v3, v4 + new-instance v5, Ljava/lang/StringBuilder; const-string v6, "onWampTrack: fetching for title='" @@ -222,6 +302,19 @@ invoke-virtual {v6}, Ljava/lang/Thread;->start()V + goto :done + + :invalid_track + const/4 v3, 0x0 + + sput-boolean v3, Lradiant/RLAPILyricsHook;->isRlState:Z + + sput-boolean v3, Lradiant/RLAPILyricsHook;->keepOpen:Z + + sput-object v3, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String; + + sput-object p0, Lradiant/RLAPILyricsHook;->currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + :done return-void .end method diff --git a/patches/extension/radiant/RLAPILyricsWorker.smali b/patches/extension/radiant/RLAPILyricsWorker.smali index acc376a..38a633b 100644 --- a/patches/extension/radiant/RLAPILyricsWorker.smali +++ b/patches/extension/radiant/RLAPILyricsWorker.smali @@ -34,6 +34,86 @@ return-void .end method +.method private failCurrent()V + .locals 4 + + invoke-direct {p0}, Lradiant/RLAPILyricsWorker;->isCurrent()Z + + move-result v0 + + if-eqz v0, :done + + const/4 v0, 0x0 + + sput-object v0, Lradiant/RLAPILyricsHook;->currentKey:Ljava/lang/String; + + sput-boolean v0, Lradiant/RLAPILyricsHook;->isRlState:Z + + sget-boolean v0, Lradiant/StickyLyrics;->enabled:Z + + if-nez v0, :done + + sget-boolean v0, Lradiant/RLAPILyricsHook;->keepOpen:Z + + if-nez v0, :done + + iget-object v1, p0, Lradiant/RLAPILyricsWorker;->vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + iget-object v2, v1, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->P:Lkotlinx/coroutines/flow/MutableStateFlow; + + sget-object v3, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean; + + invoke-interface {v2, v3}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + + :done + return-void +.end method + +.method private isCurrent()Z + .locals 4 + + iget-object v0, p0, Lradiant/RLAPILyricsWorker;->vm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + sget-object v1, Lradiant/RLAPILyricsHook;->currentVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + + if-ne v0, v1, :not_current + + iget-object v1, v0, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->a:Lbi/e; + + invoke-interface {v1}, Lbi/e;->getCurrentItem()Lcom/aspiro/wamp/playqueue/u0; + + move-result-object v1 + + if-eqz v1, :not_current + + invoke-interface {v1}, Lcom/aspiro/wamp/playqueue/u0;->getMediaItem()Lcom/aspiro/wamp/model/MediaItem; + + move-result-object v1 + + if-eqz v1, :not_current + + invoke-virtual {v1}, Lcom/aspiro/wamp/model/MediaItem;->getId()I + + move-result v2 + + invoke-static {v2}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; + + move-result-object v1 + + iget-object v2, p0, Lradiant/RLAPILyricsWorker;->key:Ljava/lang/String; + + invoke-virtual {v2, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z + + move-result v3 + + return v3 + + :not_current + const/4 v3, 0x0 + + return v3 +.end method + .method public static fetch(Ljava/lang/String;Z)Ljava/lang/String; .locals 7 @@ -396,30 +476,7 @@ invoke-static {v4}, 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-eqz v4, :no_close # Smthn needed to keep lyrics open - - 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;->P: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 + goto :request_failed :got_body invoke-static {v3}, Lradiant/RLAPILyricsWorker;->parseLines(Ljava/lang/String;)Ljava/util/ArrayList; @@ -432,7 +489,7 @@ invoke-static {v4}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V - return-void + goto :request_failed :parse_ok invoke-virtual {v3}, Ljava/util/ArrayList;->size()I @@ -445,7 +502,7 @@ invoke-static {v5}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V - return-void + goto :request_failed :nonempty new-instance v5, Ljava/lang/StringBuilder; @@ -466,11 +523,7 @@ 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 + invoke-direct {p0}, Lradiant/RLAPILyricsWorker;->isCurrent()Z move-result v4 @@ -512,6 +565,8 @@ const/4 v6, 0x1 + invoke-interface {v5, v8}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + sput-boolean v6, Lradiant/RLAPILyricsHook;->isRlState:Z iget-object v6, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->K:Lkotlinx/coroutines/flow/MutableStateFlow; @@ -520,25 +575,34 @@ invoke-interface {v6, v7}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + sget-boolean v9, Lradiant/RLAPILyricsHook;->keepOpen:Z + + if-nez v9, :open_lyrics + sget-boolean v9, Lradiant/StickyLyrics;->enabled:Z if-eqz v9, :skip_n + :open_lyrics + iget-object v6, v4, Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;->P: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 + + :request_failed + invoke-direct {p0}, Lradiant/RLAPILyricsWorker;->failCurrent()V + + return-void .end method # virtual methods .method public run()V - .locals 1 + .locals 2 :try_start invoke-direct {p0}, Lradiant/RLAPILyricsWorker;->runImpl()V @@ -551,5 +615,13 @@ :catch_all move-exception v0 + invoke-virtual {v0}, Ljava/lang/Throwable;->toString()Ljava/lang/String; + + move-result-object v1 + + invoke-static {v1}, Lradiant/RLAPILyricsHook;->dlog(Ljava/lang/String;)V + + invoke-direct {p0}, Lradiant/RLAPILyricsWorker;->failCurrent()V + return-void .end method diff --git a/patches/lyrics-keep-controls-visible.patch b/patches/lyrics-keep-controls-visible.patch index 96e0be5..3f8d8b3 100644 --- a/patches/lyrics-keep-controls-visible.patch +++ b/patches/lyrics-keep-controls-visible.patch @@ -11,13 +11,18 @@ .line 28 --- a/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali +++ b/com/tidal/android/feature/playerscreen/ui/PlayerViewModel.smali -@@ -1382,6 +1382,14 @@ +@@ -1382,6 +1382,19 @@ .line 109 .line 110 move-result v1 + sget v4, Lradiant/LyricsReinitGuard;->lastMediaId:I -+ if-eq v1, v4, :rl_skip_reinit # same track re-emitted -> skip the redundant re-init (avoids the loading-state flash) ++ if-ne v1, v4, :rl_reinit_store ++ sget-object v4, Lradiant/LyricsReinitGuard;->lastVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; ++ if-ne v0, v4, :rl_reinit_store ++ goto :rl_skip_reinit # only suppress duplicate initialization on the same ViewModel ++ :rl_reinit_store + sput v1, Lradiant/LyricsReinitGuard;->lastMediaId:I ++ sput-object v0, Lradiant/LyricsReinitGuard;->lastVm:Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel; + goto :rl_reinit_ok + :rl_skip_reinit + sget-object v4, Lkotlin/u;->a:Lkotlin/u; diff --git a/patches/lyrics-rl-api.patch b/patches/lyrics-rl-api.patch index 719f7e9..e0f5c8a 100644 --- a/patches/lyrics-rl-api.patch +++ b/patches/lyrics-rl-api.patch @@ -9,43 +9,53 @@ .line 106 .line 107 invoke-virtual {v1}, Lcom/aspiro/wamp/model/MediaItem;->getId()I -@@ -1655,7 +1657,7 @@ +@@ -1655,7 +1657,11 @@ .line 239 .line 240 .line 241 - if-nez v9, :cond_a -+ goto :cond_a # skip TIDAL P=false in the has-lyrics path (for some reason) ++ invoke-static {v0}, Lradiant/RLAPILyricsHook;->shouldKeepOpen(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;)Z ++ move-result v20 ++ if-nez v20, :cond_a # keep visibility only for RL state owned by this ViewModel ++ :rl_native_visibility ++ if-nez v9, :cond_a .line 242 .line 243 -@@ -2347,7 +2349,10 @@ +@@ -2347,7 +2353,12 @@ .line 572 :goto_c -+ sget-boolean v5, Lradiant/RLAPILyricsHook;->isRlState:Z -+ if-nez v5, :rl_keep_n2 # RL active -> keep worker content ++ invoke-static {v0}, Lradiant/RLAPILyricsHook;->isOwner(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;)Z ++ move-result v20 ++ if-nez v20, :rl_keep_n2 # only preserve RL content owned by this ViewModel ++ :rl_set_n2 invoke-interface {v9, v1}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + :rl_keep_n2 .line 573 .line 574 -@@ -2396,7 +2401,10 @@ +@@ -2396,7 +2407,12 @@ .line 595 .line 596 -+ sget-boolean v7, Lradiant/RLAPILyricsHook;->isRlState:Z -+ if-nez v7, :rl_keep_o # RL active -> keep view-open flag ++ invoke-static {v0}, Lradiant/RLAPILyricsHook;->shouldKeepOpen(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;)Z ++ move-result v20 ++ if-nez v20, :rl_keep_o # only preserve visibility owned by this ViewModel ++ :rl_set_o invoke-interface {v1, v5}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + :rl_keep_o .line 597 .line 598 -@@ -2425,7 +2433,10 @@ +@@ -2425,7 +2441,12 @@ .line 610 .line 611 -+ sget-boolean v7, Lradiant/RLAPILyricsHook;->isRlState:Z -+ if-nez v7, :rl_keep_n # RL active -> keep worker content ++ invoke-static {v0}, Lradiant/RLAPILyricsHook;->isOwner(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;)Z ++ move-result v20 ++ if-nez v20, :rl_keep_n # only preserve RL content owned by this ViewModel ++ :rl_set_n invoke-interface {v1, v6}, Lkotlinx/coroutines/flow/MutableStateFlow;->setValue(Ljava/lang/Object;)V + :rl_keep_n @@ -53,12 +63,13 @@ .line 613 --- a/com/tidal/android/feature/playerscreen/ui/PlayerViewModel$special$$inlined$mapNotNull$1$2.smali +++ b/com/tidal/android/feature/playerscreen/ui/PlayerViewModel$special$$inlined$mapNotNull$1$2.smali -@@ -2256,6 +2256,10 @@ +@@ -2256,6 +2256,11 @@ .line 1040 .line 1041 move-result v34 -+ sget-boolean v4, Lradiant/RLAPILyricsHook;->isRlState:Z -+ if-eqz v4, :rl_hl_done # not RL = use TIDAL's hasLyrics ++ invoke-static {v1}, Lradiant/RLAPILyricsHook;->isOwner(Lcom/tidal/android/feature/playerscreen/ui/PlayerViewModel;)Z ++ move-result v4 ++ if-eqz v4, :rl_hl_done # ignore non-RL state or state owned by an obsolete ViewModel + const/16 v34, 0x1 # RL lyrics active = force hasLyrics so the sparkle stays + :rl_hl_done From 6a5771cf9e668a23f1b7418ba3b6dd8559348eeb Mon Sep 17 00:00:00 2001 From: meowarex Date: Fri, 17 Jul 2026 00:00:49 +1000 Subject: [PATCH 2/2] Readme changes <3 --- README.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7466008..2e985e1 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,24 @@ A Manager app for Android that patches TIDAL to bring the Radiant Lyrics experience to mobile. -## What it does +## What it does? -- Blurred album art background in the player -- Progress pill overlay in lyrics mode - restored from old UI -- Hides the ugly cover art when in lyrics mode +This project adds ALLOT of completely Optional & Customisable Patches/Integrations to TIDAL on Android (iOS SoonTM). +Every Patch & Integration can be disabled/enabled & adjusted exactly how you want TIDAL to feel/look <3 + +### Patches: + +- Insane Customisation of the Player Backdrop +- Higher quality lyrics from more providers (WIP) +- Progress pill overlay in lyrics mode (restored from old UI) +- Keeps lyrics controls visible +- One-handed player controls +- Redesigned mini-player with gestures - Syllable Level lyrics (SoonTM) +### Integrations + +- Waze | Control TIDAL playback from Waze ### Disclaimer @@ -18,7 +29,10 @@ This project is not affiliated with TIDAL in any way. Use at your own risk. ## Installation ### Batteries <3 -- Android 10.0+ & >340 DPI +- Phone of some unknown description +- The smallest amount of brain damage +- Android 7.0+ & >340 DPI +- Internet - Paid TIDAL Account ### Steps