From e111d4f11344effcb2d2ba0bfff2ac9e09ec79f4 Mon Sep 17 00:00:00 2001 From: tkashkin Date: Wed, 19 Jul 2017 23:45:56 +0300 Subject: [PATCH] Added card-like widget --- app/src/main/AndroidManifest.xml | 12 + .../gramophone/appwidgets/AppWidgetCard.java | 220 ++++++++++++++++++ .../gramophone/appwidgets/BootReceiver.java | 3 +- .../gramophone/service/MusicService.java | 6 + .../drawable-v21/widget_selector_light.xml | 3 + app/src/main/res/drawable/card.xml | 45 ++++ .../res/drawable/widget_selector_light.xml | 9 + app/src/main/res/layout/app_widget_card.xml | 134 +++++++++++ app/src/main/res/values/dimens.xml | 5 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/app_widget_card_info.xml | 12 + 11 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/appwidgets/AppWidgetCard.java create mode 100644 app/src/main/res/drawable-v21/widget_selector_light.xml create mode 100644 app/src/main/res/drawable/card.xml create mode 100644 app/src/main/res/drawable/widget_selector_light.xml create mode 100644 app/src/main/res/layout/app_widget_card.xml create mode 100644 app/src/main/res/xml/app_widget_card_info.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1ee61abb..d0c73348 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -190,6 +190,18 @@ android:name="android.appwidget.provider" android:resource="@xml/app_widget_small_info" /> + + + + + + + diff --git a/app/src/main/java/com/kabouzeid/gramophone/appwidgets/AppWidgetCard.java b/app/src/main/java/com/kabouzeid/gramophone/appwidgets/AppWidgetCard.java new file mode 100644 index 00000000..44325d46 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appwidgets/AppWidgetCard.java @@ -0,0 +1,220 @@ +package com.kabouzeid.gramophone.appwidgets; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v7.graphics.Palette; +import android.text.TextUtils; +import android.view.View; +import android.widget.RemoteViews; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; +import com.kabouzeid.appthemehelper.util.MaterialValueHelper; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.glide.SongGlideRequest; +import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.service.MusicService; +import com.kabouzeid.gramophone.ui.activities.MainActivity; +import com.kabouzeid.gramophone.util.Util; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public class AppWidgetCard extends BaseAppWidget { + public static final String NAME = "app_widget_card"; + + private static AppWidgetCard mInstance; + private Target target; // for cancellation + + public static synchronized AppWidgetCard getInstance() { + if (mInstance == null) { + mInstance = new AppWidgetCard(); + } + return mInstance; + } + + /** + * {@inheritDoc} + */ + @Override + public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, + final int[] appWidgetIds) { + defaultAppWidget(context, appWidgetIds); + final Intent updateIntent = new Intent(MusicService.APP_WIDGET_UPDATE); + updateIntent.putExtra(MusicService.EXTRA_APP_WIDGET_NAME, NAME); + updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + context.sendBroadcast(updateIntent); + } + + /** + * Initialize given widgets to default state, where we launch Music on + * default click and hide actions if service not running. + */ + private void defaultAppWidget(final Context context, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(), R.layout.app_widget_card); + + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + appWidgetView.setViewVisibility(R.id.image, View.INVISIBLE); + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(Util.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(Util.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(Util.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + + linkButtons(context, appWidgetView); + pushUpdate(context, appWidgetIds, appWidgetView); + } + + private void pushUpdate(final Context context, final int[] appWidgetIds, final RemoteViews views) { + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + if (appWidgetIds != null) { + appWidgetManager.updateAppWidget(appWidgetIds, views); + } else { + appWidgetManager.updateAppWidget(new ComponentName(context, getClass()), views); + } + } + + /** + * Check against {@link AppWidgetManager} if there are any instances of this + * widget. + */ + private boolean hasInstances(final Context context) { + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + final int[] mAppWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, + getClass())); + return mAppWidgetIds.length > 0; + } + + /** + * Handle a change notification coming over from + * {@link MusicService} + */ + public void notifyChange(final MusicService service, final String what) { + if (hasInstances(service)) { + if (MusicService.META_CHANGED.equals(what) || MusicService.PLAY_STATE_CHANGED.equals(what)) { + performUpdate(service, null); + } + } + } + + /** + * Update all active widget instances by pushing changes + */ + public void performUpdate(final MusicService service, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(), R.layout.app_widget_card); + + final boolean isPlaying = service.isPlaying(); + final Song song = service.getCurrentSong(); + + // Set the titles and artwork + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + if (TextUtils.isEmpty(song.artistName) || TextUtils.isEmpty(song.albumName)) { + appWidgetView.setTextViewText(R.id.text_separator, ""); + } else { + appWidgetView.setTextViewText(R.id.text_separator, "•"); + } + + appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE); + appWidgetView.setTextViewText(R.id.title, song.title); + appWidgetView.setTextViewText(R.id.artist, song.artistName); + appWidgetView.setTextViewText(R.id.album, song.albumName); + } + + // Set correct drawable for pause state + int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(Util.getTintedVectorDrawable(service, playPauseRes, MaterialValueHelper.getSecondaryTextColor(service, true)), 1f)); + + // Set prev/next button drawables + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(Util.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getSecondaryTextColor(service, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(Util.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getSecondaryTextColor(service, true)), 1f)); + + // Link actions buttons to intents + linkButtons(service, appWidgetView); + + // Load the album cover async and push the update on completion + final Context appContext = service.getApplicationContext(); + final int widgetImageSize = service.getResources().getDimensionPixelSize(R.dimen.app_widget_small_image_size); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(appContext), song) + .checkIgnoreMediaStore(appContext) + .generatePalette(service).build() + .into(new SimpleTarget(widgetImageSize, widgetImageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation glideAnimation) { + Palette palette = resource.getPalette(); + update(resource.getBitmap(), palette.getVibrantColor(palette.getMutedColor(MaterialValueHelper.getSecondaryTextColor(appContext, true)))); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + update(null, MaterialValueHelper.getSecondaryTextColor(appContext, true)); + } + + private void update(@Nullable Bitmap bitmap, int color) { + appWidgetView.setViewVisibility(R.id.image, View.VISIBLE); + + // Set correct drawable for pause state + int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(Util.getTintedVectorDrawable(service, playPauseRes, color), 1f)); + + // Set prev/next button drawables + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(Util.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, color), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(Util.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, color), 1f)); + + if (bitmap == null) { + appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art); + } else { + appWidgetView.setImageViewBitmap(R.id.image, bitmap); + } + pushUpdate(appContext, appWidgetIds, appWidgetView); + } + }); + } + }); + } + + /** + * Link up various button actions using {@link PendingIntent}. + */ + private void linkButtons(final Context context, final RemoteViews views) { + Intent action; + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(context, MusicService.class); + + // Home + action = new Intent(context, MainActivity.class); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + pendingIntent = PendingIntent.getActivity(context, 0, action, 0); + views.setOnClickPendingIntent(R.id.image, pendingIntent); + views.setOnClickPendingIntent(R.id.media_titles, pendingIntent); + + // Previous track + pendingIntent = buildPendingIntent(context, MusicService.ACTION_REWIND, serviceName); + views.setOnClickPendingIntent(R.id.button_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(context, MusicService.ACTION_TOGGLE_PAUSE, serviceName); + views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(context, MusicService.ACTION_SKIP, serviceName); + views.setOnClickPendingIntent(R.id.button_next, pendingIntent); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/appwidgets/BootReceiver.java b/app/src/main/java/com/kabouzeid/gramophone/appwidgets/BootReceiver.java index 0773efce..c80a77fe 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/appwidgets/BootReceiver.java +++ b/app/src/main/java/com/kabouzeid/gramophone/appwidgets/BootReceiver.java @@ -19,7 +19,8 @@ public class BootReceiver extends BroadcastReceiver { // Start music service if there are any existing widgets if (widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetBig.class)).length > 0 || widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetClassic.class)).length > 0 || - widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetSmall.class)).length > 0) { + widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetSmall.class)).length > 0 || + widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetCard.class)).length > 0) { final Intent serviceIntent = new Intent(context, MusicService.class); context.startService(serviceIntent); } diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java index de5e1999..4e389887 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -40,6 +40,7 @@ import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.appwidgets.AppWidgetBig; +import com.kabouzeid.gramophone.appwidgets.AppWidgetCard; import com.kabouzeid.gramophone.appwidgets.AppWidgetClassic; import com.kabouzeid.gramophone.appwidgets.AppWidgetSmall; import com.kabouzeid.gramophone.glide.BlurTransformation; @@ -128,6 +129,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private AppWidgetBig appWidgetBig = AppWidgetBig.getInstance(); private AppWidgetClassic appWidgetClassic = AppWidgetClassic.getInstance(); private AppWidgetSmall appWidgetSmall = AppWidgetSmall.getInstance(); + private AppWidgetCard appWidgetCard = AppWidgetCard.getInstance(); private Playback playback; private ArrayList playingQueue = new ArrayList<>(); @@ -1055,6 +1057,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP appWidgetBig.notifyChange(this, what); appWidgetClassic.notifyChange(this, what); appWidgetSmall.notifyChange(this, what); + appWidgetCard.notifyChange(this, what); } private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY @@ -1295,6 +1298,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } else if (AppWidgetBig.NAME.equals(command)) { final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); appWidgetBig.performUpdate(MusicService.this, ids); + } else if (AppWidgetCard.NAME.equals(command)) { + final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + appWidgetCard.performUpdate(MusicService.this, ids); } } }; diff --git a/app/src/main/res/drawable-v21/widget_selector_light.xml b/app/src/main/res/drawable-v21/widget_selector_light.xml new file mode 100644 index 00000000..86c53d73 --- /dev/null +++ b/app/src/main/res/drawable-v21/widget_selector_light.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card.xml b/app/src/main/res/drawable/card.xml new file mode 100644 index 00000000..2fc967fe --- /dev/null +++ b/app/src/main/res/drawable/card.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/widget_selector_light.xml b/app/src/main/res/drawable/widget_selector_light.xml new file mode 100644 index 00000000..b03bcafa --- /dev/null +++ b/app/src/main/res/drawable/widget_selector_light.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/app_widget_card.xml b/app/src/main/res/layout/app_widget_card.xml new file mode 100644 index 00000000..eba8957b --- /dev/null +++ b/app/src/main/res/layout/app_widget_card.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 63fc626a..7a4c26f2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -73,4 +73,9 @@ http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout 250dp 110dp + 48dp + 250dp + 56dp + 96dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4a69299..f7b4c0cf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -268,6 +268,7 @@ Phonograph - Big Phonograph - Classic Phonograph - Small + Phonograph - Card Report an issue Issue Login diff --git a/app/src/main/res/xml/app_widget_card_info.xml b/app/src/main/res/xml/app_widget_card_info.xml new file mode 100644 index 00000000..18ac28cd --- /dev/null +++ b/app/src/main/res/xml/app_widget_card_info.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file