Add option to use cover image as widget background (#7924)

This commit is contained in:
Soumyadip 2025-08-16 14:37:02 +05:30 committed by GitHub
parent 4b101583c5
commit e3b587ae55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 120 additions and 35 deletions

View File

@ -901,6 +901,7 @@
<string name="widget_settings">Widget settings</string>
<string name="widget_create_button">Create widget</string>
<string name="widget_opacity">Opacity</string>
<string name="cover_as_background">Cover as background</string>
<!-- On-Demand configuration -->
<string name="on_demand_config_setting_changed">Setting updated successfully.</string>

View File

@ -26,6 +26,7 @@ dependencies {
implementation project(":ui:common")
implementation project(":ui:episodes")
implementation project(':ui:i18n')
implementation project(':ui:glide')
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"

View File

@ -23,6 +23,7 @@ public class PlayerWidget extends AppWidgetProvider {
public static final String KEY_WIDGET_SKIP = "widget_skip";
public static final String KEY_WIDGET_FAST_FORWARD = "widget_fast_forward";
public static final String KEY_WIDGET_REWIND = "widget_rewind";
public static final String KEY_WIDGET_COVER_BACKGROUND = "widget_cover_background";
public static final int DEFAULT_COLOR = 0xff262C31;
private static final String WORKAROUND_WORK_NAME = "WidgetUpdaterWorkaround";
@ -65,6 +66,7 @@ public class PlayerWidget extends AppWidgetProvider {
prefs.edit().remove(KEY_WIDGET_REWIND + appWidgetId).apply();
prefs.edit().remove(KEY_WIDGET_FAST_FORWARD + appWidgetId).apply();
prefs.edit().remove(KEY_WIDGET_SKIP + appWidgetId).apply();
prefs.edit().remove(KEY_WIDGET_COVER_BACKGROUND + appWidgetId).apply();
}
AppWidgetManager manager = AppWidgetManager.getInstance(context);
int[] widgetIds = manager.getAppWidgetIds(new ComponentName(context, PlayerWidget.class));

View File

@ -10,9 +10,16 @@ import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.load.Transformation;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.ui.common.ToolbarActivity;
import de.danoeh.antennapod.ui.glide.FastBlurTransformation;
import java.util.Locale;
@ -26,6 +33,7 @@ public class WidgetConfigActivity extends ToolbarActivity {
private CheckBox ckRewind;
private CheckBox ckFastForward;
private CheckBox ckSkip;
private CheckBox ckCoverAsBcg;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -85,6 +93,8 @@ public class WidgetConfigActivity extends ToolbarActivity {
ckFastForward.setOnClickListener(v -> displayPreviewPanel());
ckSkip = findViewById(R.id.ckSkip);
ckSkip.setOnClickListener(v -> displayPreviewPanel());
ckCoverAsBcg = findViewById(R.id.ckCoverAsBcg);
ckCoverAsBcg.setOnClickListener(v -> displayPreviewPanel());
setInitialState();
}
@ -95,6 +105,7 @@ public class WidgetConfigActivity extends ToolbarActivity {
ckRewind.setChecked(prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, false));
ckFastForward.setChecked(prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, false));
ckSkip.setChecked(prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, false));
ckCoverAsBcg.setChecked(prefs.getBoolean(PlayerWidget.KEY_WIDGET_COVER_BACKGROUND + appWidgetId, false));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
int color = prefs.getInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, PlayerWidget.DEFAULT_COLOR);
int opacity = Color.alpha(color) * 100 / 0xFF;
@ -116,6 +127,31 @@ public class WidgetConfigActivity extends ToolbarActivity {
.setVisibility(ckFastForward.isChecked() ? View.VISIBLE : View.GONE);
widgetPreview.findViewById(R.id.butSkip).setVisibility(ckSkip.isChecked() ? View.VISIBLE : View.GONE);
widgetPreview.findViewById(R.id.butRew).setVisibility(ckRewind.isChecked() ? View.VISIBLE : View.GONE);
if (ckCoverAsBcg.isChecked()) {
widgetPreview.findViewById(R.id.imgvCover).setVisibility(View.GONE);
widgetPreview.findViewById(R.id.imgvBackground).setVisibility(View.VISIBLE);
loadCover(R.id.imgvBackground, new FastBlurTransformation());
opacitySeekBar.setEnabled(false);
opacitySeekBar.setProgress(100);
} else {
widgetPreview.findViewById(R.id.imgvCover).setVisibility(View.VISIBLE);
widgetPreview.findViewById(R.id.imgvBackground).setVisibility(View.GONE);
widgetPreview.findViewById(R.id.widgetLayout).setBackgroundColor(PlayerWidget.DEFAULT_COLOR);
opacitySeekBar.setEnabled(true);
int radius = getResources().getDimensionPixelSize(R.dimen.widget_inner_radius);
loadCover(R.id.imgvCover, new RoundedCorners(radius));
}
}
private void loadCover(int viewId, Transformation<android.graphics.Bitmap> transform) {
ImageView target = findViewById(viewId);
Glide.with(this)
.asBitmap()
.load(Feed.PREFIX_GENERATIVE_COVER)
.dontAnimate()
.transform(new FitCenter(), transform)
.into(target);
}
private void confirmCreateWidget() {
@ -128,6 +164,7 @@ public class WidgetConfigActivity extends ToolbarActivity {
editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked());
editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind.isChecked());
editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward.isChecked());
editor.putBoolean(PlayerWidget.KEY_WIDGET_COVER_BACKGROUND + appWidgetId, ckCoverAsBcg.isChecked());
editor.apply();
Intent resultValue = new Intent();

View File

@ -13,13 +13,13 @@ import android.view.View;
import android.widget.RemoteViews;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter;
import de.danoeh.antennapod.ui.common.Converter;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.model.playback.MediaType;
@ -30,6 +30,7 @@ import de.danoeh.antennapod.ui.appstartintent.PlaybackSpeedActivityStarter;
import de.danoeh.antennapod.ui.appstartintent.VideoPlayerActivityStarter;
import de.danoeh.antennapod.ui.episodes.ImageResourceUtils;
import de.danoeh.antennapod.ui.episodes.TimeSpeedConverter;
import de.danoeh.antennapod.ui.glide.FastBlurTransformation;
/**
* Updates the state of the player widget.
@ -79,40 +80,10 @@ public abstract class WidgetUpdater {
views = new RemoteViews(context.getPackageName(), R.layout.player_widget);
if (widgetState.media != null) {
Bitmap icon;
int iconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer);
views.setOnClickPendingIntent(R.id.imgvCover, startMediaPlayer);
views.setOnClickPendingIntent(R.id.butPlaybackSpeed, startPlaybackSpeedDialog);
int radius = context.getResources().getDimensionPixelSize(R.dimen.widget_inner_radius);
RequestOptions options = new RequestOptions()
.dontAnimate()
.transform(new FitCenter(), new RoundedCorners(radius));
try {
icon = Glide.with(context)
.asBitmap()
.load(widgetState.media.getImageLocation())
.apply(options)
.submit(iconSize, iconSize)
.get(500, TimeUnit.MILLISECONDS);
views.setImageViewBitmap(R.id.imgvCover, icon);
} catch (Throwable tr1) {
try {
icon = Glide.with(context)
.asBitmap()
.load(ImageResourceUtils.getFallbackImageLocation(widgetState.media))
.apply(options)
.submit(iconSize, iconSize)
.get(500, TimeUnit.MILLISECONDS);
views.setImageViewBitmap(R.id.imgvCover, icon);
} catch (Throwable tr2) {
Log.e(TAG, "Error loading the media icon for the widget", tr2);
views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher);
}
}
views.setTextViewText(R.id.txtvTitle, widgetState.media.getEpisodeTitle());
views.setViewVisibility(R.id.txtvTitle, View.VISIBLE);
views.setViewVisibility(R.id.txtNoPlaying, View.GONE);
@ -177,6 +148,7 @@ public abstract class WidgetUpdater {
boolean showRewind = prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + id, false);
boolean showFastForward = prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + id, false);
boolean showSkip = prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + id, false);
boolean showCoverAsBcg = prefs.getBoolean(PlayerWidget.KEY_WIDGET_COVER_BACKGROUND + id, false);
if (showPlaybackSpeed || showRewind || showSkip || showFastForward) {
views.setInt(R.id.extendedButtonsContainer, "setVisibility", View.VISIBLE);
@ -190,6 +162,34 @@ public abstract class WidgetUpdater {
views.setInt(R.id.butPlay, "setVisibility", View.VISIBLE);
}
if (showCoverAsBcg) {
views.setViewVisibility(R.id.imgvCover, View.GONE);
views.setViewVisibility(R.id.imgvBackground, View.VISIBLE);
int iconSize = 4 * context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
Bitmap icon = null;
if (widgetState.media != null) {
icon = loadCover(context, iconSize, widgetState.media, new FastBlurTransformation());
}
if (icon != null) {
views.setImageViewBitmap(R.id.imgvBackground, icon);
} else {
views.setViewVisibility(R.id.imgvBackground, View.GONE);
}
} else {
views.setViewVisibility(R.id.imgvCover, View.VISIBLE);
views.setViewVisibility(R.id.imgvBackground, View.GONE);
int iconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
int radius = context.getResources().getDimensionPixelSize(R.dimen.widget_inner_radius);
Bitmap icon = null;
if (widgetState.media != null) {
icon = loadCover(context, iconSize, widgetState.media, new RoundedCorners(radius));
}
if (icon != null) {
views.setImageViewBitmap(R.id.imgvCover, icon);
} else {
views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher);
}
}
int backgroundColor = prefs.getInt(PlayerWidget.KEY_WIDGET_COLOR + id, PlayerWidget.DEFAULT_COLOR);
views.setInt(R.id.widgetLayout, "setBackgroundColor", backgroundColor);
@ -197,6 +197,31 @@ public abstract class WidgetUpdater {
}
}
private static Bitmap loadCover(Context context, int iconSize, Playable media, Transformation<Bitmap> transform) {
try {
return Glide.with(context)
.asBitmap()
.load(media.getImageLocation())
.dontAnimate()
.transform(transform)
.submit(iconSize, iconSize)
.get(500, TimeUnit.MILLISECONDS);
} catch (Throwable tr1) {
try {
return Glide.with(context)
.asBitmap()
.load(ImageResourceUtils.getFallbackImageLocation(media))
.dontAnimate()
.transform(transform)
.submit(iconSize, iconSize)
.get(500, TimeUnit.MILLISECONDS);
} catch (Throwable tr2) {
Log.e(TAG, "Error loading the media icon for the widget", tr2);
return null;
}
}
}
/**
* Returns number of cells needed for given size of the widget.
*

View File

@ -18,6 +18,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:importantForAccessibility="no"
app:srcCompat="@drawable/teaser" />
<include
@ -47,7 +48,6 @@
android:orientation="horizontal">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/widget_opacity"
@ -108,6 +108,13 @@
android:layout_weight="1"
android:text="@string/skip_episode_label" />
<CheckBox
android:id="@+id/ckCoverAsBcg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cover_as_background" />
</LinearLayout>
<Button

View File

@ -13,6 +13,15 @@
android:background="#262C31"
tools:ignore="UselessParent">
<ImageView
android:id="@+id/imgvBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:alpha="0.6"
android:scaleType="centerCrop"
android:importantForAccessibility="no" />
<ImageButton
android:id="@+id/butPlay"
android:layout_width="@android:dimen/app_icon_size"
@ -38,16 +47,19 @@
<ImageView
android:id="@+id/imgvCover"
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="match_parent"
android:layout_height="@android:dimen/app_icon_size"
android:src="@mipmap/ic_launcher"
android:layout_gravity="center_vertical"
android:importantForAccessibility="no"
android:layout_margin="12dp" />
android:layout_marginStart="12dp"
android:layout_marginVertical="12dp" />
<LinearLayout
android:id="@+id/layout_center"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:layout_marginStart="12dp"
android:orientation="vertical">
<TextView