diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java
index c035fa78..5be730f8 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java
@@ -4,6 +4,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.util.Log;
+import androidx.media3.common.Player;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
@@ -34,7 +35,23 @@ public final class WidgetActions {
case WidgetProvider.ACT_PREV:
c.seekToPrevious();
break;
+ case WidgetProvider.ACT_TOGGLE_SHUFFLE:
+ c.setShuffleModeEnabled(!c.getShuffleModeEnabled());
+ break;
+ case WidgetProvider.ACT_CYCLE_REPEAT:
+ int repeatMode = c.getRepeatMode();
+ int nextMode;
+ if (repeatMode == Player.REPEAT_MODE_OFF) {
+ nextMode = Player.REPEAT_MODE_ALL;
+ } else if (repeatMode == Player.REPEAT_MODE_ALL) {
+ nextMode = Player.REPEAT_MODE_ONE;
+ } else {
+ nextMode = Player.REPEAT_MODE_OFF;
+ }
+ c.setRepeatMode(nextMode);
+ break;
}
+ WidgetUpdateManager.refreshFromController(ctx);
c.release();
} catch (ExecutionException | InterruptedException e) {
Log.e("TempoWidget", "dispatch failed", e);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java
index 2b4b1f3c..57033c20 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java
@@ -18,6 +18,8 @@ public class WidgetProvider extends AppWidgetProvider {
public static final String ACT_PLAY_PAUSE = "tempo.widget.PLAY_PAUSE";
public static final String ACT_NEXT = "tempo.widget.NEXT";
public static final String ACT_PREV = "tempo.widget.PREV";
+ public static final String ACT_TOGGLE_SHUFFLE = "tempo.widget.SHUFFLE";
+ public static final String ACT_CYCLE_REPEAT = "tempo.widget.REPEAT";
@Override public void onUpdate(Context ctx, AppWidgetManager mgr, int[] ids) {
for (int id : ids) {
@@ -31,7 +33,8 @@ public class WidgetProvider extends AppWidgetProvider {
super.onReceive(ctx, intent);
String a = intent.getAction();
Log.d(TAG, "onReceive action=" + a);
- if (ACT_PLAY_PAUSE.equals(a) || ACT_NEXT.equals(a) || ACT_PREV.equals(a)) {
+ if (ACT_PLAY_PAUSE.equals(a) || ACT_NEXT.equals(a) || ACT_PREV.equals(a)
+ || ACT_TOGGLE_SHUFFLE.equals(a) || ACT_CYCLE_REPEAT.equals(a)) {
WidgetActions.dispatchToMediaSession(ctx, a);
} else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(a)) {
WidgetUpdateManager.refreshFromController(ctx);
@@ -69,10 +72,24 @@ public class WidgetProvider extends AppWidgetProvider {
new Intent(ctx, WidgetProvider4x1.class).setAction(ACT_PREV),
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
);
+ PendingIntent shuffle = PendingIntent.getBroadcast(
+ ctx,
+ requestCodeBase + 3,
+ new Intent(ctx, WidgetProvider4x1.class).setAction(ACT_TOGGLE_SHUFFLE),
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ );
+ PendingIntent repeat = PendingIntent.getBroadcast(
+ ctx,
+ requestCodeBase + 4,
+ new Intent(ctx, WidgetProvider4x1.class).setAction(ACT_CYCLE_REPEAT),
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ );
rv.setOnClickPendingIntent(R.id.btn_play_pause, playPause);
rv.setOnClickPendingIntent(R.id.btn_next, next);
rv.setOnClickPendingIntent(R.id.btn_prev, prev);
+ rv.setOnClickPendingIntent(R.id.btn_shuffle, shuffle);
+ rv.setOnClickPendingIntent(R.id.btn_repeat, repeat);
PendingIntent launch = TaskStackBuilder.create(ctx)
.addNextIntentWithParentStack(new Intent(ctx, MainActivity.class))
diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java
index e9515c50..dbbcc6b3 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java
@@ -24,20 +24,24 @@ public final class WidgetUpdateManager {
public static void updateFromState(Context ctx,
String title,
String artist,
+ String album,
Bitmap art,
boolean playing,
+ boolean shuffleEnabled,
+ int repeatMode,
long positionMs,
long durationMs) {
if (TextUtils.isEmpty(title)) title = ctx.getString(R.string.widget_not_playing);
if (TextUtils.isEmpty(artist)) artist = ctx.getString(R.string.widget_placeholder_subtitle);
+ if (TextUtils.isEmpty(album)) album = "";
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
for (int id : ids) {
- android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, art, playing,
- timing.elapsedText, timing.totalText, timing.progress, id);
+ android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, album, art, playing,
+ timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id);
WidgetProvider.attachIntents(ctx, rv, id);
mgr.updateAppWidget(id, rv);
}
@@ -56,14 +60,20 @@ public final class WidgetUpdateManager {
public static void updateFromState(Context ctx,
String title,
String artist,
+ String album,
String coverArtId,
boolean playing,
+ boolean shuffleEnabled,
+ int repeatMode,
long positionMs,
long durationMs) {
final Context appCtx = ctx.getApplicationContext();
final String t = TextUtils.isEmpty(title) ? appCtx.getString(R.string.widget_not_playing) : title;
final String a = TextUtils.isEmpty(artist) ? appCtx.getString(R.string.widget_placeholder_subtitle) : artist;
+ final String alb = !TextUtils.isEmpty(album) ? album : "";
final boolean p = playing;
+ final boolean sh = shuffleEnabled;
+ final int rep = repeatMode;
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
if (!TextUtils.isEmpty(coverArtId)) {
@@ -76,8 +86,8 @@ public final class WidgetUpdateManager {
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
for (int id : ids) {
- android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, resource, p,
- timing.elapsedText, timing.totalText, timing.progress, id);
+ android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, resource, p,
+ timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
WidgetProvider.attachIntents(appCtx, rv, id);
mgr.updateAppWidget(id, rv);
}
@@ -87,8 +97,8 @@ public final class WidgetUpdateManager {
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
for (int id : ids) {
- android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, null, p,
- timing.elapsedText, timing.totalText, timing.progress, id);
+ android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
+ timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
WidgetProvider.attachIntents(appCtx, rv, id);
mgr.updateAppWidget(id, rv);
}
@@ -99,8 +109,8 @@ public final class WidgetUpdateManager {
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
for (int id : ids) {
- android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, null, p,
- timing.elapsedText, timing.totalText, timing.progress, id);
+ android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
+ timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
WidgetProvider.attachIntents(appCtx, rv, id);
mgr.updateAppWidget(id, rv);
}
@@ -116,13 +126,15 @@ public final class WidgetUpdateManager {
if (!future.isDone()) return;
MediaController c = future.get();
androidx.media3.common.MediaItem mi = c.getCurrentMediaItem();
- String title = null, artist = null, coverId = null;
+ String title = null, artist = null, album = null, coverId = null;
if (mi != null && mi.mediaMetadata != null) {
if (mi.mediaMetadata.title != null) title = mi.mediaMetadata.title.toString();
if (mi.mediaMetadata.artist != null) artist = mi.mediaMetadata.artist.toString();
+ if (mi.mediaMetadata.albumTitle != null) album = mi.mediaMetadata.albumTitle.toString();
if (mi.mediaMetadata.extras != null) {
if (title == null) title = mi.mediaMetadata.extras.getString("title");
if (artist == null) artist = mi.mediaMetadata.extras.getString("artist");
+ if (album == null) album = mi.mediaMetadata.extras.getString("album");
coverId = mi.mediaMetadata.extras.getString("coverArtId");
}
}
@@ -133,8 +145,11 @@ public final class WidgetUpdateManager {
updateFromState(appCtx,
title != null ? title : appCtx.getString(R.string.widget_not_playing),
artist != null ? artist : appCtx.getString(R.string.widget_placeholder_subtitle),
+ album,
coverId,
c.isPlaying(),
+ c.getShuffleModeEnabled(),
+ c.getRepeatMode(),
position,
duration);
c.release();
@@ -174,31 +189,68 @@ public final class WidgetUpdateManager {
}
public static android.widget.RemoteViews chooseBuild(Context ctx, int appWidgetId) {
- if (isLarge(ctx, appWidgetId)) return WidgetViewsFactory.buildLarge(ctx);
- return WidgetViewsFactory.buildCompact(ctx);
+ LayoutSize size = resolveLayoutSize(ctx, appWidgetId);
+ switch (size) {
+ case MEDIUM:
+ return WidgetViewsFactory.buildMedium(ctx);
+ case LARGE:
+ return WidgetViewsFactory.buildLarge(ctx);
+ case EXPANDED:
+ return WidgetViewsFactory.buildExpanded(ctx);
+ case COMPACT:
+ default:
+ return WidgetViewsFactory.buildCompact(ctx);
+ }
}
private static android.widget.RemoteViews choosePopulate(Context ctx,
String title,
String artist,
+ String album,
Bitmap art,
boolean playing,
String elapsedText,
String totalText,
int progress,
+ boolean shuffleEnabled,
+ int repeatMode,
int appWidgetId) {
- if (isLarge(ctx, appWidgetId)) {
- return WidgetViewsFactory.populateLarge(ctx, title, artist, art, playing, elapsedText, totalText, progress);
+ LayoutSize size = resolveLayoutSize(ctx, appWidgetId);
+ switch (size) {
+ case MEDIUM:
+ return WidgetViewsFactory.populateMedium(ctx, title, artist, album, art, playing,
+ elapsedText, totalText, progress, shuffleEnabled, repeatMode);
+ case LARGE:
+ return WidgetViewsFactory.populateLarge(ctx, title, artist, album, art, playing,
+ elapsedText, totalText, progress, shuffleEnabled, repeatMode);
+ case EXPANDED:
+ return WidgetViewsFactory.populateExpanded(ctx, title, artist, album, art, playing,
+ elapsedText, totalText, progress, shuffleEnabled, repeatMode);
+ case COMPACT:
+ default:
+ return WidgetViewsFactory.populateCompact(ctx, title, artist, album, art, playing,
+ elapsedText, totalText, progress, shuffleEnabled, repeatMode);
}
- return WidgetViewsFactory.populate(ctx, title, artist, art, playing, elapsedText, totalText, progress);
}
- private static boolean isLarge(Context ctx, int appWidgetId) {
+ private static LayoutSize resolveLayoutSize(Context ctx, int appWidgetId) {
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
android.os.Bundle opts = mgr.getAppWidgetOptions(appWidgetId);
int minH = opts != null ? opts.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) : 0;
- int threshold = ctx.getResources().getInteger(com.cappielloantonio.tempo.R.integer.widget_large_min_height_dp);
- return minH >= threshold; // dp threshold for 2-row height
+ int expandedThreshold = ctx.getResources().getInteger(R.integer.widget_expanded_min_height_dp);
+ int largeThreshold = ctx.getResources().getInteger(R.integer.widget_large_min_height_dp);
+ int mediumThreshold = ctx.getResources().getInteger(R.integer.widget_medium_min_height_dp);
+ if (minH >= expandedThreshold) return LayoutSize.EXPANDED;
+ if (minH >= largeThreshold) return LayoutSize.LARGE;
+ if (minH >= mediumThreshold) return LayoutSize.MEDIUM;
+ return LayoutSize.COMPACT;
+ }
+
+ private enum LayoutSize {
+ COMPACT,
+ MEDIUM,
+ LARGE,
+ EXPANDED
}
private static final class TimingInfo {
diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java
index 05346eb3..ef960aef 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java
@@ -2,76 +2,168 @@ package com.cappielloantonio.tempo.widget;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
import android.text.TextUtils;
+import android.util.TypedValue;
+import android.view.View;
import android.widget.RemoteViews;
+
+import androidx.core.content.ContextCompat;
+import androidx.media3.common.Player;
+
import com.cappielloantonio.tempo.R;
public final class WidgetViewsFactory {
static final int PROGRESS_MAX = 1000;
+ private static final float ALBUM_ART_CORNER_RADIUS_DP = 6f;
+
+ private WidgetViewsFactory() {}
public static RemoteViews buildCompact(Context ctx) {
- return build(ctx, R.layout.widget_layout_compact);
+ return build(ctx, R.layout.widget_layout_compact, false, false);
+ }
+
+ public static RemoteViews buildMedium(Context ctx) {
+ return build(ctx, R.layout.widget_layout_medium, false, false);
}
public static RemoteViews buildLarge(Context ctx) {
- return build(ctx, R.layout.widget_layout_large);
+ return build(ctx, R.layout.widget_layout_large_short, true, true);
}
+ public static RemoteViews buildExpanded(Context ctx) {
+ return build(ctx, R.layout.widget_layout_large, true, true);
+ }
- private static RemoteViews build(Context ctx, int layoutRes) {
+ private static RemoteViews build(Context ctx,
+ int layoutRes,
+ boolean showAlbum,
+ boolean showSecondaryControls) {
RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes);
rv.setTextViewText(R.id.title, ctx.getString(R.string.widget_not_playing));
rv.setTextViewText(R.id.subtitle, ctx.getString(R.string.widget_placeholder_subtitle));
+ rv.setTextViewText(R.id.album, "");
+ rv.setViewVisibility(R.id.album, showAlbum ? View.INVISIBLE : View.GONE);
rv.setTextViewText(R.id.time_elapsed, ctx.getString(R.string.widget_time_elapsed_placeholder));
rv.setTextViewText(R.id.time_total, ctx.getString(R.string.widget_time_duration_placeholder));
rv.setProgressBar(R.id.progress, PROGRESS_MAX, 0, false);
rv.setImageViewResource(R.id.btn_play_pause, R.drawable.ic_play);
- // Show Tempo logo when nothing is playing
rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo);
+ applySecondaryControlsDefaults(ctx, rv, showSecondaryControls);
return rv;
}
- public static RemoteViews populate(Context ctx,
- String title,
- String subtitle,
- Bitmap art,
- boolean playing,
- String elapsedText,
- String totalText,
- int progress) {
- return populateWithLayout(ctx, title, subtitle, art, playing, elapsedText, totalText, progress, R.layout.widget_layout_compact);
+ private static void applySecondaryControlsDefaults(Context ctx,
+ RemoteViews rv,
+ boolean show) {
+ int visibility = show ? View.VISIBLE : View.GONE;
+ rv.setViewVisibility(R.id.controls_secondary, visibility);
+ rv.setViewVisibility(R.id.btn_shuffle, visibility);
+ rv.setViewVisibility(R.id.btn_repeat, visibility);
+ if (show) {
+ int defaultColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint);
+ rv.setImageViewResource(R.id.btn_shuffle, R.drawable.ic_shuffle);
+ rv.setImageViewResource(R.id.btn_repeat, R.drawable.ic_repeat);
+ rv.setInt(R.id.btn_shuffle, "setColorFilter", defaultColor);
+ rv.setInt(R.id.btn_repeat, "setColorFilter", defaultColor);
+ }
+ }
+
+ public static RemoteViews populateCompact(Context ctx,
+ String title,
+ String subtitle,
+ String album,
+ Bitmap art,
+ boolean playing,
+ String elapsedText,
+ String totalText,
+ int progress,
+ boolean shuffleEnabled,
+ int repeatMode) {
+ return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
+ progress, R.layout.widget_layout_compact, false, false, shuffleEnabled, repeatMode);
+ }
+
+ public static RemoteViews populateMedium(Context ctx,
+ String title,
+ String subtitle,
+ String album,
+ Bitmap art,
+ boolean playing,
+ String elapsedText,
+ String totalText,
+ int progress,
+ boolean shuffleEnabled,
+ int repeatMode) {
+ return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
+ progress, R.layout.widget_layout_medium, true, true, shuffleEnabled, repeatMode);
}
public static RemoteViews populateLarge(Context ctx,
- String title,
- String subtitle,
- Bitmap art,
- boolean playing,
- String elapsedText,
- String totalText,
- int progress) {
- return populateWithLayout(ctx, title, subtitle, art, playing, elapsedText, totalText, progress, R.layout.widget_layout_large);
+ String title,
+ String subtitle,
+ String album,
+ Bitmap art,
+ boolean playing,
+ String elapsedText,
+ String totalText,
+ int progress,
+ boolean shuffleEnabled,
+ int repeatMode) {
+ return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
+ progress, R.layout.widget_layout_large_short, true, true, shuffleEnabled, repeatMode);
}
+ public static RemoteViews populateExpanded(Context ctx,
+ String title,
+ String subtitle,
+ String album,
+ Bitmap art,
+ boolean playing,
+ String elapsedText,
+ String totalText,
+ int progress,
+ boolean shuffleEnabled,
+ int repeatMode) {
+ return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
+ progress, R.layout.widget_layout_large, true, true, shuffleEnabled, repeatMode);
+ }
private static RemoteViews populateWithLayout(Context ctx,
String title,
- String subtitle,
- Bitmap art,
- boolean playing,
- String elapsedText,
- String totalText,
- int progress,
- int layoutRes) {
+ String subtitle,
+ String album,
+ Bitmap art,
+ boolean playing,
+ String elapsedText,
+ String totalText,
+ int progress,
+ int layoutRes,
+ boolean showAlbum,
+ boolean showSecondaryControls,
+ boolean shuffleEnabled,
+ int repeatMode) {
RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes);
rv.setTextViewText(R.id.title, title);
rv.setTextViewText(R.id.subtitle, subtitle);
- if (art != null) {
- rv.setImageViewBitmap(R.id.album_art, art);
+ if (showAlbum && !TextUtils.isEmpty(album)) {
+ rv.setTextViewText(R.id.album, album);
+ rv.setViewVisibility(R.id.album, View.VISIBLE);
+ } else {
+ rv.setTextViewText(R.id.album, "");
+ rv.setViewVisibility(R.id.album, View.GONE);
+ }
+
+ if (art != null) {
+ Bitmap rounded = maybeRoundBitmap(ctx, art);
+ rv.setImageViewBitmap(R.id.album_art, rounded != null ? rounded : art);
} else {
- // Fallback to app logo when art is missing
rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo);
}
@@ -93,6 +185,67 @@ public final class WidgetViewsFactory {
rv.setTextViewText(R.id.time_total, total);
rv.setProgressBar(R.id.progress, PROGRESS_MAX, safeProgress, false);
+ applySecondaryControls(ctx, rv, showSecondaryControls, shuffleEnabled, repeatMode);
+
return rv;
}
+
+ private static Bitmap maybeRoundBitmap(Context ctx, Bitmap source) {
+ if (source == null || source.isRecycled()) {
+ return null;
+ }
+
+ try {
+ int width = source.getWidth();
+ int height = source.getHeight();
+ if (width <= 0 || height <= 0) {
+ return null;
+ }
+
+ Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(output);
+
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+
+ float radiusPx = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ ALBUM_ART_CORNER_RADIUS_DP,
+ ctx.getResources().getDisplayMetrics());
+ float maxRadius = Math.min(width, height) / 2f;
+ float safeRadius = Math.min(radiusPx, maxRadius);
+
+ canvas.drawRoundRect(new RectF(0f, 0f, width, height), safeRadius, safeRadius, paint);
+ return output;
+ } catch (RuntimeException | OutOfMemoryError e) {
+ android.util.Log.w("TempoWidget", "Failed to round album art", e);
+ return null;
+ }
+ }
+
+ private static void applySecondaryControls(Context ctx,
+ RemoteViews rv,
+ boolean show,
+ boolean shuffleEnabled,
+ int repeatMode) {
+ if (!show) {
+ rv.setViewVisibility(R.id.controls_secondary, View.GONE);
+ rv.setViewVisibility(R.id.btn_shuffle, View.GONE);
+ rv.setViewVisibility(R.id.btn_repeat, View.GONE);
+ return;
+ }
+
+ int inactiveColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint);
+ int activeColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint_active);
+
+ rv.setViewVisibility(R.id.controls_secondary, View.VISIBLE);
+ rv.setViewVisibility(R.id.btn_shuffle, View.VISIBLE);
+ rv.setViewVisibility(R.id.btn_repeat, View.VISIBLE);
+ rv.setImageViewResource(R.id.btn_shuffle, R.drawable.ic_shuffle);
+ rv.setImageViewResource(R.id.btn_repeat,
+ repeatMode == Player.REPEAT_MODE_ONE ? R.drawable.ic_repeat_one : R.drawable.ic_repeat);
+ rv.setInt(R.id.btn_shuffle, "setColorFilter", shuffleEnabled ? activeColor : inactiveColor);
+ rv.setInt(R.id.btn_repeat, "setColorFilter",
+ repeatMode == Player.REPEAT_MODE_OFF ? inactiveColor : activeColor);
+ }
}
diff --git a/app/src/main/res/drawable/ic_repeat_one.xml b/app/src/main/res/drawable/ic_repeat_one.xml
new file mode 100644
index 00000000..f422f79a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_repeat_one.xml
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_layout_compact.xml b/app/src/main/res/layout/widget_layout_compact.xml
index 5e0108dc..78fb72fb 100644
--- a/app/src/main/res/layout/widget_layout_compact.xml
+++ b/app/src/main/res/layout/widget_layout_compact.xml
@@ -3,13 +3,17 @@
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="64dp"
- android:padding="8dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
android:background="@drawable/widget_bg">
@@ -32,6 +36,8 @@
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/widget_title"
+ android:includeFontPadding="false"
+ android:freezesText="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
@@ -41,15 +47,30 @@
android:ellipsize="end"
android:textSize="12sp"
android:textColor="@color/widget_subtitle"
+ android:includeFontPadding="false"
+ android:freezesText="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
+
+
+ android:textSize="10sp"
+ android:includeFontPadding="false"/>
+ android:textSize="10sp"
+ android:includeFontPadding="false"/>
+
+
+
+
+
+
+
@@ -93,22 +145,28 @@
android:layout_width="wrap_content"
android:layout_height="match_parent">
-
-
-
-
-
-
+ android:orientation="horizontal"
+ android:baselineAligned="false">
-
-
-
-
-
+
+ android:id="@+id/text_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="center_vertical">
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:scrollHorizontally="true"
+ android:textStyle="bold"
+ android:textSize="18sp"
+ android:textColor="@color/widget_title"
+ android:includeFontPadding="false"
+ android:freezesText="true" />
+ android:includeFontPadding="false"
+ android:freezesText="true" />
+
+
+
+
+
+
+
+
+
+
+
+ android:orientation="horizontal">
-
+ android:src="@drawable/ic_skip_previous"
+ android:tint="@color/widget_icon_tint" />
-
+ android:src="@drawable/ic_play"
+ android:tint="@color/widget_icon_tint" />
-
+ android:src="@drawable/ic_skip_next"
+ android:tint="@color/widget_icon_tint" />
-
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/widget_layout_large_short.xml b/app/src/main/res/layout/widget_layout_large_short.xml
new file mode 100644
index 00000000..6a715f6e
--- /dev/null
+++ b/app/src/main/res/layout/widget_layout_large_short.xml
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/widget_layout_medium.xml b/app/src/main/res/layout/widget_layout_medium.xml
new file mode 100644
index 00000000..802da828
--- /dev/null
+++ b/app/src/main/res/layout/widget_layout_medium.xml
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/widget_preview_compact.xml b/app/src/main/res/layout/widget_preview_compact.xml
index e369ffb6..e863603a 100644
--- a/app/src/main/res/layout/widget_preview_compact.xml
+++ b/app/src/main/res/layout/widget_preview_compact.xml
@@ -11,8 +11,9 @@
@@ -79,4 +80,3 @@
android:contentDescription="@string/widget_content_desc_next"/>
-
diff --git a/app/src/main/res/values/colors_widget.xml b/app/src/main/res/values/colors_widget.xml
index 75a9f7f1..71a34138 100644
--- a/app/src/main/res/values/colors_widget.xml
+++ b/app/src/main/res/values/colors_widget.xml
@@ -5,4 +5,5 @@
#DE000000
#99000000
#DE000000
+ #FF6750A4
diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml
index 7b968814..e1a1ac1c 100644
--- a/app/src/main/res/values/integers.xml
+++ b/app/src/main/res/values/integers.xml
@@ -1,4 +1,6 @@
- 100
+ 100
+ 160
+ 220
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6a4851cd..47a6650e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -448,6 +448,8 @@
Play or pause
Next track
Previous track
+ Toggle shuffle
+ Change repeat mode
- %d album to sync
- %d albums to sync
diff --git a/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
index bdc07e96..67b02594 100644
--- a/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
+++ b/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
@@ -416,6 +416,8 @@ class MediaService : MediaLibraryService() {
?: mi?.mediaMetadata?.extras?.getString("title")
val artist = mi?.mediaMetadata?.artist?.toString()
?: mi?.mediaMetadata?.extras?.getString("artist")
+ val album = mi?.mediaMetadata?.albumTitle?.toString()
+ ?: mi?.mediaMetadata?.extras?.getString("album")
val coverId = mi?.mediaMetadata?.extras?.getString("coverArtId")
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
@@ -423,8 +425,11 @@ class MediaService : MediaLibraryService() {
this,
title ?: "",
artist ?: "",
+ album ?: "",
coverId,
player.isPlaying,
+ player.shuffleModeEnabled,
+ player.repeatMode,
position,
duration
)
diff --git a/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt
index a0af016b..5f44ed3a 100644
--- a/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt
+++ b/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt
@@ -262,6 +262,8 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
?: mi?.mediaMetadata?.extras?.getString("title")
val artist = mi?.mediaMetadata?.artist?.toString()
?: mi?.mediaMetadata?.extras?.getString("artist")
+ val album = mi?.mediaMetadata?.albumTitle?.toString()
+ ?: mi?.mediaMetadata?.extras?.getString("album")
val coverId = mi?.mediaMetadata?.extras?.getString("coverArtId")
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
@@ -270,8 +272,11 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
this,
title ?: "",
artist ?: "",
+ album ?: "",
coverId,
player.isPlaying,
+ player.shuffleModeEnabled,
+ player.repeatMode,
position,
duration
)
diff --git a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
index a5164e05..ac20668a 100644
--- a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
+++ b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
@@ -262,6 +262,8 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
?: mi?.mediaMetadata?.extras?.getString("title")
val artist = mi?.mediaMetadata?.artist?.toString()
?: mi?.mediaMetadata?.extras?.getString("artist")
+ val album = mi?.mediaMetadata?.albumTitle?.toString()
+ ?: mi?.mediaMetadata?.extras?.getString("album")
val coverId = mi?.mediaMetadata?.extras?.getString("coverArtId")
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
@@ -269,8 +271,11 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
this,
title ?: "",
artist ?: "",
+ album ?: "",
coverId,
player.isPlaying,
+ player.shuffleModeEnabled,
+ player.repeatMode,
position,
duration
)
diff --git a/notes b/notes
new file mode 100644
index 00000000..e69de29b