From 738e2ac3443fa256cbdb7c452cf28c93bf9dfdd5 Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Mon, 12 Feb 2018 19:44:35 +0100 Subject: [PATCH] merge RouterActivity and RouterVideoActivity --- app/src/main/AndroidManifest.xml | 113 ++--- .../org/schabi/newpipe/RouterActivity.java | 460 ++++++++++++++++-- .../schabi/newpipe/RouterPlayerActivity.java | 413 ---------------- .../ic_info_outline_black_24dp.png | Bin 0 -> 487 bytes .../ic_info_outline_white_24dp.png | Bin 0 -> 485 bytes .../ic_info_outline_black_24dp.png | Bin 0 -> 323 bytes .../ic_info_outline_white_24dp.png | Bin 0 -> 320 bytes .../ic_info_outline_black_24dp.png | Bin 0 -> 640 bytes .../ic_info_outline_white_24dp.png | Bin 0 -> 655 bytes .../ic_info_outline_black_24dp.png | Bin 0 -> 940 bytes .../ic_info_outline_white_24dp.png | Bin 0 -> 953 bytes .../ic_info_outline_black_24dp.png | Bin 0 -> 1256 bytes .../ic_info_outline_white_24dp.png | Bin 0 -> 1279 bytes app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 2 + 17 files changed, 465 insertions(+), 526 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java create mode 100644 app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png create mode 100644 app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_info_outline_black_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bc3dc62e6..f286ee76c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -122,8 +122,12 @@ + + @@ -169,6 +173,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -176,17 +215,8 @@ - - - - - - - - - - + @@ -195,68 +225,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 8aaa248dd..0586a86be 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -1,51 +1,80 @@ package org.schabi.newpipe; +import android.app.IntentService; +import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; +import android.os.PersistableBundle; +import android.preference.PreferenceManager; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; import android.widget.Toast; +import org.schabi.newpipe.extractor.Info; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.StreamingService.LinkType; +import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.playlist.PlaylistInfo; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.playlist.ChannelPlayQueue; +import org.schabi.newpipe.playlist.PlayQueue; +import org.schabi.newpipe.playlist.PlaylistPlayQueue; +import org.schabi.newpipe.playlist.SinglePlayQueue; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.PermissionHelper; +import org.schabi.newpipe.util.ThemeHelper; +import java.io.Serializable; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import icepick.Icepick; import icepick.State; import io.reactivex.Observable; +import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; -/* - * Copyright (C) Christian Schabesberger 2017 - * RouterActivity.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ +import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr; /** - * This Acitivty is designed to route share/open intents to the specified service, and - * to the part of the service which can handle the url. + * Get the url from the intent and open it in the chosen preferred player */ public class RouterActivity extends AppCompatActivity { @State + protected int currentServiceId = -1; + private StreamingService currentService; + @State + protected LinkType currentLinkType; + @State + protected int selectedRadioPosition = -1; + protected int selectedPreviously = -1; + protected String currentUrl; protected CompositeDisposable disposables = new CompositeDisposable(); @@ -62,6 +91,10 @@ public class RouterActivity extends AppCompatActivity { finish(); } } + + setTheme(ThemeHelper.isLightThemeSelected(this) + ? R.style.RouterActivityThemeLight + : R.style.RouterActivityThemeDark); } @Override @@ -73,25 +106,43 @@ public class RouterActivity extends AppCompatActivity { @Override protected void onStart() { super.onStart(); + handleUrl(currentUrl); } - protected void handleUrl(String url) { - disposables.add(Observable - .fromCallable(() -> NavigationHelper.getIntentByLink(this, url)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(intent -> { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); + @Override + protected void onDestroy() { + super.onDestroy(); - finish(); - }, this::handleError) - ); + disposables.clear(); } - protected void handleError(Throwable error) { + private void handleUrl(String url) { + disposables.add(Observable + .fromCallable(() -> { + if (currentServiceId == -1) { + currentService = NewPipe.getServiceByUrl(url); + currentServiceId = currentService.getServiceId(); + currentLinkType = currentService.getLinkTypeByUrl(url); + currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType); + } else { + currentService = NewPipe.getService(currentServiceId); + } + + return currentLinkType != LinkType.NONE; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result) { + onSuccess(); + } else { + onError(); + } + }, this::handleError)); + } + + private void handleError(Throwable error) { error.printStackTrace(); if (error instanceof ExtractionException) { @@ -103,11 +154,339 @@ public class RouterActivity extends AppCompatActivity { finish(); } - @Override - protected void onDestroy() { - super.onDestroy(); + private void onError() { + Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); + finish(); + } - disposables.clear(); + protected void onSuccess() { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); + boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); + + if ((isExtAudioEnabled || isExtVideoEnabled) && currentLinkType != LinkType.STREAM) { + Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show(); + finish(); + return; + } + + // TODO: Add some sort of "capabilities" field to services (audio only, video and audio, etc.) + if (currentService == ServiceList.SoundCloud.getService()) { + handleChoice(getString(R.string.background_player_key)); + return; + } + + final String playerChoiceKey = preferences.getString(getString(R.string.preferred_player_key), getString(R.string.preferred_player_default)); + final String alwaysAskKey = getString(R.string.always_ask_player_key); + + if (playerChoiceKey.equals(alwaysAskKey)) { + showDialog(); + } else { + handleChoice(playerChoiceKey); + } + } + + private void showDialog() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this, + ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme); + + LayoutInflater inflater = LayoutInflater.from(themeWrapper); + final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false); + final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list); + + final AdapterChoiceItem[] choices = { + new AdapterChoiceItem(getString(R.string.info_screen_key), getString(R.string.info_screen), + resolveResourceIdFromAttr(themeWrapper, R.attr.info)), + new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player), + resolveResourceIdFromAttr(themeWrapper, R.attr.play)), + new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player), + resolveResourceIdFromAttr(themeWrapper, R.attr.audio)), + new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player), + resolveResourceIdFromAttr(themeWrapper, R.attr.popup)) + }; + + final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> { + final int indexOfChild = radioGroup.indexOfChild( + radioGroup.findViewById(radioGroup.getCheckedRadioButtonId())); + final AdapterChoiceItem choice = choices[indexOfChild]; + + handleChoice(choice.key); + + if (which == DialogInterface.BUTTON_POSITIVE) { + preferences.edit().putString(getString(R.string.preferred_player_key), choice.key).apply(); + } + }; + + final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper) + .setTitle(R.string.preferred_player_share_menu_title) + .setView(radioGroup) + .setCancelable(true) + .setNegativeButton(R.string.just_once, dialogButtonsClickListener) + .setPositiveButton(R.string.always, dialogButtonsClickListener) + .setOnDismissListener((dialog) -> finish()) + .create(); + + alertDialog.setOnShowListener(dialog -> { + setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1); + }); + + radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true)); + final View.OnClickListener radioButtonsClickListener = v -> { + final int indexOfChild = radioGroup.indexOfChild(v); + if (indexOfChild == -1) return; + + selectedPreviously = selectedRadioPosition; + selectedRadioPosition = indexOfChild; + + if (selectedPreviously == selectedRadioPosition) { + handleChoice(choices[selectedRadioPosition].key); + } + }; + + int id = 12345; + for (AdapterChoiceItem item : choices) { + final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null); + radioButton.setText(item.description); + radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0); + radioButton.setChecked(false); + radioButton.setId(id++); + radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + radioButton.setOnClickListener(radioButtonsClickListener); + radioGroup.addView(radioButton); + } + + if (selectedRadioPosition == -1) { + final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_player_last_selected_key), null); + if (!TextUtils.isEmpty(lastSelectedPlayer)) { + for (int i = 0; i < choices.length; i++) { + AdapterChoiceItem c = choices[i]; + if (lastSelectedPlayer.equals(c.key)) { + selectedRadioPosition = i; + break; + } + } + } + } + + selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1); + if (selectedRadioPosition != -1) { + ((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true); + } + selectedPreviously = selectedRadioPosition; + + alertDialog.show(); + } + + private void setDialogButtonsState(AlertDialog dialog, boolean state) { + final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); + final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (negativeButton == null || positiveButton == null) return; + + negativeButton.setEnabled(state); + positiveButton.setEnabled(state); + } + + private void handleChoice(final String playerChoiceKey) { + if (Arrays.asList(getResources().getStringArray(R.array.preferred_player_values_list)).contains(playerChoiceKey)) { + PreferenceManager.getDefaultSharedPreferences(this).edit() + .putString(getString(R.string.preferred_player_last_selected_key), playerChoiceKey).apply(); + } + + if (playerChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) { + PermissionHelper.showPopupEnablementToast(this); + finish(); + return; + } + + // stop and bypass FetcherService if InfoScreen was selected since + // StreamDetailFragment can fetch data itself + if(playerChoiceKey.equals(getString(R.string.info_screen_key))) { + disposables.add(Observable + .fromCallable(() -> NavigationHelper.getIntentByLink(this, currentUrl)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(intent -> { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + + finish(); + }, this::handleError) + ); + return; + } + + final Intent intent = new Intent(this, FetcherService.class); + intent.putExtra(FetcherService.KEY_CHOICE, + new Choice(currentService.getServiceId(), + currentLinkType, + currentUrl, + playerChoiceKey)); + startService(intent); + + finish(); + } + + private static class AdapterChoiceItem { + final String description, key; + @DrawableRes + final int icon; + + AdapterChoiceItem(String key, String description, int icon) { + this.description = description; + this.key = key; + this.icon = icon; + } + } + + private static class Choice implements Serializable { + final int serviceId; + final String url, playerChoice; + final LinkType linkType; + + Choice(int serviceId, LinkType linkType, String url, String playerChoice) { + this.serviceId = serviceId; + this.linkType = linkType; + this.url = url; + this.playerChoice = playerChoice; + } + + @Override + public String toString() { + return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice; + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Service Fetcher + //////////////////////////////////////////////////////////////////////////*/ + + public static class FetcherService extends IntentService { + + private static final int ID = 456; + public static final String KEY_CHOICE = "key_choice"; + private Disposable fetcher; + + public FetcherService() { + super(FetcherService.class.getSimpleName()); + } + + @Override + public void onCreate() { + super.onCreate(); + startForeground(ID, createNotification().build()); + } + + @Override + protected void onHandleIntent(@Nullable Intent intent) { + if (intent == null) return; + + final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE); + if (!(serializable instanceof Choice)) return; + Choice playerChoice = (Choice) serializable; + handleChoice(playerChoice); + } + + public void handleChoice(Choice choice) { + Single single = null; + UserAction userAction = UserAction.SOMETHING_ELSE; + + switch (choice.linkType) { + case STREAM: + single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false); + userAction = UserAction.REQUESTED_STREAM; + break; + case CHANNEL: + single = ExtractorHelper.getChannelInfo(choice.serviceId, choice.url, false); + userAction = UserAction.REQUESTED_CHANNEL; + break; + case PLAYLIST: + single = ExtractorHelper.getPlaylistInfo(choice.serviceId, choice.url, false); + userAction = UserAction.REQUESTED_PLAYLIST; + break; + } + + + if (single != null) { + final UserAction finalUserAction = userAction; + final Consumer resultHandler = getResultHandler(choice); + fetcher = single + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(info -> { + resultHandler.accept(info); + if (fetcher != null) fetcher.dispose(); + }, throwable -> ExtractorHelper.handleGeneralException(this, + choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice)); + } + } + + public Consumer getResultHandler(Choice choice) { + return info -> { + final String videoPlayerKey = getString(R.string.video_player_key); + final String backgroundPlayerKey = getString(R.string.background_player_key); + final String popupPlayerKey = getString(R.string.popup_player_key); + + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); + boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); + boolean useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this); + + PlayQueue playQueue; + String playerChoice = choice.playerChoice; + + if (info instanceof StreamInfo) { + if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) { + NavigationHelper.playOnExternalAudioPlayer(this, (StreamInfo) info); + + } else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) { + NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info); + + } else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) { + NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info); + + } else { + playQueue = new SinglePlayQueue((StreamInfo) info); + + if (playerChoice.equals(videoPlayerKey)) { + NavigationHelper.playOnMainPlayer(this, playQueue); + } else if (playerChoice.equals(backgroundPlayerKey)) { + NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true); + } else if (playerChoice.equals(popupPlayerKey)) { + NavigationHelper.enqueueOnPopupPlayer(this, playQueue, true); + } + } + } + + if (info instanceof ChannelInfo || info instanceof PlaylistInfo) { + playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info); + + if (playerChoice.equals(videoPlayerKey)) { + NavigationHelper.playOnMainPlayer(this, playQueue); + } else if (playerChoice.equals(backgroundPlayerKey)) { + NavigationHelper.playOnBackgroundPlayer(this, playQueue); + } else if (playerChoice.equals(popupPlayerKey)) { + NavigationHelper.playOnPopupPlayer(this, playQueue); + } + } + }; + } + + @Override + public void onDestroy() { + super.onDestroy(); + stopForeground(true); + if (fetcher != null) fetcher.dispose(); + } + + private NotificationCompat.Builder createNotification() { + return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) + .setOngoing(true) + .setSmallIcon(R.drawable.ic_newpipe_triangle_white) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContentTitle(getString(R.string.preferred_player_fetcher_notification_title)) + .setContentText(getString(R.string.preferred_player_fetcher_notification_message)); + } } /*////////////////////////////////////////////////////////////////////////// @@ -119,9 +498,9 @@ public class RouterActivity extends AppCompatActivity { * brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for * more details. */ - protected final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]"; + private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]"; - protected String getUrl(Intent intent) { + private String getUrl(Intent intent) { // first gather data and find service String videoUrl = null; if (intent.getData() != null) { @@ -137,7 +516,7 @@ public class RouterActivity extends AppCompatActivity { return videoUrl; } - protected String removeHeadingGibberish(final String input) { + private String removeHeadingGibberish(final String input) { int start = 0; for (int i = input.indexOf("://") - 1; i >= 0; i--) { if (!input.substring(i, i + 1).matches("\\p{L}")) { @@ -148,7 +527,7 @@ public class RouterActivity extends AppCompatActivity { return input.substring(start, input.length()); } - protected String trim(final String input) { + private String trim(final String input) { if (input == null || input.length() < 1) { return input; } else { @@ -188,5 +567,4 @@ public class RouterActivity extends AppCompatActivity { } return result.toArray(new String[result.size()]); } - } diff --git a/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java deleted file mode 100644 index 7196e413d..000000000 --- a/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java +++ /dev/null @@ -1,413 +0,0 @@ -package org.schabi.newpipe; - -import android.app.IntentService; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.PersistableBundle; -import android.preference.PreferenceManager; -import android.support.annotation.DrawableRes; -import android.support.annotation.Nullable; -import android.support.v4.app.NotificationCompat; -import android.support.v7.app.AlertDialog; -import android.text.TextUtils; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.Toast; - -import org.schabi.newpipe.extractor.Info; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.ServiceList; -import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.StreamingService.LinkType; -import org.schabi.newpipe.extractor.channel.ChannelInfo; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.playlist.PlaylistInfo; -import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.player.helper.PlayerHelper; -import org.schabi.newpipe.playlist.ChannelPlayQueue; -import org.schabi.newpipe.playlist.PlayQueue; -import org.schabi.newpipe.playlist.PlaylistPlayQueue; -import org.schabi.newpipe.playlist.SinglePlayQueue; -import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.util.ExtractorHelper; -import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.PermissionHelper; -import org.schabi.newpipe.util.ThemeHelper; - -import java.io.Serializable; -import java.util.Arrays; - -import icepick.State; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Consumer; -import io.reactivex.schedulers.Schedulers; - -import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr; - -/** - * Get the url from the intent and open it in the chosen preferred player - */ -public class RouterPlayerActivity extends RouterActivity { - - @State - protected int currentServiceId = -1; - private StreamingService currentService; - @State - protected LinkType currentLinkType; - @State - protected int selectedRadioPosition = -1; - protected int selectedPreviously = -1; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { - super.onCreate(savedInstanceState, persistentState); - setTheme(ThemeHelper.isLightThemeSelected(this) ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark); - } - - @Override - protected void handleUrl(String url) { - disposables.add(Observable - .fromCallable(() -> { - if (currentServiceId == -1) { - currentService = NewPipe.getServiceByUrl(url); - currentServiceId = currentService.getServiceId(); - currentLinkType = currentService.getLinkTypeByUrl(url); - currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType); - } else { - currentService = NewPipe.getService(currentServiceId); - } - - return currentLinkType != LinkType.NONE; - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - if (result) { - onSuccess(); - } else { - onError(); - } - }, this::handleError)); - } - - protected void onError() { - Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); - finish(); - } - - protected void onSuccess() { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); - boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); - - if ((isExtAudioEnabled || isExtVideoEnabled) && currentLinkType != LinkType.STREAM) { - Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show(); - finish(); - return; - } - - // TODO: Add some sort of "capabilities" field to services (audio only, video and audio, etc.) - if (currentService == ServiceList.SoundCloud.getService()) { - handleChoice(getString(R.string.background_player_key)); - return; - } - - final String playerChoiceKey = preferences.getString(getString(R.string.preferred_player_key), getString(R.string.preferred_player_default)); - final String alwaysAskKey = getString(R.string.always_ask_player_key); - - if (playerChoiceKey.equals(alwaysAskKey)) { - showDialog(); - } else { - handleChoice(playerChoiceKey); - } - } - - private void showDialog() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this, - ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme); - - LayoutInflater inflater = LayoutInflater.from(themeWrapper); - final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false); - final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list); - - final AdapterChoiceItem[] choices = { - new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player), - resolveResourceIdFromAttr(themeWrapper, R.attr.play)), - new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player), - resolveResourceIdFromAttr(themeWrapper, R.attr.audio)), - new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player), - resolveResourceIdFromAttr(themeWrapper, R.attr.popup)) - }; - - final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> { - final int indexOfChild = radioGroup.indexOfChild(radioGroup.findViewById(radioGroup.getCheckedRadioButtonId())); - final AdapterChoiceItem choice = choices[indexOfChild]; - - handleChoice(choice.key); - - if (which == DialogInterface.BUTTON_POSITIVE) { - preferences.edit().putString(getString(R.string.preferred_player_key), choice.key).apply(); - } - }; - - final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper) - .setTitle(R.string.preferred_player_share_menu_title) - .setView(radioGroup) - .setCancelable(true) - .setNegativeButton(R.string.just_once, dialogButtonsClickListener) - .setPositiveButton(R.string.always, dialogButtonsClickListener) - .setOnDismissListener((dialog) -> finish()) - .create(); - - alertDialog.setOnShowListener(dialog -> { - setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1); - }); - - radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true)); - final View.OnClickListener radioButtonsClickListener = v -> { - final int indexOfChild = radioGroup.indexOfChild(v); - if (indexOfChild == -1) return; - - selectedPreviously = selectedRadioPosition; - selectedRadioPosition = indexOfChild; - - if (selectedPreviously == selectedRadioPosition) { - handleChoice(choices[selectedRadioPosition].key); - } - }; - - int id = 12345; - for (AdapterChoiceItem item : choices) { - final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null); - radioButton.setText(item.description); - radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0); - radioButton.setChecked(false); - radioButton.setId(id++); - radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - radioButton.setOnClickListener(radioButtonsClickListener); - radioGroup.addView(radioButton); - } - - if (selectedRadioPosition == -1) { - final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_player_last_selected_key), null); - if (!TextUtils.isEmpty(lastSelectedPlayer)) { - for (int i = 0; i < choices.length; i++) { - AdapterChoiceItem c = choices[i]; - if (lastSelectedPlayer.equals(c.key)) { - selectedRadioPosition = i; - break; - } - } - } - } - - selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1); - if (selectedRadioPosition != -1) { - ((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true); - } - selectedPreviously = selectedRadioPosition; - - alertDialog.show(); - } - - private void setDialogButtonsState(AlertDialog dialog, boolean state) { - final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); - final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - if (negativeButton == null || positiveButton == null) return; - - negativeButton.setEnabled(state); - positiveButton.setEnabled(state); - } - - private void handleChoice(final String playerChoiceKey) { - if (Arrays.asList(getResources().getStringArray(R.array.preferred_player_values_list)).contains(playerChoiceKey)) { - PreferenceManager.getDefaultSharedPreferences(this).edit() - .putString(getString(R.string.preferred_player_last_selected_key), playerChoiceKey).apply(); - } - - if (playerChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) { - PermissionHelper.showPopupEnablementToast(this); - finish(); - return; - } - - final Intent intent = new Intent(this, FetcherService.class); - intent.putExtra(FetcherService.KEY_CHOICE, new Choice(currentService.getServiceId(), currentLinkType, currentUrl, playerChoiceKey)); - startService(intent); - - finish(); - } - - private static class AdapterChoiceItem { - final String description, key; - @DrawableRes - final int icon; - - AdapterChoiceItem(String key, String description, int icon) { - this.description = description; - this.key = key; - this.icon = icon; - } - } - - private static class Choice implements Serializable { - final int serviceId; - final String url, playerChoice; - final LinkType linkType; - - Choice(int serviceId, LinkType linkType, String url, String playerChoice) { - this.serviceId = serviceId; - this.linkType = linkType; - this.url = url; - this.playerChoice = playerChoice; - } - - @Override - public String toString() { - return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice; - } - } - - /*////////////////////////////////////////////////////////////////////////// - // Service Fetcher - //////////////////////////////////////////////////////////////////////////*/ - - public static class FetcherService extends IntentService { - - private static final int ID = 456; - public static final String KEY_CHOICE = "key_choice"; - private Disposable fetcher; - - public FetcherService() { - super(FetcherService.class.getSimpleName()); - } - - @Override - public void onCreate() { - super.onCreate(); - startForeground(ID, createNotification().build()); - } - - @Override - protected void onHandleIntent(@Nullable Intent intent) { - if (intent == null) return; - - final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE); - if (!(serializable instanceof Choice)) return; - Choice playerChoice = (Choice) serializable; - handleChoice(playerChoice); - } - - public void handleChoice(Choice choice) { - Single single = null; - UserAction userAction = UserAction.SOMETHING_ELSE; - - switch (choice.linkType) { - case STREAM: - single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false); - userAction = UserAction.REQUESTED_STREAM; - break; - case CHANNEL: - single = ExtractorHelper.getChannelInfo(choice.serviceId, choice.url, false); - userAction = UserAction.REQUESTED_CHANNEL; - break; - case PLAYLIST: - single = ExtractorHelper.getPlaylistInfo(choice.serviceId, choice.url, false); - userAction = UserAction.REQUESTED_PLAYLIST; - break; - } - - - if (single != null) { - final UserAction finalUserAction = userAction; - final Consumer resultHandler = getResultHandler(choice); - fetcher = single - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(info -> { - resultHandler.accept(info); - if (fetcher != null) fetcher.dispose(); - }, throwable -> ExtractorHelper.handleGeneralException(this, - choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice)); - } - } - - public Consumer getResultHandler(Choice choice) { - return info -> { - final String videoPlayerKey = getString(R.string.video_player_key); - final String backgroundPlayerKey = getString(R.string.background_player_key); - final String popupPlayerKey = getString(R.string.popup_player_key); - - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); - boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); - boolean useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this); - - PlayQueue playQueue; - String playerChoice = choice.playerChoice; - - if (info instanceof StreamInfo) { - if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) { - NavigationHelper.playOnExternalAudioPlayer(this, (StreamInfo) info); - - } else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) { - NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info); - - } else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) { - NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info); - - } else { - playQueue = new SinglePlayQueue((StreamInfo) info); - - if (playerChoice.equals(videoPlayerKey)) { - NavigationHelper.playOnMainPlayer(this, playQueue); - } else if (playerChoice.equals(backgroundPlayerKey)) { - NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true); - } else if (playerChoice.equals(popupPlayerKey)) { - NavigationHelper.enqueueOnPopupPlayer(this, playQueue, true); - } - } - } - - if (info instanceof ChannelInfo || info instanceof PlaylistInfo) { - playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info); - - if (playerChoice.equals(videoPlayerKey)) { - NavigationHelper.playOnMainPlayer(this, playQueue); - } else if (playerChoice.equals(backgroundPlayerKey)) { - NavigationHelper.playOnBackgroundPlayer(this, playQueue); - } else if (playerChoice.equals(popupPlayerKey)) { - NavigationHelper.playOnPopupPlayer(this, playQueue); - } - } - }; - } - - @Override - public void onDestroy() { - super.onDestroy(); - stopForeground(true); - if (fetcher != null) fetcher.dispose(); - } - - private NotificationCompat.Builder createNotification() { - return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) - .setOngoing(true) - .setSmallIcon(R.drawable.ic_newpipe_triangle_white) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setContentTitle(getString(R.string.preferred_player_fetcher_notification_title)) - .setContentText(getString(R.string.preferred_player_fetcher_notification_message)); - } - } -} diff --git a/app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4b5ab06e19d61515cf3a7395db43a81bb30a8203 GIT binary patch literal 487 zcmVS-P=?%O3jv@aJa6HGW<0ceyC!`rEoc~?j{U=`ic|b z9N*QPsMv*+MQv@1i8kR%)UIh1`=XoCqqIYBOKErvea{GWZ!y>;D~RpT`=%tUi9cM8 zjMJd-5IZXpx*;AQpQG66NVp4e3Hh{Q=T<@);yl@>u@F125nkF002ovPDHLkV1kFY-~<2w literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c7b1113cfef22bcec86ead7ae67be12326276cab GIT binary patch literal 485 zcmVW+#V#f>U=9~3v zy5k#|%`o-bRp5gvNWBjKU}@RX_o(#cOX<;tO*2A$>f4JBeW-BBCp2Wv4dAi~>H>ZU-{cI? zse=}k;97{XqEJ|gdQ)z}NLv+h2nN-m6riYd=e>Y~D%A1;c7!A-*ac%VF|-AqWJ2?3 zQF6~&s5ci5fY}sT-yed|rRg0)~VyRe(RxP5| ziKSu?+iDTDUGbuL5L2~?+LgFdoQk=|C}Pxj7RQRtyXcJ~MwepHsvL@iJ?s4SCH8$Y z6AQgYt#Ro~-1uQ)CSvZwj!tLSm1hyxCe}LeCL$stVz6(6)`@$smX==JIoA4R{{X3C VfQx`Hg^K_H002ovPDHLkV1oM>kN^Mx literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..353e064951788a29a64eef439284fd97e904f374 GIT binary patch literal 320 zcmV-G0l)rT_h)IX%8`!xx#?O($dW=BI;pgAmAMoJ&FVmpak^_M&10n8om%>Hp0CB z>RbH6BfRjxN9M~)&M4Ia$ETJBLQ^<<<3g$He5hLhidW{F&l zq78;P`5|=ls>YxRGDkGO4)Ha@UK+s{*1}DYDDe@&3c1kJ7>fuhJobWT>>{`#?O7?v zaJd6ic&m6Kxyn*Ci7xy1O7D6yV<~Ad#ZoSbb%oCVPWUuVr%YQDrkfL?gZZ)JBOfu SH&bZ<0000o#88fEO5na|OlO{3G z*yIPEVO{WsIjnKnVT>2DMzkql!ApMPMO=L<#3I9oI}LubOO+CNvScYxqQ)LKcQkDr zB9h}Xp02N~GG)+JIYyNnkc)PDZOnzu*#; zB8R%~1(i4Rl0>e>Eb#WLtxfOV(#vvo;8MrfX8# zOJwj-6bRC^RW#U^^>`Uk%GqUlp=9>%y-#&$qZ(w=qV2=#%SNueJg~}R_Q4VL}5`+@9~aM z91c`Iph*TZkfp^?^`Dp^0y)$Ij}vN48)8+LhbiruA|j3sO47|fb;`I*GKou>Ivq)^ z4u=V1QQ;ad;<{u73-W9;3T-}Qiy5qW$_D4g=4Y%kOJdS2(WFC22NX4g$Gh;r2I3UU zC}4AjSPu(ixIwI-i~@=>;)?%*fd#|^BPhd-@r0NU2I`1i6z1DU)PjK~qBKyTf@lN- zCy2s8fg++640I7Wl<`GYIEcGoz(-^U3JfE>zYypm@_+0U8C+z9*+p(5%4UK1zv(Js zZ=k^ch-xrUL_9KT9C*VMq7V#ZxJHzW0~N$&*uzfo7172v3S>D)tf9gJQ}~FgQJ{|K z@irKcxri5LQU~VfAvRG_fjnJAhY3`QO>%*_Wjs2dSVB8ak(i`~wix%~<)U?%MJ3oA z7ie3kxPZkW+6y&o6w%94r-ydHP_ma_Nr*Gbj78j570v p!{d$?4XPC6k!Bt+*lWOGzX87wDYF(rDzN|n002ovPDHLkV1ir08p{9x literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3847a9fe74774d3593dd694cff48ccca13cdec5a GIT binary patch literal 940 zcmV;d15^BoP)mdcfcOtJM6uO{+z8D@UxkKMBq1+Buz@6?P#P0jG1VJo zr4$sRhK5k2Z=fXRN~6nW@y^{1KD~XI=}gYdoFjX&pWp3sm>a@9yM-m0@&}6isAEB(Qj;>qRst`p9~=6q zAu7LO=u+TQ_K7Dd@dg8*_ZUX>5!&WBSSti4^<9Bi7`TY2+y<+KU=~psWnX%OzQeO# z$Y%%reeR=-{wJ-1^+Iq9eT6F#>jwG=^_&zcppV__#9BjN7F^M%z)NCy=&SoFTsf2ppC<<**2_ZI5@SJo9Db@pv9Dwel>@Q2Y|A^pR+ zXm)h0I4$&8t(hXy6*cz3S)t(J`aO{jqc{F(SB1>4AMjd@E;uWs|KhZ2{;};ky2AQ2 z)rxDxD$`NJH=PukQuA$!?4zc}-&3$&Xn>Du{t5dULsafqFEod!oWp?rpGSuiRtueB z&+LQNE+8tK3|cF6gtuz;3+FNP08y#Yj|~U-15sJR*io8@$}eQs#}cB_phN-_NT5>I zKj|$a=v*bC1q7WMgQRsN@Unzi63g)%L1&Ybq;ZC~2s(dqkQ990An0_M#V3gY=Ga5f z`5TXvN})=^4!0;^!e^R~NT^Pc418W7q3m*(v-Aa=K4XNz?fnc_RXp{J*(OZ^8ac7(2N>$@cY O0000+=u1!Si^=c~o6pU56D8MW!jS z$u@O7JnC$-Nr`E0Z~~QViq1F4UDojk)8rj4KiUtv${KCrYEfp?UeGn(lf*h#%mwiZO?K>;SqC6p{w@`EooS#EOw+c}UBbluHuSO9kD3{Dexd zGgQ&NCewM)0=iXlsD$fh4c%;~LATKTP7f-@j@01>+c-g20r3E3BuF*`h>_@^ zHN+KB7e-Jm@Z>j$y%;(Bn$f*7_b_YN7RCYJVeH7P%ol+c!LiAkL*xA)X9%M!t}A%e=*t&auG}M zUL7k|gPtKKgM)4$K3fdZU#`Cd2c5t}oVOJe{qaNg4l!>nXc19Cg$22YJsc}RIU0!D zQ6KVKhxdR-j0Rnxtv}Fl zg03K-O24t7lYB&c=W=+E>@fn$hQ5wD$_oT6qv8ggqJe-vjQr`djDR|Y=pflR=t-%+ z(kr36+3A;_^XSU-C(b#^3v@H6PJ=SMLRaN%qMQqSME8wcL$0HkK4xj7 z`v)iS)q*bxcDYYB<~wVOFW}3`r@4H|@fyE97P!cfkP~%@MH=|M#7TQSm0^y9)p&}_ z7{lc0-Cv$z#&CH?jf4HhF*6r<<1|RC&P|h7oP+#Da^G2|VEz(#l(!^NVZ`1w`3S4D zh|A+Sm+k)}L4iBGp-Gr;RJbh%++L9EIM5D%liv*A40h bb%Xu~a~|hNXxfgr00000NkvXXu0mjfV)nVh literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e2a03a4bc540419ce1165d5dedd8d5345fdec2 GIT binary patch literal 1256 zcmVP)W$LQv#I8j8T_{yf~=m$~;IXzu5?eh%l+bI*O5LnIQ3L?S-g zNEKIT<0XBJGshx}%rQL6n2^)65BytPpur(aAq3mzkx5L*!vp z0a@Ola>x)hq%a~$J)clM5drmH23z#q<|xU<0`*qKPGo7Si~n z%wqBU&0hSlj{%E?RDuuU7MJcXB=Jj%b`&x_e(Xp;ExUAT@Y|(jm(qjWZ(B;_Uor&Y z6!R#Nv*chaGsx+GPf(5^TbQOC&z>U}Plh0!MlPf-JUW9CsUc{WQ6klNkmN0L=W$Hb zI`=E``6_{H*O2>uGL!}iknh%)aH)U^vHJHyC=I|j;!5~HKCWcN1R5Z;2B2BgU%-W! zczBp#L#PcDi)Yo>;Taa!Y@Ptv;<}7l4r)8NeQGlwNF& zYb9ky8`z|cGKQ@=tt4)=frR*F&DNq;5;NLBOdDlk)i+CZzQKZh$R zK8n3)UuX^N7uDk=aj8xWGC(Sn1_~Jx)mdCiFf4{?52b-lQTv8C9#o@5E)leAD3OzR z)Fn^Gaf%@QAdedG1kbj}b8XCXgrFSb3rcL@n*sbhFEP*Qfd2Kr_;5}_cz_aF;&K3M ze+ebhM9$XHgOZ`06n-hB6D318YwImEMS(?`<%O>vfHEvXL&vNpLe#!|zDLp9b&_k9T z*x5;~c-Pf0-68_APCn*}yr+}vWZ1(d62yp+WD|SHaD&H;+SWI+mO!0l8Wm6`sU`&F zbfE&s6E+i?YKBohW{8u7EWu?ypnS+US>hOx&jq=;y2>EuNMckT2YAG1l*`QUkZ)PT z3X$L_jr@gjkUknXOq>;|goD)5%rkl!W0nOLm}QJ!o^qco2Ph^Ii9{k1-~0zqnGDmx SqDLYC0000 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3a82cab3b4f2cfe77333cee6121d867353753d5e GIT binary patch literal 1279 zcmVXA;DPbB};p_0dfAo z$vAy9#+6S9#ELYJ@(Lqa#%C_802gIqArYp#f(wN zJ5D-2x}I^0EQMtFA?SeRyu{vHx_o|^)~`kO7MeJbsRD=Aw#N4d<{5Bs2vT z(7+gW9I~nSN2#E6lJC{kK0$L^4urow?@d3Y+FGmL|;O)mE-^F4Bc9HL2 zQm7ENoloT3SxmqprfnYoS5!#bXp=BSqyK>0=yIe`A#Mj2x&gld?d0n~g}ejgYx5J3 z2Egy^UyUJ3s{D( z);XXRX&hy6FOb$b2OLD|GaAs3)ae{>38~9yz)_?g=YYFNn~esfkZw5#3?r>T`I=7o z+A5@n&H)oh4MqdzBfWAC_=prY8jwJmaSq5K#f%2TkiLBX0pHn2D2X)lKlmdaMq2s3 zeU08h+F~>yg>=(7pcm8C~mULv(P2h^i$HWskl&kK!W z1ZkhKfKETJl!_jtL1O{;k-FUlY(=tYHWIKD-3E67NoJ5v83{OpG)>%HfT9m6OI73n zbC^cDf^r+M9^H}115Tj(#eF~-qezp~L>f@f6jFv+?gJDZ=*~qN(2H(2%1b~cZ;&k3 zL>90X-2_Q*0g5hkkEx6#pqf#1X_VK11mozgMH0}DZj8A107V=5_M<}HVe)PBd(>b6 zoyB%ki2Dr--3^rAfQ4kyePSajq;2Lix=9xJ4^Xs&O~Q_l{`EhTudUbvPZ!B&aUg`x zzD2%ssA2=k8N$vrDp3KgnttpIGQ0QyMI9OJJZ23lfURW|J0sKtb?mZ<=h(66p%zud z>bZoyXEX+R@O5c{1jurnYLt)7;RI9Id&-iaj~+Mi1PVr+=0}fjTW6S}kP#ZgIQ(7D z5MMJynq@Bjr=gSke9a&=VI99OrxzzL=;tUYRxzIhF=8Z{&ni+J<2o;Ka+cZQUVzxi z6k(a9Es85BjSLWm8!U+K5>OlC1Zk9QQD4JKaDdkYVS+SqGZ*11*vk`&kzqGU(^nB& zxXOFHn&t{WGwWNq44PmQXZZ&=hUsR5_|TXQ5UXPyo%C>vhrD8jFMMH!S3Kk<=SkB- pEh@+ + diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 372b917e0..8f33f9297 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -155,6 +155,7 @@ @string/always_ask_player_key preferred_player_last_selected + info_screen video_player background_player popup_player diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea8f0fce8..0e9f6e7ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,6 +30,7 @@ Channel unsubscribed Unable to change subscription Unable to update subscription + Info Screen Main Subscriptions diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b16958ae6..dcf8f9268 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -18,6 +18,7 @@ @drawable/ic_thumb_up_black_24dp @drawable/ic_thumb_down_black_24dp + @drawable/ic_info_outline_black_24dp @drawable/ic_headset_black_24dp @drawable/ic_delete_sweep_white_24dp @drawable/ic_file_download_black_24dp @@ -69,6 +70,7 @@ @drawable/ic_thumb_up_white_24dp @drawable/ic_thumb_down_white_24dp @drawable/ic_headset_white_24dp + @drawable/ic_info_outline_white_24dp @drawable/ic_delete_sweep_black_24dp @drawable/ic_file_download_white_24dp @drawable/ic_share_white_24dp