diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 70fceaf07..0d658f3a1 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -258,7 +258,7 @@ public class RouterActivity extends AppCompatActivity { final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext); final LinearLayout rootLayout = (LinearLayout) inflater.inflate( - R.layout.preferred_player_dialog_view, null, false); + R.layout.single_choice_dialog_view, null, false); final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list); final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> { diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java index 065efca02..a16fc3cbf 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java @@ -77,11 +77,11 @@ public final class MainPlayer extends Service { static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD"; static final String ACTION_BUFFERING - = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_BUFFERING"; + = "org.schabi.newpipe.player.MainPlayer.ACTION_BUFFERING"; static final String ACTION_SHUFFLE - = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_SHUFFLE"; - - static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource"; + = "org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE"; + public static final String ACTION_RECREATE_NOTIFICATION + = "org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; /*////////////////////////////////////////////////////////////////////////// // Service's LifeCycle diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java new file mode 100644 index 000000000..599e18e65 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java @@ -0,0 +1,141 @@ +package org.schabi.newpipe.player; + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.annotation.DrawableRes; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +import org.schabi.newpipe.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +public final class NotificationConstants { + + private NotificationConstants() { } + + + public static final int NOTHING = 0; + public static final int PREVIOUS = 1; + public static final int NEXT = 2; + public static final int REWIND = 3; + public static final int FORWARD = 4; + public static final int SMART_REWIND_PREVIOUS = 5; + public static final int SMART_FORWARD_NEXT = 6; + public static final int PLAY_PAUSE = 7; + public static final int PLAY_PAUSE_BUFFERING = 8; + public static final int REPEAT = 9; + public static final int SHUFFLE = 10; + public static final int CLOSE = 11; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({NOTHING, PREVIOUS, NEXT, REWIND, FORWARD, SMART_REWIND_PREVIOUS, SMART_FORWARD_NEXT, + PLAY_PAUSE, PLAY_PAUSE_BUFFERING, REPEAT, SHUFFLE, CLOSE}) + public @interface Action { } + + @StringRes + public static final int[] ACTION_SUMMARIES = { + R.string.notification_action_nothing, + R.string.notification_action_previous, + R.string.notification_action_next, + R.string.notification_action_rewind, + R.string.notification_action_forward, + R.string.notification_action_smart_rewind_previous, + R.string.notification_action_smart_forward_next, + R.string.notification_action_play_pause, + R.string.notification_action_play_pause_buffering, + R.string.notification_action_repeat, + R.string.notification_action_shuffle, + R.string.close, + }; + + @DrawableRes + public static final int[] ACTION_ICONS = { + 0, + R.drawable.exo_icon_previous, + R.drawable.exo_icon_next, + R.drawable.exo_icon_rewind, + R.drawable.exo_icon_fastforward, + R.drawable.exo_icon_previous, + R.drawable.exo_icon_next, + R.drawable.ic_pause_white_24dp, + R.drawable.ic_hourglass_top_white_24dp, + R.drawable.exo_icon_repeat_all, + R.drawable.exo_icon_shuffle_on, + R.drawable.ic_close_white_24dp, + }; + + + @Action + public static final int[] SLOT_DEFAULTS = { + SMART_REWIND_PREVIOUS, + PLAY_PAUSE_BUFFERING, + SMART_FORWARD_NEXT, + REPEAT, + CLOSE, + }; + + @Action + public static final int[][] SLOT_ALLOWED_ACTIONS = { + new int[] {PREVIOUS, REWIND, SMART_REWIND_PREVIOUS}, + new int[] {REWIND, PLAY_PAUSE, PLAY_PAUSE_BUFFERING}, + new int[] {NEXT, FORWARD, SMART_FORWARD_NEXT, PLAY_PAUSE, PLAY_PAUSE_BUFFERING}, + new int[] {NOTHING, PREVIOUS, NEXT, REWIND, FORWARD, SMART_REWIND_PREVIOUS, + SMART_FORWARD_NEXT, REPEAT, SHUFFLE, CLOSE}, + new int[] {NOTHING, NEXT, FORWARD, SMART_FORWARD_NEXT, REPEAT, SHUFFLE, CLOSE}, + }; + + public static final int[] SLOT_PREF_KEYS = { + R.string.notification_slot_0_key, + R.string.notification_slot_1_key, + R.string.notification_slot_2_key, + R.string.notification_slot_3_key, + R.string.notification_slot_4_key, + }; + + + public static final Integer[] SLOT_COMPACT_DEFAULTS = {0, 1, 2}; + + public static final int[] SLOT_COMPACT_PREF_KEYS = { + R.string.notification_slot_compact_0_key, + R.string.notification_slot_compact_1_key, + R.string.notification_slot_compact_2_key, + }; + + /** + * @param context the context to use + * @param sharedPreferences the shared preferences to query values from + * @param slotCount remove indices >= than this value (set to {@code 5} to do nothing, or make + * it lower if there are slots with empty actions) + * @return a sorted list of the indices of the slots to use as compact slots + */ + public static List getCompactSlotsFromPreferences( + @NonNull final Context context, + final SharedPreferences sharedPreferences, + final int slotCount) { + final SortedSet compactSlots = new TreeSet<>(); + for (int i = 0; i < 3; i++) { + final int compactSlot = sharedPreferences.getInt( + context.getString(SLOT_COMPACT_PREF_KEYS[i]), Integer.MAX_VALUE); + + if (compactSlot == Integer.MAX_VALUE) { + // settings not yet populated, return default values + return new ArrayList<>(Arrays.asList(SLOT_COMPACT_DEFAULTS)); + } + + // a negative value (-1) is set when the user does not want a particular compact slot + if (compactSlot >= 0 && compactSlot < slotCount) { + compactSlots.add(compactSlot); + } + } + return new ArrayList<>(compactSlots); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index 95c604472..2c5e882bb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -20,6 +20,8 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.util.NavigationHelper; +import java.util.List; + import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.content.Context.NOTIFICATION_SERVICE; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; @@ -46,11 +48,8 @@ public final class NotificationUtil { @Nullable private static NotificationUtil instance = null; - private String notificationSlot0 = "smart_rewind_prev"; - private String notificationSlot1 = "play_pause_buffering"; - private String notificationSlot2 = "smart_forward_next"; - private String notificationSlot3 = "repeat"; - private String notificationSlot4 = "close"; + @NotificationConstants.Action + private int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone(); private NotificationManager notificationManager; private NotificationCompat.Builder notificationBuilder; @@ -91,35 +90,28 @@ public final class NotificationUtil { final NotificationCompat.Builder builder = new NotificationCompat.Builder(player.context, player.context.getString(R.string.notification_channel_id)); - final String compactView = player.sharedPreferences.getString(player.context.getString( - R.string.settings_notifications_compact_view_key), "0,1,2"); - int compactSlot0 = 0; - int compactSlot1 = 1; - int compactSlot2 = 2; - try { - if (compactView != null) { - final String[] parts = compactView.split(","); - compactSlot0 = Integer.parseInt(parts[0]); - compactSlot1 = Integer.parseInt(parts[1]); - compactSlot2 = Integer.parseInt(parts[2]); - if (compactSlot0 > 4) { - compactSlot0 = 0; - } - if (compactSlot1 > 4) { - compactSlot1 = 1; - } - if (compactSlot2 > 4) { - compactSlot2 = 2; - } - } - } catch (final Exception e) { - e.printStackTrace(); + initializeNotificationSlots(player); + + // count the number of real slots, to make sure compact slots indices are not out of bound + int nonNothingSlotCount = 5; + if (notificationSlots[3] == NotificationConstants.NOTHING) { + --nonNothingSlotCount; + } + if (notificationSlots[4] == NotificationConstants.NOTHING) { + --nonNothingSlotCount; } + // build the compact slot indices array (need code to convert from Integer... because Java) + final List compactSlotList = NotificationConstants.getCompactSlotsFromPreferences( + player.context, player.sharedPreferences, nonNothingSlotCount); + final int[] compactSlots = new int[compactSlotList.size()]; + for (int i = 0; i < compactSlotList.size(); i++) { + compactSlots[i] = compactSlotList.get(i); + } builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(player.mediaSessionManager.getSessionToken()) - .setShowActionsInCompactView(compactSlot0, compactSlot1, compactSlot2)) + .setShowActionsInCompactView(compactSlots)) .setPriority(NotificationCompat.PRIORITY_HIGH) .setSmallIcon(R.drawable.ic_newpipe_triangle_white) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -131,7 +123,6 @@ public final class NotificationUtil { .setDeleteIntent(PendingIntent.getBroadcast(player.context, NOTIFICATION_ID, new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT)); - initializeNotificationSlots(player); updateActions(builder, player); setLargeIcon(builder, player); @@ -171,22 +162,20 @@ public final class NotificationUtil { boolean hasSlotWithBuffering() { - return notificationSlot0.equals("play_pause_buffering") - || notificationSlot1.equals("play_pause_buffering") - || notificationSlot2.equals("play_pause_buffering") - || notificationSlot3.equals("play_pause_buffering") - || notificationSlot4.equals("play_pause_buffering"); + return notificationSlots[1] == NotificationConstants.PLAY_PAUSE_BUFFERING + || notificationSlots[2] == NotificationConstants.PLAY_PAUSE_BUFFERING; } - public void cancelNotification() { + void cancelNotification() { try { if (notificationManager != null) { notificationManager.cancel(NOTIFICATION_ID); - notificationManager = null; } } catch (final Exception e) { Log.e(TAG, "Could not cancel notification", e); } + notificationManager = null; + notificationBuilder = null; } @@ -195,32 +184,25 @@ public final class NotificationUtil { ///////////////////////////////////////////////////// private void initializeNotificationSlots(final VideoPlayerImpl player) { - notificationSlot0 = player.sharedPreferences.getString( - player.context.getString(R.string.notification_action_0_key), notificationSlot0); - notificationSlot1 = player.sharedPreferences.getString( - player.context.getString(R.string.notification_action_1_key), notificationSlot1); - notificationSlot2 = player.sharedPreferences.getString( - player.context.getString(R.string.notification_action_2_key), notificationSlot2); - notificationSlot3 = player.sharedPreferences.getString( - player.context.getString(R.string.notification_action_3_key), notificationSlot3); - notificationSlot4 = player.sharedPreferences.getString( - player.context.getString(R.string.notification_action_4_key), notificationSlot4); + for (int i = 0; i < 5; ++i) { + notificationSlots[i] = player.sharedPreferences.getInt( + player.context.getString(NotificationConstants.SLOT_PREF_KEYS[i]), + NotificationConstants.SLOT_DEFAULTS[i]); + } } @SuppressLint("RestrictedApi") private void updateActions(final NotificationCompat.Builder builder, final VideoPlayerImpl player) { builder.mActions.clear(); - addAction(builder, player, notificationSlot0); - addAction(builder, player, notificationSlot1); - addAction(builder, player, notificationSlot2); - addAction(builder, player, notificationSlot3); - addAction(builder, player, notificationSlot4); + for (int i = 0; i < 5; ++i) { + addAction(builder, player, notificationSlots[i]); + } } private void addAction(final NotificationCompat.Builder builder, final VideoPlayerImpl player, - final String slot) { + @NotificationConstants.Action final int slot) { final NotificationCompat.Action action = getAction(player, slot); if (action != null) { builder.addAction(action); @@ -228,23 +210,42 @@ public final class NotificationUtil { } @Nullable - private NotificationCompat.Action getAction(final VideoPlayerImpl player, - final String slot) { - switch (slot) { - case "play_pause_buffering": - if (player.getCurrentState() == BasePlayer.STATE_PREFLIGHT - || player.getCurrentState() == BasePlayer.STATE_BLOCKED - || player.getCurrentState() == BasePlayer.STATE_BUFFERING) { - return getAction(player, R.drawable.ic_hourglass_top_white_24dp, - "Buffering", ACTION_BUFFERING); + private NotificationCompat.Action getAction( + final VideoPlayerImpl player, + @NotificationConstants.Action final int selectedAction) { + final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction]; + switch (selectedAction) { + case NotificationConstants.PREVIOUS: + return getAction(player, baseActionIcon, "Previous", ACTION_PLAY_PREVIOUS); + + case NotificationConstants.NEXT: + return getAction(player, baseActionIcon, "Next", ACTION_PLAY_NEXT); + + case NotificationConstants.REWIND: + return getAction(player, baseActionIcon, "Rewind", ACTION_FAST_REWIND); + + case NotificationConstants.FORWARD: + return getAction(player, baseActionIcon, "Forward", ACTION_FAST_FORWARD); + + case NotificationConstants.SMART_REWIND_PREVIOUS: + if (player.playQueue != null && player.playQueue.size() > 1) { + return getAction(player, R.drawable.exo_notification_previous, + "Previous", ACTION_PLAY_PREVIOUS); } else { - return getAction(player, - player.isPlaying() ? R.drawable.exo_notification_pause - : R.drawable.exo_notification_play, - player.isPlaying() ? "Pause" : "Play", - ACTION_PLAY_PAUSE); + return getAction(player, R.drawable.exo_controls_rewind, + "Rewind", ACTION_FAST_REWIND); } - case "play_pause": + + case NotificationConstants.SMART_FORWARD_NEXT: + if (player.playQueue != null && player.playQueue.size() > 1) { + return getAction(player, R.drawable.exo_notification_next, + "Next", ACTION_PLAY_NEXT); + } else { + return getAction(player, R.drawable.exo_controls_fastforward, + "Forward", ACTION_FAST_FORWARD); + } + + case NotificationConstants.PLAY_PAUSE: final boolean pauseOrPlay = player.isPlaying() || player.getCurrentState() == BasePlayer.STATE_PREFLIGHT || player.getCurrentState() == BasePlayer.STATE_BLOCKED @@ -254,48 +255,38 @@ public final class NotificationUtil { : R.drawable.exo_notification_play, pauseOrPlay ? "Pause" : "Play", ACTION_PLAY_PAUSE); - case "rewind": - return getAction(player, R.drawable.exo_controls_rewind, - "Rewind", ACTION_FAST_REWIND); - case "smart_rewind_prev": - if (player.playQueue != null && player.playQueue.size() > 1) { - return getAction(player, R.drawable.exo_notification_previous, - "Prev", ACTION_PLAY_PREVIOUS); + + case NotificationConstants.PLAY_PAUSE_BUFFERING: + if (player.getCurrentState() == BasePlayer.STATE_PREFLIGHT + || player.getCurrentState() == BasePlayer.STATE_BLOCKED + || player.getCurrentState() == BasePlayer.STATE_BUFFERING) { + return getAction(player, R.drawable.ic_hourglass_top_white_24dp_png, + "Buffering", ACTION_BUFFERING); } else { - return getAction(player, R.drawable.exo_controls_rewind, - "Rewind", ACTION_FAST_REWIND); + return getAction(player, + player.isPlaying() ? R.drawable.exo_notification_pause + : R.drawable.exo_notification_play, + player.isPlaying() ? "Pause" : "Play", + ACTION_PLAY_PAUSE); } - case "forward": - return getAction(player, R.drawable.exo_controls_fastforward, - "Forward", ACTION_FAST_FORWARD); - case "smart_forward_next": - if (player.playQueue != null && player.playQueue.size() > 1) { - return getAction(player, R.drawable.exo_notification_next, - "Next", ACTION_PLAY_NEXT); - } else { - return getAction(player, R.drawable.exo_controls_fastforward, - "Forward", ACTION_FAST_FORWARD); - } - case "next": - return getAction(player, R.drawable.exo_notification_next, - "Next", ACTION_PLAY_NEXT); - case "prev": - return getAction(player, R.drawable.exo_notification_previous, - "Prev", ACTION_PLAY_PREVIOUS); - case "repeat": + + case NotificationConstants.REPEAT: return getAction(player, getRepeatModeDrawable(player.getRepeatMode()), getRepeatModeTitle(player.getRepeatMode()), ACTION_REPEAT); - case "shuffle": + + case NotificationConstants.SHUFFLE: final boolean shuffled = player.playQueue != null && player.playQueue.isShuffled(); return getAction(player, shuffled ? R.drawable.exo_controls_shuffle_on : R.drawable.exo_controls_shuffle_off, shuffled ? "ShuffleOn" : "ShuffleOff", ACTION_SHUFFLE); - case "close": - return getAction(player, R.drawable.ic_close_white_24dp, + + case NotificationConstants.CLOSE: + return getAction(player, R.drawable.ic_close_white_24dp_png, "Close", ACTION_CLOSE); - case "n/a": + + case NotificationConstants.NOTHING: default: // do nothing return null; diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java index 024be26dc..c1171efb7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java @@ -112,6 +112,7 @@ import static org.schabi.newpipe.player.MainPlayer.ACTION_OPEN_CONTROLS; import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; @@ -1179,6 +1180,7 @@ public class VideoPlayerImpl extends VideoPlayer intentFilter.addAction(ACTION_FAST_FORWARD); intentFilter.addAction(ACTION_BUFFERING); intentFilter.addAction(ACTION_SHUFFLE); + intentFilter.addAction(ACTION_RECREATE_NOTIFICATION); intentFilter.addAction(VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED); intentFilter.addAction(VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED); @@ -1236,6 +1238,9 @@ public class VideoPlayerImpl extends VideoPlayer case ACTION_SHUFFLE: onShuffleClicked(); break; + case ACTION_RECREATE_NOTIFICATION: + resetNotification(true); + break; case Intent.ACTION_HEADSET_PLUG: //FIXME /*notificationManager.cancel(NOTIFICATION_ID); mediaSessionManager.dispose(); diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.java new file mode 100644 index 000000000..f4bbc96a7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.java @@ -0,0 +1,271 @@ +package org.schabi.newpipe.settings; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.fragment.app.Fragment; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.NotificationConstants; +import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.views.FocusOverlayView; + +import java.util.List; + +public class NotificationSettingsFragment extends Fragment { + + private Switch scaleSwitch; + private NotificationSlot[] notificationSlots; + + private SharedPreferences pref; + private List compactSlots; + private String scaleKey; + + //////////////////////////////////////////////////////////////////////////// + // Lifecycle + //////////////////////////////////////////////////////////////////////////// + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + pref = PreferenceManager.getDefaultSharedPreferences(requireContext()); + scaleKey = getString(R.string.scale_to_square_image_in_notifications_key); + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + final ViewGroup container, + @Nullable final Bundle savedInstanceState) { + return inflater.inflate(R.layout.settings_notification, container, false); + } + + @Override + public void onViewCreated(@NonNull final View rootView, + @Nullable final Bundle savedInstanceState) { + super.onViewCreated(rootView, savedInstanceState); + + setupScaleSwitch(rootView); + setupActions(rootView); + } + + @Override + public void onResume() { + super.onResume(); + ThemeHelper.setTitleToAppCompatActivity(getActivity(), + getString(R.string.settings_category_notification_title)); + } + + @Override + public void onPause() { + super.onPause(); + saveChanges(); + requireContext().sendBroadcast(new Intent(MainPlayer.ACTION_RECREATE_NOTIFICATION)); + } + + + //////////////////////////////////////////////////////////////////////////// + // Setup + //////////////////////////////////////////////////////////////////////////// + + private void setupScaleSwitch(@NonNull final View view) { + scaleSwitch = view.findViewById(R.id.notificationScaleSwitch); + scaleSwitch.setChecked(pref.getBoolean(scaleKey, false)); + + view.findViewById(R.id.notificationScaleSwitchClickableArea) + .setOnClickListener(v -> scaleSwitch.toggle()); + } + + private void setupActions(@NonNull final View view) { + compactSlots = + NotificationConstants.getCompactSlotsFromPreferences(requireContext(), pref, 5); + notificationSlots = new NotificationSlot[5]; + for (int i = 0; i < 5; i++) { + notificationSlots[i] = new NotificationSlot(i, view); + } + } + + + //////////////////////////////////////////////////////////////////////////// + // Saving + //////////////////////////////////////////////////////////////////////////// + + private void saveChanges() { + final SharedPreferences.Editor editor = pref.edit(); + editor.putBoolean(scaleKey, scaleSwitch.isChecked()); + + for (int i = 0; i < 3; i++) { + editor.putInt(getString(NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]), + (i < compactSlots.size() ? compactSlots.get(i) : -1)); + } + + for (int i = 0; i < 5; i++) { + editor.putInt(getString(NotificationConstants.SLOT_PREF_KEYS[i]), + notificationSlots[i].selectedAction); + } + + editor.apply(); + } + + + //////////////////////////////////////////////////////////////////////////// + // Notification action + //////////////////////////////////////////////////////////////////////////// + + private static final int[] SLOT_ITEMS = { + R.id.notificationAction0, + R.id.notificationAction1, + R.id.notificationAction2, + R.id.notificationAction3, + R.id.notificationAction4, + }; + + private static final int[] SLOT_TITLES = { + R.string.notification_action_0_title, + R.string.notification_action_1_title, + R.string.notification_action_2_title, + R.string.notification_action_3_title, + R.string.notification_action_4_title, + }; + + private class NotificationSlot { + + final int i; + @NotificationConstants.Action int selectedAction; + + ImageView icon; + TextView summary; + + NotificationSlot(final int actionIndex, final View parentView) { + this.i = actionIndex; + + final View view = parentView.findViewById(SLOT_ITEMS[i]); + setupSelectedAction(view); + setupTitle(view); + setupCheckbox(view); + } + + void setupTitle(final View view) { + ((TextView) view.findViewById(R.id.notificationActionTitle)) + .setText(SLOT_TITLES[i]); + view.findViewById(R.id.notificationActionClickableArea).setOnClickListener( + v -> openActionChooserDialog()); + } + + void setupCheckbox(final View view) { + final CheckBox compactSlotCheckBox = view.findViewById(R.id.notificationActionCheckBox); + compactSlotCheckBox.setChecked(compactSlots.contains(i)); + view.findViewById(R.id.notificationActionCheckBoxClickableArea).setOnClickListener( + v -> { + if (compactSlotCheckBox.isChecked()) { + compactSlots.remove((Integer) i); + } else if (compactSlots.size() < 3) { + compactSlots.add(i); + } else { + Toast.makeText(requireContext(), + R.string.notification_actions_at_most_three, + Toast.LENGTH_SHORT).show(); + return; + } + + compactSlotCheckBox.toggle(); + }); + } + + void setupSelectedAction(final View view) { + icon = view.findViewById(R.id.notificationActionIcon); + summary = view.findViewById(R.id.notificationActionSummary); + selectedAction = pref.getInt(getString(NotificationConstants.SLOT_PREF_KEYS[i]), + NotificationConstants.SLOT_DEFAULTS[i]); + updateInfo(); + } + + void updateInfo() { + if (NotificationConstants.ACTION_ICONS[selectedAction] == 0) { + icon.setImageDrawable(null); + } else { + icon.setImageDrawable(AppCompatResources.getDrawable(requireContext(), + NotificationConstants.ACTION_ICONS[selectedAction])); + } + + summary.setText(NotificationConstants.ACTION_SUMMARIES[selectedAction]); + } + + void openActionChooserDialog() { + final LayoutInflater inflater = LayoutInflater.from(requireContext()); + final LinearLayout rootLayout = (LinearLayout) inflater.inflate( + R.layout.single_choice_dialog_view, null, false); + final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list); + + final AlertDialog alertDialog = new AlertDialog.Builder(requireContext()) + .setTitle(SLOT_TITLES[i]) + .setView(radioGroup) + .setCancelable(true) + .create(); + + final View.OnClickListener radioButtonsClickListener = v -> { + final int id = ((RadioButton) v).getId(); + selectedAction = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id]; + updateInfo(); + alertDialog.dismiss(); + }; + + for (int id = 0; id < NotificationConstants.SLOT_ALLOWED_ACTIONS[i].length; ++id) { + final int action = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id]; + final RadioButton radioButton + = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null); + + // if present set action icon with correct color + if (NotificationConstants.ACTION_ICONS[action] != 0) { + final Drawable drawable = AppCompatResources.getDrawable(requireContext(), + NotificationConstants.ACTION_ICONS[action]); + if (drawable != null) { + final int color = ThemeHelper.resolveColorFromAttr(requireContext(), + android.R.attr.textColorPrimary); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + drawable.setTint(color); + } else { + drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + radioButton.setCompoundDrawablesWithIntrinsicBounds( + null, null, drawable, null); + } + } + + radioButton.setText(NotificationConstants.ACTION_SUMMARIES[action]); + radioButton.setChecked(action == selectedAction); + radioButton.setId(id); + radioButton.setLayoutParams(new RadioGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + radioButton.setOnClickListener(radioButtonsClickListener); + radioGroup.addView(radioButton); + } + alertDialog.show(); + + if (DeviceUtils.isTv(requireContext())) { + FocusOverlayView.setupFocusObserver(alertDialog); + } + } + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_close_white_24dp_png.png b/app/src/main/res/drawable-hdpi/ic_close_white_24dp_png.png new file mode 100644 index 000000000..2f73a04b1 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_close_white_24dp_png.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_hourglass_top_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_hourglass_top_white_24dp_png.png similarity index 100% rename from app/src/main/res/drawable-hdpi/ic_hourglass_top_white_24dp.png rename to app/src/main/res/drawable-hdpi/ic_hourglass_top_white_24dp_png.png diff --git a/app/src/main/res/drawable-mdpi/ic_close_white_24dp_png.png b/app/src/main/res/drawable-mdpi/ic_close_white_24dp_png.png new file mode 100644 index 000000000..d8aa2f7c4 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_close_white_24dp_png.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_hourglass_top_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_hourglass_top_white_24dp_png.png similarity index 100% rename from app/src/main/res/drawable-mdpi/ic_hourglass_top_white_24dp.png rename to app/src/main/res/drawable-mdpi/ic_hourglass_top_white_24dp_png.png diff --git a/app/src/main/res/drawable-xhdpi/ic_close_white_24dp_png.png b/app/src/main/res/drawable-xhdpi/ic_close_white_24dp_png.png new file mode 100644 index 000000000..40782d057 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_close_white_24dp_png.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_hourglass_top_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_hourglass_top_white_24dp_png.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/ic_hourglass_top_white_24dp.png rename to app/src/main/res/drawable-xhdpi/ic_hourglass_top_white_24dp_png.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp_png.png b/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp_png.png new file mode 100644 index 000000000..2cd1a8865 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp_png.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_hourglass_top_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_hourglass_top_white_24dp_png.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_hourglass_top_white_24dp.png rename to app/src/main/res/drawable-xxhdpi/ic_hourglass_top_white_24dp_png.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp_png.png b/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp_png.png new file mode 100644 index 000000000..4d278c5bf Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp_png.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_hourglass_top_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_hourglass_top_white_24dp_png.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/ic_hourglass_top_white_24dp.png rename to app/src/main/res/drawable-xxxhdpi/ic_hourglass_top_white_24dp_png.png diff --git a/app/src/main/res/drawable/ic_hourglass_top_white_24dp.xml b/app/src/main/res/drawable/ic_hourglass_top_white_24dp.xml new file mode 100644 index 000000000..d6156dfa1 --- /dev/null +++ b/app/src/main/res/drawable/ic_hourglass_top_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/list_radio_icon_item.xml b/app/src/main/res/layout/list_radio_icon_item.xml index 947747c16..3446e351c 100644 --- a/app/src/main/res/layout/list_radio_icon_item.xml +++ b/app/src/main/res/layout/list_radio_icon_item.xml @@ -10,10 +10,8 @@ android:maxLines="2" android:minHeight="?attr/listPreferredItemHeightSmall" android:paddingEnd="?attr/listPreferredItemPaddingRight" - android:paddingLeft="?attr/listPreferredItemPaddingLeft" - android:paddingRight="?attr/listPreferredItemPaddingRight" android:paddingStart="?attr/listPreferredItemPaddingLeft" android:background="?attr/checked_selector" android:textColor="?attr/textColorAlertDialogListItem" - tools:drawableLeft="?attr/ic_play" + tools:drawableLeft="?attr/ic_play_arrow" tools:text="Lorem ipsum dolor sit amet" /> diff --git a/app/src/main/res/layout/settings_notification.xml b/app/src/main/res/layout/settings_notification.xml new file mode 100644 index 000000000..61b7f1aad --- /dev/null +++ b/app/src/main/res/layout/settings_notification.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_notification_action.xml b/app/src/main/res/layout/settings_notification_action.xml new file mode 100644 index 000000000..8ef2d8ab2 --- /dev/null +++ b/app/src/main/res/layout/settings_notification_action.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/preferred_player_dialog_view.xml b/app/src/main/res/layout/single_choice_dialog_view.xml similarity index 100% rename from app/src/main/res/layout/preferred_player_dialog_view.xml rename to app/src/main/res/layout/single_choice_dialog_view.xml diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 52b43002d..0aa1435c1 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -118,108 +118,15 @@ 0,1,2 scale_to_square_image_in_notifications - prev - next - rewind - forward - smart_rewind_prev - smart_forward_next - play_pause_buffering - play_pause - repeat - shuffle - close - n/a + notification_slot_0_key + notification_slot_1_key + notification_slot_2_key + notification_slot_3_key + notification_slot_4_key - notification_action_0_key - @string/notification_action_smart_rewind_prev_key - - @string/notification_action_smart_rewind_prev_value - @string/notification_action_prev_value - @string/notification_action_rewind_value - - - @string/notification_action_smart_rewind_prev_key - @string/notification_action_prev_key - @string/notification_action_rewind_key - - - notification_action_1_key - @string/notification_action_play_pause_buffering_key - - @string/notification_action_play_pause_buffering_value - @string/notification_action_play_pause_value - @string/notification_action_rewind_value - - - @string/notification_action_play_pause_buffering_key - @string/notification_action_play_pause_key - @string/notification_action_rewind_key - - - - notification_action_2_key - @string/notification_action_smart_forward_next_key - - @string/notification_action_smart_forward_next_value - @string/notification_action_forward_value - @string/notification_action_next_value - @string/notification_action_play_pause_buffering_value - @string/notification_action_play_pause_value - - - @string/notification_action_smart_forward_next_key - @string/notification_action_forward_key - @string/notification_action_next_key - @string/notification_action_play_pause_buffering_key - @string/notification_action_play_pause_key - - - notification_action_3_key - @string/notification_action_repeat_key - - @string/notification_action_repeat_value - @string/notification_action_shuffle_value - @string/notification_action_prev_value - @string/notification_action_forward_value - @string/notification_action_smart_forward_next_value - @string/notification_action_rewind_value - @string/notification_action_smart_rewind_prev_value - @string/notification_action_close_value - @string/notification_action_n_a_value - - - @string/notification_action_repeat_key - @string/notification_action_shuffle_key - @string/notification_action_prev_key - @string/notification_action_forward_key - @string/notification_action_smart_forward_next_key - @string/notification_action_rewind_key - @string/notification_action_smart_rewind_prev_key - @string/notification_action_close_key - @string/notification_action_n_a_key - - - notification_action_4_key - @string/notification_action_close_key - - @string/notification_action_close_value - @string/notification_action_repeat_value - @string/notification_action_shuffle_value - @string/notification_action_next_value - @string/notification_action_forward_value - @string/notification_action_smart_forward_next_value - @string/notification_action_n_a_value - - - @string/notification_action_close_key - @string/notification_action_repeat_key - @string/notification_action_shuffle_key - @string/notification_action_next_key - @string/notification_action_forward_key - @string/notification_action_smart_forward_next_key - @string/notification_action_n_a_key - + notification_slot_compact_0_key + notification_slot_compact_1_key + notification_slot_compact_2_key video_mp4 video_webm diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 45d806728..21c734741 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,21 +66,20 @@ Third action button Fourth action button Fifth action button - Notification compact view - Notification slots to show in compact view - - Previous - Next - Rewind - Forward - Rewind / Previous - Forward / Next - Play / Pause / Buffering - Play / Pause - Repeat - Shuffle - Close - Nothing + Edit each notification action below by tapping on it.\nSelect up to three of them to be shown in the compact notification by using the checkboxes on the right. + You can select at most three actions to show in the compact notification! + + Previous + Next + Rewind + Forward + Rewind / Previous + Forward / Next + Play / Pause / Buffering + Play / Pause + Repeat + Shuffle + Nothing Audio Default audio format @@ -154,7 +153,7 @@ Other Debug Updates - Notifications + Notification Playing in background Playing in popup mode Queued on background player diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml index 7b25a4ab5..e45641f91 100644 --- a/app/src/main/res/xml/appearance_settings.xml +++ b/app/src/main/res/xml/appearance_settings.xml @@ -34,70 +34,4 @@ android:title="@string/caption_setting_title" app:iconSpaceReserved="false" /> - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/xml/main_settings.xml b/app/src/main/res/xml/main_settings.xml index 604f3bb05..af093a757 100644 --- a/app/src/main/res/xml/main_settings.xml +++ b/app/src/main/res/xml/main_settings.xml @@ -35,6 +35,12 @@ android:icon="?attr/ic_language" android:title="@string/content"/> + +