Crossfade & Animated Covers

This commit is contained in:
meowarex committed 2026-10-11 18:18:12 +00:00
1 parent d47693ccaa
commit f5cd48be5a
158 files changed
+7211 -1855

No files matched your search

+12 -12
View File
@@ -24,7 +24,7 @@ import androidx.compose.ui.platform.AndroidCompositionLocals_androidKt;
import coil.request.CachePolicy;
import com.tidal.android.feature.playerscreen.ui.composables.p3;
import com.tidal.android.feature.playerscreen.ui.composables.e3;
import com.tidal.android.feature.playerscreen.ui.model.PlayerBackgroundStyle;
import com.tidal.android.image.coil.base.CoilImageLoader;
import com.tidal.android.image.core.b; // MARKER: R8 core.b
@@ -41,7 +41,7 @@ import java.util.concurrent.Executors;
* (cloned at tools/kawarp-agsl) and is compiled into this bundle by tools/kawarp-build/build.sh;
* this class only holds what is TIDAL/Compose-specific
*/
public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallback, b0.c { // MARKER: R8 am0.l b0.c
public final class Kawarp implements yl0.l, Runnable, Choreographer.FrameCallback, c0.c { // MARKER: R8 yl0.l c0.c
/** Stop the frame loop once nothing has drawn for this long (player left / backgrounded). */
private static final long IDLE_STOP_MS = 500L;
@@ -61,7 +61,7 @@ public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallbac
* would allocate an Integer per frame for nothing.
*/
private static final MutableIntState frameState = SnapshotIntStateKt.mutableIntStateOf(0);
/** Flips true on any shader failure; read during composition so we recompose onto p3.a. */
/** Flips true on any shader failure; read during composition so we recompose onto e3.a. */
private static final MutableState fallbackState =
SnapshotStateKt.mutableStateOf$default(Boolean.FALSE, null, 2, null);
@@ -70,7 +70,7 @@ public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallbac
private static KawarpEngine engine;
/** TIDAL's application-scoped Coil loader and the context its requests are built with. */
private static volatile coil.f loader; // MARKER: R8 coil.f
private static volatile coil.e loader; // MARKER: R8 coil.e
private static volatile Context appContext;
private static volatile String requestedUuid;
private static volatile int loadToken;
@@ -95,11 +95,11 @@ public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallbac
/**
* Drop-in replacement for TIDAL's PlayerBackground composable (p3.b) - the patch redirects
* Drop-in replacement for TIDAL's PlayerBackground composable (e3.b) - the patch redirects
* the call site here so the whole backdrop works <3
*/
public static void b(PlayerBackgroundStyle style, long color, int albumId, String coverUuid,
boolean isPlaying, boolean isSeeking, am0.a progress, Modifier modifier, // MARKER: R8 am0.a
boolean isPlaying, boolean isSeeking, yl0.a progress, Modifier modifier, // MARKER: R8 yl0.a
Composer composer, int changed) {
render(albumId, coverUuid, isPlaying, modifier, composer);
}
@@ -112,7 +112,7 @@ public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallbac
boolean ok = !((Boolean) fallbackState.getValue()).booleanValue() && ensureEngine();
if (!ok) {
// No (working) AGSL here - fall back to TIDAL's own blurred backdrop.
p3.a(albumId, coverUuid, isPlaying, false, null, modifier, composer, 0); // MARKER: R8 p3.a
e3.a(albumId, coverUuid, isPlaying, false, null, modifier, composer, 0); // MARKER: R8 e3.a
} else {
engine.setPlaying(isPlaying);
request(albumId, coverUuid);
@@ -131,8 +131,8 @@ public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallbac
Object local = composer.consume(AndroidCompositionLocals_androidKt.getLocalContext());
if (!(local instanceof Context)) return;
Context app = ((Context) local).getApplicationContext();
if (!(app instanceof ce0.b.a)) return; // MARKER: R8 ce0.b.a
Object tidalLoader = ((ce0.b.a) app).a().a(); // MARKER: R8 ce0.b.a
if (!(app instanceof ud0.b.a)) return; // MARKER: R8 ud0.b.a
Object tidalLoader = ((ud0.b.a) app).a().a(); // MARKER: R8 ud0.b.a
if (!(tidalLoader instanceof CoilImageLoader)) return;
appContext = app;
loader = ((CoilImageLoader) tidalLoader).a;
@@ -181,7 +181,7 @@ public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallbac
*/
public void run() {
try {
xd0.g tidal = new xd0.g(appContext, new b.a(String.valueOf(albumId), uuid), // MARKER: R8 xd0.g b.a
pd0.g tidal = new pd0.g(appContext, new b.a(String.valueOf(albumId), uuid), // MARKER: R8 pd0.g b.a
null, null, false, null, null, false);
coil.request.h.a req = coil.request.h.a(com.tidal.android.image.coil.c.a(tidal, null)); // MARKER: R8 h.a coil.c.a
coil.request.b d = req.b; // MARKER: R8 req.b
@@ -207,7 +207,7 @@ public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallbac
public void b(Drawable placeholder) {}
/** Target.onError - not in any of TIDAL's stores. */
public void d(Drawable error) { fail(); }
public void c(Drawable error) { fail(); }
private void fail() {
if (token == loadToken) requestedUuid = null;
@@ -258,7 +258,7 @@ public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallbac
engine.draw(AndroidCanvas_androidKt.getNativeCanvas(scope.getDrawContext().getCanvas()),
width, height);
} catch (Throwable t) {
fallbackState.setValue(Boolean.TRUE); // AGSL draw failed: recompose onto p3.a // MARKER: R8 p3.a
fallbackState.setValue(Boolean.TRUE); // AGSL draw failed: recompose onto e3.a // MARKER: R8 e3.a
}
}
}
@@ -1,5 +1,5 @@
package androidx.compose.ui.draw;
import androidx.compose.ui.Modifier;
public final class DrawModifierKt {
public static Modifier drawBehind(Modifier modifier, am0.l onDraw) { return null; } // MARKER: R8 am0.l
public static Modifier drawBehind(Modifier modifier, yl0.l onDraw) { return null; } // MARKER: R8 yl0.l
}
-9
View File
@@ -1,9 +0,0 @@
package b0;
// MARKER: R8 whole class: TIDAL dex FQN is `b0.c` - both package and name roll
import android.graphics.drawable.Drawable;
/** coil.target.Target (R8 name in TIDAL 2.200): a=onSuccess, b=onStart, d=onError. */
public interface c {
void a(Drawable success);
void b(Drawable start);
void d(Drawable error);
}
+9
View File
@@ -0,0 +1,9 @@
package c0;
// MARKER: R8 whole class: TIDAL dex FQN is `c0.c` - both package and name roll
import android.graphics.drawable.Drawable;
/** coil.target.Target (R8 name in TIDAL 2.213): a=onSuccess, b=onStart, c=onError. */
public interface c {
void a(Drawable success);
void b(Drawable start);
void c(Drawable error);
}
@@ -1,6 +1,6 @@
package coil;
// MARKER: R8 whole class: TIDAL dex FQN is `coil.f` - both package and name roll
// MARKER: R8 whole class: TIDAL dex FQN is `coil.e` - both package and name roll
/** coil.ImageLoader; b() is enqueue(). */
public interface f {
public interface e {
coil.request.d b(coil.request.h request);
}
+2 -2
View File
@@ -9,12 +9,12 @@ import kotlinx.coroutines.CoroutineDispatcher;
*/
public final class b {
public CoroutineDispatcher a, b, c, d;
public d0.c.a e; // MARKER: R8 d0.c.a
public e0.c.a e; // MARKER: R8 e0.c.a
public coil.size.Precision f;
public android.graphics.Bitmap.Config g;
public boolean h;
public CachePolicy i, j, k;
public b(CoroutineDispatcher a, CoroutineDispatcher b, CoroutineDispatcher c, CoroutineDispatcher d,
d0.c.a transition, coil.size.Precision precision, android.graphics.Bitmap.Config config, // MARKER: R8 d0.c.a
e0.c.a transition, coil.size.Precision precision, android.graphics.Bitmap.Config config, // MARKER: R8 e0.c.a
boolean h, CachePolicy memory, CachePolicy disk, CachePolicy network) {}
}
+1 -1
View File
@@ -7,7 +7,7 @@ public final class h {
/** defaults block the built request copies its cache policies from */
public b b;
/** target */
public b0.c d; // MARKER: R8 b0.c
public c0.c d; // MARKER: R8 c0.c
public h a() { return null; }
}
}
@@ -1,9 +1,9 @@
package com.tidal.android.feature.playerscreen.ui.composables;
// MARKER: R8 whole class: TIDAL dex FQN is `com.tidal.android.feature.playerscreen.ui.composables.p3` - both package and name roll
// MARKER: R8 whole class: TIDAL dex FQN is `com.tidal.android.feature.playerscreen.ui.composables.e3` - both package and name roll
import androidx.compose.runtime.Composer;
import androidx.compose.ui.Modifier;
/** TIDAL's DarkBlurBackground composable (used as the pre-Android-13 fallback). */
public final class p3 {
public final class e3 {
public static void a(int albumId, String coverUuid, boolean isPlaying, boolean isSeeking,
am0.a progress, Modifier modifier, Composer composer, int changed) {} // MARKER: R8 am0.a
yl0.a progress, Modifier modifier, Composer composer, int changed) {} // MARKER: R8 yl0.a
}
@@ -1,5 +1,5 @@
package com.tidal.android.image.coil.base;
/** TIDAL's xd0.e implementation; a is the underlying Coil ImageLoader. */
public final class CoilImageLoader implements xd0.e { // MARKER: R8 xd0.e
public final coil.f a = null; // MARKER: R8 coil.f
/** TIDAL's pd0.e implementation; a is the underlying Coil ImageLoader. */
public final class CoilImageLoader implements pd0.e { // MARKER: R8 pd0.e
public final coil.e a = null; // MARKER: R8 coil.e
}
@@ -2,5 +2,5 @@ package com.tidal.android.image.coil;
// MARKER: R8 whole class: TIDAL dex FQN is `com.tidal.android.image.coil.c` - both package and name roll
/** TIDAL request -> Coil request; installs the AlbumCover data the interceptor resolves. */
public final class c {
public static coil.request.h a(xd0.g request, androidx.compose.ui.layout.ContentScale scale) { return null; } // MARKER: R8 xd0.g
public static coil.request.h a(pd0.g request, androidx.compose.ui.layout.ContentScale scale) { return null; } // MARKER: R8 pd0.g
}
@@ -1,4 +1,4 @@
package d0;
// MARKER: R8 whole class: TIDAL dex FQN is `d0.c` - both package and name roll
package e0;
// MARKER: R8 whole class: TIDAL dex FQN is `e0.c` - both package and name roll
/** coil.transition.Transition; c.a is Transition.Factory. */
public interface c { interface a {} }
@@ -1,4 +1,4 @@
package xd0;
// MARKER: R8 whole class: TIDAL dex FQN is `xd0.e` - both package and name roll
package pd0;
// MARKER: R8 whole class: TIDAL dex FQN is `pd0.e` - both package and name roll
/** TIDAL's ImageLoader interface (implemented by CoilImageLoader). */
public interface e {}
+4
View File
@@ -0,0 +1,4 @@
package pd0;
// MARKER: R8 whole class: TIDAL dex FQN is `pd0.f` - both package and name roll
/** Size spec wrapper accepted by pd0.g's constructor; we always pass null. */
public final class f {}
@@ -1,5 +1,5 @@
package xd0;
// MARKER: R8 whole class: TIDAL dex FQN is `xd0.g` - both package and name roll
package pd0;
// MARKER: R8 whole class: TIDAL dex FQN is `pd0.g` - both package and name roll
import android.content.Context;
import com.tidal.android.image.core.b; // MARKER: R8 core.b
/** TIDAL's ImageRequest. */
@@ -1,7 +1,7 @@
package ce0;
// MARKER: R8 whole class: TIDAL dex FQN is `ce0.b` - both package and name roll
package ud0;
// MARKER: R8 whole class: TIDAL dex FQN is `ud0.b` - both package and name roll
/** Dagger image component; the Application implements b.a and hands out the loader. */
public interface b {
xd0.e a(); // MARKER: R8 xd0.e
pd0.e a(); // MARKER: R8 pd0.e
interface a { b a(); }
}
-4
View File
@@ -1,4 +0,0 @@
package xd0;
// MARKER: R8 whole class: TIDAL dex FQN is `xd0.f` - both package and name roll
/** Size spec wrapper accepted by xd0.g's constructor; we always pass null. */
public final class f {}
@@ -1,4 +1,4 @@
package am0;
// MARKER: R8 whole class: TIDAL dex FQN is `am0.a` - both package and name roll
package yl0;
// MARKER: R8 whole class: TIDAL dex FQN is `yl0.a` - both package and name roll
/** kotlin.jvm.functions.Function0 (obfuscated in the TIDAL dex). */
public interface a { Object invoke(); }
@@ -1,4 +1,4 @@
package am0;
// MARKER: R8 whole class: TIDAL dex FQN is `am0.l` - both package and name roll
package yl0;
// MARKER: R8 whole class: TIDAL dex FQN is `yl0.l` - both package and name roll
/** kotlin.jvm.functions.Function1 (obfuscated in the TIDAL dex). */
public interface l { Object invoke(Object p1); }
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# Builds the RL extension classes into smali
#
# ./build.sh # rebuild extension smali
#
# Java 8, no lambdas, no nested classes
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo="$(cd "$here/../.." && pwd)"
JAVA_HOME="${JAVA_HOME:-/home/meow/toolchain/jdk-21.0.12+8}"
ANDROID_SDK="${ANDROID_SDK:-/home/meow/toolchain/android-sdk}"
ANDROID_JAR="${ANDROID_JAR:-$ANDROID_SDK/platforms/android-36/android.jar}"
D8="${D8:-$(ls -d "$ANDROID_SDK"/build-tools/*/d8 | sort | tail -1)}"
BAKSMALI="${BAKSMALI:-/home/meow/tools/baksmali.jar}"
export PATH="$JAVA_HOME/bin:$PATH"
javac="$JAVA_HOME/bin/javac"
java="$JAVA_HOME/bin/java"
out="$here/build"
rm -rf "$out"
mkdir -p "$out/stubs" "$out/classes" "$out/dex" "$out/smali"
echo "==> javac stubs"
find "$here/stubs" -name '*.java' > "$out/stubs.list"
"$javac" -nowarn -g:none --release 8 -cp "$ANDROID_JAR" -d "$out/stubs" @"$out/stubs.list" 2>&1 |
grep -v 'bootstrap class path\|source value 8\|target value 8\|deprecat\|^[0-9] warning' || true
echo "==> javac sources"
find "$here/src" -name '*.java' > "$out/src.list"
"$javac" -nowarn -g:none --release 8 -cp "$ANDROID_JAR:$out/stubs" -d "$out/classes" @"$out/src.list" 2>&1 |
grep -v 'bootstrap class path\|source value 8\|target value 8\|deprecat\|^[0-9] warning' || true
echo "==> d8"
find "$out/classes" -name '*.class' > "$out/class.list"
"$D8" --min-api 24 --no-desugaring --lib "$ANDROID_JAR" --classpath "$out/stubs" \
--output "$out/dex" @"$out/class.list"
echo "==> baksmali"
"$java" -jar "$BAKSMALI" d "$out/dex/classes.dex" -o "$out/smali"
echo "==> post-process"
find "$out/smali" -name '*.smali' -print0 | xargs -0 sed -i \
-e 's/getSizeNHjbRc/getSize-NH-jbRc/g' \
-e 's/getIntrinsicSizeNHjbRc/getIntrinsicSize-NH-jbRc/g' \
-e 's/drawxKDEd0/draw-x_KDEd0/g' \
-e 's/getWidthImpl/getWidth-impl/g' \
-e 's/getHeightImpl/getHeight-impl/g'
if grep -rl 'invoke-custom\|access\$' "$out/smali" >/dev/null; then
echo "!! invoke-custom / access\$ bridge in output - keep it to one class, no lambdas" >&2
exit 1
fi
dest="$repo/patches/extension/radiant"
for c in AiSparkle AnimatedCovers AnimatedCoverView RoundOutline SparkleIcons; do
rm -f "$dest/$c".smali
cp "$out"/smali/radiant/"$c".smali "$dest/"
echo " -> patches/extension/radiant/$c.smali"
done
@@ -0,0 +1,155 @@
package radiant;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.view.Choreographer;
import androidx.compose.runtime.Composer;
import androidx.compose.runtime.MutableState;
import androidx.compose.runtime.SnapshotStateKt;
import androidx.compose.ui.geometry.Size;
import androidx.compose.ui.graphics.AndroidCanvas_androidKt;
import androidx.compose.ui.graphics.ColorFilter;
import androidx.compose.ui.graphics.drawscope.DrawScope;
import androidx.compose.ui.graphics.painter.Painter;
import org.json.JSONObject;
// Rainbow sparkle when syllables are AI generated
public final class AiSparkle extends Painter implements Choreographer.FrameCallback, Runnable {
private static final String TAG = "RLAiSparkle";
// Same colours as desktop
private static final int[] PALETTE = {0xFFFF6B9D, 0xFFFFD166, 0xFF5EE7FF, 0xFFA78BFA};
private static final long CYCLE_MS = 6000L;
private static final float[] STOPS = {0f, 0.35f, 0.65f, 1f};
// Current lyrics are AI generated
static final MutableState aiState = SnapshotStateKt.mutableStateOf$default(Boolean.FALSE, null, 2, null);
// Frame tick for the animation
static final MutableState tick = SnapshotStateKt.mutableStateOf$default(Long.valueOf(0L), null, 2, null);
private static final AiSparkle POSTER = new AiSparkle(null);
private static volatile boolean pending;
private static AiSparkle cached;
private static boolean frameScheduled;
private static long lastDrawMs;
private final Painter base;
private final Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int[] colors = new int[4];
private AiSparkle(Painter base) {
this.base = base;
fillPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
}
// Check the API response for AI syllables
public static void onResponse(String body) {
boolean ai = false;
try {
JSONObject o = new JSONObject(body);
ai = "Word".equals(o.optString("type", "")) && o.optBoolean("_synthesized", false);
} catch (Throwable ignored) {
}
post(ai);
}
// New lookup, clear it
public static void reset() {
post(false);
}
// Use the rainbow painter while AI
public static Painter wrap(Painter painter, Composer composer) {
if (!((Boolean) aiState.getValue()).booleanValue() || painter == null) return painter;
AiSparkle c = cached;
if (c == null || c.base != painter) {
c = new AiSparkle(painter);
cached = c;
}
return c;
}
private static void post(boolean ai) {
pending = ai;
new Handler(Looper.getMainLooper()).post(POSTER);
}
@Override
public void run() {
Boolean v = Boolean.valueOf(pending);
if (!v.equals(aiState.getValue())) aiState.setValue(v);
}
@Override
public long getIntrinsicSizeNHjbRc() {
return base.getIntrinsicSizeNHjbRc();
}
// Ignore the button tint
@Override
public boolean applyColorFilter(ColorFilter colorFilter) {
return true;
}
@Override
public void onDraw(DrawScope scope) {
tick.getValue(); // redraw every frame
long now = SystemClock.uptimeMillis();
lastDrawMs = now;
if (!frameScheduled) {
frameScheduled = true;
Choreographer.getInstance().postFrameCallback(POSTER);
}
long size = scope.getSizeNHjbRc();
float w = Size.getWidthImpl(size);
float h = Size.getHeightImpl(size);
if (w <= 0f || h <= 0f) return;
Canvas canvas = AndroidCanvas_androidKt.getNativeCanvas(scope.getDrawContext().getCanvas());
float phase = (now % CYCLE_MS) / (float) CYCLE_MS;
// Paint the gradient into the icon
int save = canvas.saveLayer(0f, 0f, w, h, null);
base.drawxKDEd0(scope, size, 1f, null);
for (int i = 0; i < 4; i++) colors[i] = lerpPalette(phase + i * 0.25f);
fillPaint.setShader(new LinearGradient(0f, h, w, 0f, colors, STOPS, Shader.TileMode.CLAMP));
canvas.drawRect(0f, 0f, w, h, fillPaint);
canvas.restoreToCount(save);
}
@Override
public void doFrame(long frameTimeNanos) {
long now = SystemClock.uptimeMillis();
if (((Boolean) aiState.getValue()).booleanValue() && now - lastDrawMs < 500L) {
tick.setValue(Long.valueOf(now));
Choreographer.getInstance().postFrameCallback(POSTER);
} else {
frameScheduled = false;
}
}
// Blend between palette colours
private static int lerpPalette(float phase) {
float p = phase - (float) Math.floor(phase);
float x = p * 4f;
int i = (int) x;
float f = x - i;
int a = PALETTE[i & 3];
int b = PALETTE[(i + 1) & 3];
int ar = (a >> 16) & 0xFF, ag = (a >> 8) & 0xFF, ab = a & 0xFF;
int br = (b >> 16) & 0xFF, bg = (b >> 8) & 0xFF, bb = b & 0xFF;
int rr = (int) (ar + (br - ar) * f);
int gg = (int) (ag + (bg - ag) * f);
int bl = (int) (ab + (bb - ab) * f);
return 0xFF000000 | (rr << 16) | (gg << 8) | bl;
}
}
@@ -0,0 +1,253 @@
package radiant;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.Iterator;
import java.lang.ref.WeakReference;
// Animated cover over the lyrics pill cover
public final class AnimatedCoverView extends TextureView implements TextureView.SurfaceTextureListener,
MediaPlayer.OnPreparedListener, MediaPlayer.OnInfoListener, MediaPlayer.OnErrorListener,
View.OnLayoutChangeListener, View.OnAttachStateChangeListener, Runnable {
private static final ArrayList<WeakReference<AnimatedCoverView>> live = new ArrayList<WeakReference<AnimatedCoverView>>();
private final ImageView target;
private final ViewGroup host;
private int albumId = -1;
private String url;
private MediaPlayer player;
private boolean outlined;
private Surface surface;
private AnimatedCoverView(ImageView target, ViewGroup host) {
super(target.getContext());
this.target = target;
this.host = host;
setOpaque(false);
setAlpha(0f);
setVisibility(View.INVISIBLE);
setSurfaceTextureListener(this);
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
// Pill cover was loaded
public static void attach(ImageView image, int albumId) {
try {
if (image == null) return;
AnimatedCoverView v = find(image);
if (v == null) {
// Use the CardView, ConstraintSet crashes otherwise
ViewGroup host = frameAncestor(image);
if (host == null) return;
v = new AnimatedCoverView(image, host);
v.setId(View.generateViewId());
host.addView(v, new ViewGroup.LayoutParams(Math.max(1, image.getWidth()), Math.max(1, image.getHeight())));
image.addOnLayoutChangeListener(v);
image.addOnAttachStateChangeListener(v);
if (image.getParent() instanceof View && image.getParent() != host) {
((View) image.getParent()).addOnLayoutChangeListener(v);
}
live.add(new WeakReference<AnimatedCoverView>(v));
v.sync();
}
v.albumId = albumId;
v.refresh();
} catch (Throwable ignored) {
}
}
// New cover URL, update every overlay
static void refreshAll() {
try {
Iterator<WeakReference<AnimatedCoverView>> it = live.iterator();
while (it.hasNext()) {
AnimatedCoverView v = it.next().get();
if (v == null) it.remove();
else v.refresh();
}
} catch (Throwable ignored) {
}
}
private static ViewGroup frameAncestor(View view) {
ViewParent p = view.getParent();
while (p instanceof ViewGroup) {
if (p instanceof android.widget.FrameLayout) return (ViewGroup) p;
p = p.getParent();
}
return null;
}
private static AnimatedCoverView find(ImageView image) {
Iterator<WeakReference<AnimatedCoverView>> it = live.iterator();
while (it.hasNext()) {
AnimatedCoverView v = it.next().get();
if (v == null) it.remove();
else if (v.target == image) return v;
}
return null;
}
private void refresh() {
String u = AnimatedCovers.urlFor(albumId);
if (u == null ? url == null : u.equals(url)) return;
release();
url = u;
if (u == null) {
setAlpha(0f);
setVisibility(View.INVISIBLE);
return;
}
setAlpha(0f);
setVisibility(View.VISIBLE);
start();
}
private void start() {
if (url == null || surface == null || player != null) return;
try {
MediaPlayer mp = new MediaPlayer();
player = mp;
mp.setSurface(surface);
mp.setOnPreparedListener(this);
mp.setOnInfoListener(this);
mp.setOnErrorListener(this);
mp.setLooping(true);
mp.setVolume(0f, 0f);
mp.setDataSource(url);
mp.prepareAsync();
} catch (Throwable t) {
release();
}
}
private void release() {
MediaPlayer mp = player;
player = null;
if (mp != null) {
try {
mp.release();
} catch (Throwable ignored) {
}
}
}
// Line up with the cover
private void sync() {
ViewGroup.LayoutParams lp = getLayoutParams();
int w = Math.max(1, target.getWidth());
int h = Math.max(1, target.getHeight());
if (lp != null && (lp.width != w || lp.height != h)) {
lp.width = w;
lp.height = h;
setLayoutParams(lp);
}
// Cover position inside the card
float x = 0f, y = 0f;
View v = target;
while (v != null && v != host) {
x += v.getLeft() + v.getTranslationX();
y += v.getTop() + v.getTranslationY();
ViewParent p = v.getParent();
v = p instanceof View ? (View) p : null;
}
if (v == null) return;
setTranslationX(x - getLeft());
setTranslationY(y - getTop());
if (!outlined) {
// Same corner radius as the cover
outlined = true;
float r = 0f;
try {
r = getResources().getDimension(com.aspiro.wamp.R.dimen.image_header_corner_radius);
} catch (Throwable ignored) {
}
setOutlineProvider(new RoundOutline(r));
setClipToOutline(true);
}
if (target.getVisibility() != View.VISIBLE && getVisibility() == View.VISIBLE) setVisibility(View.INVISIBLE);
}
// Re-align after layout
@Override
public void run() {
try {
sync();
} catch (Throwable ignored) {
}
}
@Override
public void onLayoutChange(View v, int l, int t, int r, int b, int ol, int ot, int or, int ob) {
post(this);
}
@Override
public void onViewAttachedToWindow(View v) {
try {
refresh();
} catch (Throwable ignored) {
}
}
@Override
public void onViewDetachedFromWindow(View v) {
release();
url = null;
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture st, int w, int h) {
surface = new Surface(st);
start();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture st, int w, int h) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture st) {
release();
url = null;
if (surface != null) surface.release();
surface = null;
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture st) {
}
@Override
public void onPrepared(MediaPlayer mp) {
if (mp == player) mp.start();
}
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (mp == player && what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
animate().alpha(1f).setDuration(300L).start();
}
return false;
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
if (mp == player) {
release();
setAlpha(0f);
setVisibility(View.INVISIBLE);
}
return true;
}
}
@@ -0,0 +1,267 @@
package radiant;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.compose.foundation.layout.SizeKt;
import androidx.compose.runtime.Composer;
import androidx.compose.runtime.MutableState;
import androidx.compose.runtime.SnapshotStateKt;
import androidx.compose.ui.Modifier;
import androidx.compose.ui.graphics.ColorFilter;
import androidx.compose.ui.layout.ContentScale;
import com.tidal.android.core.compose.components.l;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
// Animated covers, played with TIDAL's own AnimatedCover
public final class AnimatedCovers implements Runnable, yl0.p { // MARKER: R8 yl0.p
private static final String TAG = "RLAnimatedCovers";
private static final String SEARCH = "https://ama.trainswift.net/api/v1/artwork/search?";
private static final int TARGET_PX = 700;
private static final int CACHE_MAX = 200;
// Animated cover URL for the playing track
static final MutableState urlState = SnapshotStateKt.mutableStateOf$default(null, null, 2, null);
// Album the URL belongs to
static final MutableState albumState = SnapshotStateKt.mutableStateOf$default(Integer.valueOf(-1), null, 2, null);
private static final LinkedHashMap<String, String> cache = new LinkedHashMap<String, String>(64, 0.75f, true);
private static volatile String currentKey;
// Play queue, for the album name
private static volatile Object queue;
private static final int MODE_LOOKUP = 0;
private static final int MODE_PUBLISH = 1;
private static final int MODE_PLACEHOLDER = 2;
private final int mode;
// lookup
private String key, title, artist, album, url;
private int albumId;
// normal cover under the video
private yl0.l request; // MARKER: R8 yl0.l
private String description;
private ColorFilter colorFilter;
private ContentScale contentScale;
private Object placeholder;
private yl0.a onError; // MARKER: R8 yl0.a
private int defaults;
private AnimatedCovers(int mode) {
this.mode = mode;
}
// Keep the play queue
public static void setQueue(Object playQueue) {
queue = playQueue;
}
// Track changed, look it up
public static void onMediaState(String title, String artist, int albumId, String cover) {
if (title == null || artist == null || title.isEmpty()) return;
String key = artist + "\u0000" + title;
if (key.equals(currentKey)) return;
currentKey = key;
publish(key, null, albumId);
String hit;
synchronized (cache) {
hit = cache.get(key);
}
if (hit != null) {
publish(key, hit.isEmpty() ? null : hit, albumId);
return;
}
AnimatedCovers job = new AnimatedCovers(MODE_LOOKUP);
job.key = key;
job.title = title;
job.artist = artist;
job.album = currentAlbum(title);
job.albumId = albumId;
Thread t = new Thread(job, "RL-AnimatedCovers");
t.setDaemon(true);
t.start();
}
// Animated cover if we have one, else TIDAL's image
public static void cover(yl0.l request, String description, Modifier modifier, ColorFilter colorFilter, // MARKER: R8 yl0.l
ContentScale contentScale, Object placeholder, yl0.a onError, // MARKER: R8 yl0.a
Composer composer, int changed, int defaults, int albumId) {
String u = (String) urlState.getValue();
int playing = ((Integer) albumState.getValue()).intValue();
if (u != null && playing == albumId) {
composer.startReplaceGroup(0x52414143);
AnimatedCovers ph = new AnimatedCovers(MODE_PLACEHOLDER);
ph.request = request;
ph.description = description;
ph.colorFilter = colorFilter;
ph.contentScale = contentScale;
ph.placeholder = placeholder;
ph.onError = onError;
ph.defaults = defaults;
l.a(u, modifier, ph, composer, 0);
composer.endReplaceGroup();
} else {
composer.startReplaceGroup(0x52414144);
sd0.f.a(request, description, modifier, colorFilter, contentScale, placeholder, onError, composer, changed, defaults);
composer.endReplaceGroup();
}
}
// URL if it's for this album
static String urlFor(int albumId) {
String u = (String) urlState.getValue();
return u != null && ((Integer) albumState.getValue()).intValue() == albumId ? u : null;
}
// Normal cover as the placeholder
@Override
public Object invoke(Object composer, Object changed) {
Modifier fill = SizeKt.fillMaxSize(Modifier.Companion, 1f);
sd0.f.a(request, description, fill, colorFilter, contentScale, placeholder, onError, (Composer) composer, 0, defaults);
return kotlin.u.a; // MARKER: R8 kotlin.u
}
@Override
public void run() {
if (mode == MODE_PUBLISH) {
// Newer track, drop it
if (!key.equals(currentKey)) return;
urlState.setValue(url);
albumState.setValue(Integer.valueOf(albumId));
AnimatedCoverView.refreshAll();
return;
}
String found = null;
try {
// Queue can lag, try once more
if (album == null) {
Thread.sleep(800L);
album = currentAlbum(title);
}
// With album first, then without
if (album != null) found = lookup(title, artist, album);
if (found == null) found = lookup(title, artist, null);
} catch (Throwable t) {
Log.w(TAG, "lookup failed for " + title + ": " + t);
}
synchronized (cache) {
if (cache.size() >= CACHE_MAX) {
String eldest = cache.keySet().iterator().next();
cache.remove(eldest);
}
cache.put(key, found == null ? "" : found);
}
Log.i(TAG, (found == null ? "no animated cover for " : "animated cover for ") + title);
publish(key, found, albumId);
}
private static void publish(String key, String url, int albumId) {
AnimatedCovers p = new AnimatedCovers(MODE_PUBLISH);
p.key = key;
p.url = url;
p.albumId = albumId;
new Handler(Looper.getMainLooper()).post(p);
}
// Album name of the playing track
private static String currentAlbum(String title) {
try {
Object q = queue;
if (q == null) return null;
com.aspiro.wamp.playqueue.q0 item = ((zh.e) q).getCurrentItem(); // MARKER: R8 zh.e q0
if (item == null) return null;
com.aspiro.wamp.model.MediaItem media = item.getMediaItem();
if (media == null || !title.equals(media.getTitle()) || media.getAlbum() == null) return null;
String a = media.getAlbum().getTitle();
return a == null || a.trim().isEmpty() ? null : a.trim();
} catch (Throwable t) {
return null;
}
}
private static String lookup(String title, String artist, String album) throws Exception {
String q = SEARCH + "title=" + enc(title) + "&artist=" + enc(artist);
if (album != null) q = q + "&album=" + enc(album);
String body = get(q);
if (body == null) return null;
JSONObject o = new JSONObject(body);
String master = o.optString("url", "");
if (master.isEmpty()) return null;
return resolveMp4(master);
}
// Find the mp4 behind the playlist
private static String resolveMp4(String masterUrl) throws Exception {
String master = get(masterUrl);
if (master == null) return null;
String[] lines = master.split("\n");
String best = null;
int bestW = -1;
boolean bestBig = false;
for (int i = 0; i < lines.length - 1; i++) {
String line = lines[i].trim();
if (!line.startsWith("#EXT-X-STREAM-INF:") || line.indexOf("avc1") < 0) continue;
if (line.indexOf("VIDEO-RANGE=SDR") < 0 && line.indexOf("VIDEO-RANGE") >= 0) continue;
int w = 0;
int r = line.indexOf("RESOLUTION=");
if (r >= 0) {
int x = line.indexOf('x', r);
try {
w = Integer.parseInt(line.substring(r + 11, x));
} catch (Throwable ignored) {
}
}
String uri = lines[i + 1].trim();
if (uri.isEmpty() || uri.startsWith("#")) continue;
boolean big = w >= TARGET_PX;
// Smallest one big enough
if (best == null || (big && (!bestBig || w < bestW)) || (!big && !bestBig && w > bestW)) {
best = new URL(new URL(masterUrl), uri).toString();
bestW = w;
bestBig = big;
}
}
if (best == null) return null;
String media = get(best);
if (media == null) return null;
int m = media.indexOf("#EXT-X-MAP:URI=\"");
if (m < 0) return null;
int s = m + 16;
int e = media.indexOf('"', s);
if (e < 0) return null;
return new URL(new URL(best), media.substring(s, e)).toString();
}
private static String get(String url) throws Exception {
HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
c.setConnectTimeout(10000);
c.setReadTimeout(10000);
try {
if (c.getResponseCode() != 200) return null;
BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
char[] buf = new char[4096];
int n;
while ((n = r.read(buf)) > 0) sb.append(buf, 0, n);
r.close();
return sb.toString();
} finally {
c.disconnect();
}
}
private static String enc(String s) throws Exception {
return URLEncoder.encode(s, "UTF-8").replace("+", "%20");
}
}
@@ -0,0 +1,19 @@
package radiant;
import android.graphics.Outline;
import android.view.View;
import android.view.ViewOutlineProvider;
// Rounded corners for the cover overlay
public final class RoundOutline extends ViewOutlineProvider {
private final float radius;
public RoundOutline(float radius) {
this.radius = radius;
}
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
}
}
@@ -0,0 +1,262 @@
package radiant;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.Choreographer;
import androidx.compose.runtime.Composer;
import androidx.compose.runtime.MutableState;
import androidx.compose.runtime.SnapshotStateKt;
import androidx.compose.ui.geometry.Size;
import androidx.compose.ui.graphics.AndroidCanvas_androidKt;
import androidx.compose.ui.graphics.drawscope.DrawScope;
import androidx.compose.ui.graphics.painter.Painter;
import androidx.compose.ui.res.PainterResources_androidKt;
import com.tidal.android.feature.playerscreen.ui.g;
// Sparkle button loading spinner and no lyrics icon
public final class SparkleIcons extends Painter implements Choreographer.FrameCallback, Runnable {
static final int MODE_SPARKLE = 0;
static final int MODE_LOADING = 1;
static final int MODE_NONE = 2;
private static final long SPIN_MS = 900L;
// Same size as the lyrics icon
private static final float NOTE_SCALE = 0.75f;
// Desktop spinner, a bit smaller
private static final float RING_R = 7.1f / 20f;
private static final float RING_STROKE = 1.8f / 20f;
private static final float ARC_SWEEP = 42f / (float) (2 * Math.PI * 8) * 360f; // same gap as desktop
// Mode for the sparkle button
public static int currentMode = MODE_SPARKLE;
// RL API fetch running
static final MutableState rlLoading = SnapshotStateKt.mutableStateOf$default(Boolean.FALSE, null, 2, null);
static final MutableState tick = SnapshotStateKt.mutableStateOf$default(Long.valueOf(0L), null, 2, null);
private static final SparkleIcons POSTER = new SparkleIcons(MODE_SPARKLE, null, null);
private static final long LOADING_TIMEOUT_MS = 60000L; // requests can take ~30s
private static final Object lock = new Object();
// Newest fetch and if it's running
private static Object latestWorker;
private static boolean latestRunning;
// Bumped by timers to recompose
static final MutableState timeoutTick = SnapshotStateKt.mutableStateOf$default(Integer.valueOf(0), null, 2, null);
private static long loadingSinceMs;
private static boolean timeoutPosted;
private static volatile boolean newFetch;
private static Object lastMedia;
private static final long NONE_GRACE_MS = 5000L;
private static final String TAG = "RLSparkle";
private static long noneSinceMs;
private static int lastBranch = -1;
private static long postedAtMs = Long.MAX_VALUE;
private static final SparkleIcons TIMEOUT = new SparkleIcons(-1, null, null);
private static SparkleIcons spinner;
private static SparkleIcons noLyrics;
private static boolean frameScheduled;
private static long lastSpinDrawMs;
private final int mode;
private final Painter inner;
// Sparkle painter, for sizing
private final Painter sizeRef;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint clear = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF oval = new RectF();
private SparkleIcons(int mode, Painter inner, Painter sizeRef) {
this.mode = mode;
this.inner = inner;
this.sizeRef = sizeRef;
paint.setColor(0xFFFFFFFF);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
clear.setStyle(Paint.Style.STROKE);
clear.setStrokeCap(Paint.Cap.ROUND);
clear.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
// Pick the button mode
public static int modeFor(Object lyricsState, boolean hasLyrics, Object media) { // MARKER: R8 g$b g$c g$d
timeoutTick.getValue(); // recompose on timers
long now = SystemClock.uptimeMillis();
// New track, reset timers
if (media == null ? lastMedia != null : !media.equals(lastMedia)) {
lastMedia = media;
loadingSinceMs = 0L;
lastBranch = -1;
}
boolean rl = ((Boolean) rlLoading.getValue()).booleanValue();
int m;
int branch;
if (hasLyrics || lyricsState instanceof g.c || lyricsState instanceof g.d) {
// Lyrics win
branch = 0;
m = MODE_SPARKLE;
loadingSinceMs = 0L;
} else if (rl || lyricsState instanceof g.b) {
branch = 1;
if (loadingSinceMs == 0L) loadingSinceMs = now;
if (now - loadingSinceMs >= LOADING_TIMEOUT_MS) {
m = MODE_NONE; // don't spin forever
} else {
m = MODE_LOADING;
scheduleRecompose(loadingSinceMs + LOADING_TIMEOUT_MS);
}
} else {
// Wait a bit before saying no lyrics
branch = 2;
if (lastBranch != 2) noneSinceMs = now;
if (now - noneSinceMs < NONE_GRACE_MS) {
m = MODE_LOADING;
scheduleRecompose(noneSinceMs + NONE_GRACE_MS);
} else {
m = MODE_NONE;
}
loadingSinceMs = 0L;
}
lastBranch = branch;
if (m != currentMode) {
Log.i(TAG, "mode " + currentMode + " -> " + m + " (lyricsState="
+ (lyricsState == null ? "null" : lyricsState.getClass().getName())
+ " hasLyrics=" + hasLyrics + " rlLoading=" + rl + ")");
}
currentMode = m;
return m;
}
// Recompose later
private static void scheduleRecompose(long atMs) {
if (timeoutPosted && atMs >= postedAtMs) return;
timeoutPosted = true;
postedAtMs = atMs;
long delay = Math.max(0L, atMs - SystemClock.uptimeMillis()) + 50L;
new Handler(Looper.getMainLooper()).postDelayed(TIMEOUT, delay);
}
// Icon for the mode
public static Painter forMode(Painter sparkle, int mode, Composer composer) {
composer.startReplaceGroup(0x52415300 + mode);
Painter out = sparkle;
if (mode == MODE_LOADING && sparkle != null) {
SparkleIcons s = spinner;
if (s == null || s.inner != sparkle) spinner = s = new SparkleIcons(MODE_LOADING, sparkle, sparkle);
out = s;
} else if (mode == MODE_NONE && sparkle != null) {
Painter note = PainterResources_androidKt.painterResource(com.aspiro.wamp.R.drawable.ic_music_note, composer, 0);
SparkleIcons s = noLyrics;
if (s == null || s.inner != note || s.sizeRef != sparkle) noLyrics = s = new SparkleIcons(MODE_NONE, note, sparkle);
out = s;
}
composer.endReplaceGroup();
return out;
}
// Fetch started
public static void rlBegin(Object worker) {
synchronized (lock) {
latestWorker = worker;
latestRunning = true;
}
newFetch = true;
new Handler(Looper.getMainLooper()).post(POSTER);
}
// Fetch done, only the newest counts
public static void rlEnd(Object worker) {
synchronized (lock) {
if (worker == latestWorker) latestRunning = false;
}
new Handler(Looper.getMainLooper()).post(POSTER);
}
@Override
public void run() {
if (mode == -1) {
// Timer fired, recompose
timeoutPosted = false;
postedAtMs = Long.MAX_VALUE;
timeoutTick.setValue(Integer.valueOf(((Integer) timeoutTick.getValue()).intValue() + 1));
return;
}
boolean on;
synchronized (lock) {
on = latestRunning;
}
if (newFetch) {
// New fetch, reset timeout
newFetch = false;
loadingSinceMs = 0L;
timeoutTick.setValue(Integer.valueOf(((Integer) timeoutTick.getValue()).intValue() + 1));
}
Boolean v = Boolean.valueOf(on);
if (!v.equals(rlLoading.getValue())) rlLoading.setValue(v);
}
@Override
public long getIntrinsicSizeNHjbRc() {
return sizeRef.getIntrinsicSizeNHjbRc();
}
@Override
public void onDraw(DrawScope scope) {
long size = scope.getSizeNHjbRc();
float w = Size.getWidthImpl(size);
float h = Size.getHeightImpl(size);
if (w <= 0f || h <= 0f) return;
Canvas canvas = AndroidCanvas_androidKt.getNativeCanvas(scope.getDrawContext().getCanvas());
float s = Math.min(w, h);
if (mode == MODE_LOADING) {
tick.getValue(); // redraw every frame
long now = SystemClock.uptimeMillis();
lastSpinDrawMs = now;
if (!frameScheduled) {
frameScheduled = true;
Choreographer.getInstance().postFrameCallback(POSTER);
}
float r = s * RING_R;
paint.setStrokeWidth(s * RING_STROKE);
oval.set(w / 2f - r, h / 2f - r, w / 2f + r, h / 2f + r);
float angle = (now % SPIN_MS) / (float) SPIN_MS * 360f;
canvas.drawArc(oval, angle - 90f, ARC_SWEEP, false, paint);
return;
}
// Note with a line through it
int save = canvas.saveLayer(0f, 0f, w, h, null);
canvas.save();
canvas.scale(NOTE_SCALE, NOTE_SCALE, w / 2f, h / 2f);
inner.drawxKDEd0(scope, size, 1f, null);
canvas.restore();
float stroke = s * 0.06f;
float a = s * 0.2f, b = s * 0.8f;
clear.setStrokeWidth(stroke * 2.6f);
canvas.drawLine(a, a, b, b, clear);
paint.setStrokeWidth(stroke);
canvas.drawLine(a, a, b, b, paint);
canvas.restoreToCount(save);
}
@Override
public void doFrame(long frameTimeNanos) {
long now = SystemClock.uptimeMillis();
if (now - lastSpinDrawMs < 500L) {
tick.setValue(Long.valueOf(now));
Choreographer.getInstance().postFrameCallback(POSTER);
} else {
frameScheduled = false;
}
}
}
@@ -0,0 +1,5 @@
package androidx.compose.foundation.layout;
import androidx.compose.ui.Modifier;
public final class SizeKt {
public static Modifier fillMaxSize(Modifier modifier, float fraction) { return null; }
}
@@ -0,0 +1,6 @@
package androidx.compose.runtime;
public interface Composer {
void startReplaceGroup(int key);
void endReplaceGroup();
Object consume(CompositionLocal key);
}
@@ -0,0 +1,2 @@
package androidx.compose.runtime;
public abstract class CompositionLocal {}
@@ -0,0 +1,2 @@
package androidx.compose.runtime;
public interface MutableState extends State { void setValue(Object value); }
@@ -0,0 +1,2 @@
package androidx.compose.runtime;
public interface SnapshotMutationPolicy {}
@@ -0,0 +1,4 @@
package androidx.compose.runtime;
public final class SnapshotStateKt {
public static MutableState mutableStateOf$default(Object value, SnapshotMutationPolicy policy, int flags, Object marker) { return null; }
}
@@ -0,0 +1,2 @@
package androidx.compose.runtime;
public interface State { Object getValue(); }
@@ -0,0 +1,6 @@
package androidx.compose.ui;
public interface Modifier {
/** Non-constant initialiser so javac emits a real sget-object. */
Companion Companion = new Companion();
final class Companion implements Modifier {}
}
@@ -0,0 +1,7 @@
package androidx.compose.ui.geometry;
public final class Size {
/** Renamed to getWidth-impl by build.sh (Kotlin value-class mangling). */
public static float getWidthImpl(long packed) { return 0f; }
/** Renamed to getHeight-impl by build.sh (Kotlin value-class mangling). */
public static float getHeightImpl(long packed) { return 0f; }
}
@@ -0,0 +1,4 @@
package androidx.compose.ui.graphics;
public final class AndroidCanvas_androidKt {
public static android.graphics.Canvas getNativeCanvas(Canvas canvas) { return null; }
}
@@ -0,0 +1,2 @@
package androidx.compose.ui.graphics;
public interface Canvas {}
@@ -0,0 +1,2 @@
package androidx.compose.ui.graphics;
public class ColorFilter {}
@@ -0,0 +1,3 @@
package androidx.compose.ui.graphics.drawscope;
import androidx.compose.ui.graphics.Canvas;
public interface DrawContext { Canvas getCanvas(); }
@@ -0,0 +1,6 @@
package androidx.compose.ui.graphics.drawscope;
public interface DrawScope {
DrawContext getDrawContext();
/** Renamed to getSize-NH-jbRc by build.sh (Kotlin value-class mangling). */
long getSizeNHjbRc();
}
@@ -0,0 +1,14 @@
package androidx.compose.ui.graphics.painter;
import androidx.compose.ui.graphics.ColorFilter;
import androidx.compose.ui.graphics.drawscope.DrawScope;
// build.sh fixes the mangled names
public abstract class Painter {
public Painter() {}
public boolean applyAlpha(float alpha) { return false; }
public boolean applyColorFilter(ColorFilter colorFilter) { return false; }
public final void drawxKDEd0(DrawScope scope, long size, float alpha, ColorFilter colorFilter) {}
public abstract long getIntrinsicSizeNHjbRc();
public abstract void onDraw(DrawScope scope);
}
@@ -0,0 +1,2 @@
package androidx.compose.ui.layout;
public interface ContentScale {}
@@ -0,0 +1,4 @@
package androidx.compose.ui.res;
public final class PainterResources_androidKt {
public static androidx.compose.ui.graphics.painter.Painter painterResource(int id, androidx.compose.runtime.Composer composer, int changed) { return null; }
}
@@ -0,0 +1,6 @@
package com.aspiro.wamp;
// Not final so javac doesn't inline them
public final class R {
public static final class drawable { public static int ic_music_note; }
public static final class dimen { public static int image_header_corner_radius; }
}
@@ -0,0 +1,2 @@
package com.aspiro.wamp.model;
public class Album { public String getTitle() { return null; } }
@@ -0,0 +1,5 @@
package com.aspiro.wamp.model;
public class MediaItem {
public Album getAlbum() { return null; }
public String getTitle() { return null; }
}
@@ -0,0 +1,3 @@
package com.aspiro.wamp.playqueue;
// MARKER: R8 class name: TIDAL dex FQN is `com.aspiro.wamp.playqueue.q0` (play queue item)
public interface q0 { com.aspiro.wamp.model.MediaItem getMediaItem(); }
@@ -0,0 +1,6 @@
package com.tidal.android.core.compose.components;
// MARKER: R8 class name: TIDAL's AnimatedCover.kt (AnimatedCover(url, modifier, placeholder) + LoopingVideo)
public final class l {
public static void a(String url, androidx.compose.ui.Modifier modifier, yl0.p placeholder,
androidx.compose.runtime.Composer composer, int changed) {}
}
@@ -0,0 +1,7 @@
package com.tidal.android.feature.playerscreen.ui;
// MARKER: R8 class names: player lyrics state - g$a Hidden, g$b Loading, g$c Synced, g$d Unsynced
public abstract class g {
public static final class b extends g {}
public static final class c extends g {}
public static final class d extends g {}
}
+4
View File
@@ -0,0 +1,4 @@
package kotlin;
// MARKER: R8 whole class: TIDAL dex FQN is `kotlin.u` - both package and name roll
/** kotlin.Unit (obfuscated in the TIDAL dex). */
public final class u { public static final u a = null; }
+7
View File
@@ -0,0 +1,7 @@
package sd0;
// MARKER: R8 whole class: TIDAL dex FQN is `sd0.f` (TIDAL's image composable used by AlbumCover)
public final class f {
public static void a(yl0.l request, String contentDescription, androidx.compose.ui.Modifier modifier,
androidx.compose.ui.graphics.ColorFilter colorFilter, androidx.compose.ui.layout.ContentScale contentScale,
Object placeholder, yl0.a onError, androidx.compose.runtime.Composer composer, int changed, int defaults) {}
}
+4
View File
@@ -0,0 +1,4 @@
package yl0;
// MARKER: R8 whole class: TIDAL dex FQN is `yl0.a` - both package and name roll
/** kotlin.jvm.functions.Function0 (obfuscated in the TIDAL dex). */
public interface a { Object invoke(); }
+4
View File
@@ -0,0 +1,4 @@
package yl0;
// MARKER: R8 whole class: TIDAL dex FQN is `yl0.l` - both package and name roll
/** kotlin.jvm.functions.Function1 (obfuscated in the TIDAL dex). */
public interface l { Object invoke(Object p1); }
+4
View File
@@ -0,0 +1,4 @@
package yl0;
// MARKER: R8 whole class: TIDAL dex FQN is `yl0.p` - both package and name roll
/** kotlin.jvm.functions.Function2 (obfuscated in the TIDAL dex). */
public interface p { Object invoke(Object p1, Object p2); }
+3
View File
@@ -0,0 +1,3 @@
package zh;
// MARKER: R8 whole class: TIDAL dex FQN is `zh.e` (play queue provider held by Mini/PlayerViewModel.a)
public interface e { com.aspiro.wamp.playqueue.q0 getCurrentItem(); }