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 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm`qb)GJcAr*{IuNZnICyE^X zIKNV=+mJ1~(9p~&V9SO8mph#v8v-sc^1997?O1GdT41`NlCM)XVa(+xYwEp3t zc=l;$R~(h9yn-F;`i%aVir1}3I<~aHa{W6sp1NOM z!VhKcU!Ak!M(Y&U+}!R?vxi<+)@6$PUNrmS;w{ek-kB9VwdXlX^9^sPXm`B5@Ozoc znoj$e-k!?~J_uNA3++;rQ<%T_#!HVK+=ZM!w zto+aK_%c%xgj3ZWXQs|?t?2l(;PH$(UJ<+><8-Z8T*~;)u23Lu0j)mdm@ib{%@)J4i*Lm25-&)VFm`qHJ&bxAr*{IuQ_@t#|yAO zNZ)?!XgqJ;(Zf3gT`snEdkEw!UD^?Ffsxnk1Jk4U)ILSI!xFAZ$GKlToBcI=*JJ4! zXAQpI4t>z9<*}FVe?sQXOW#*~dFuW0VymyQQ|+tU-F&qxR&R8&7Ja4VyK3UO?*Z?) z4op^FJ#EVtoygK0h6VcbS)SH0v6ZDbsm@%d_`vTTPb}lQzv?C_=Xf7%;(Wza$8rA& z&xMuEVpAVPb!=J`5Ybr`oOjS8TC|9R?dn321uIIP?`>$-y7Gy!tVKoR25Yjh;TG0; zDNdg=0*lTkD7XE3;4xvI@P+P$?i;i`&P!EvEELk=Jh__jCZW5Ya%h*@XZN>WBa0@5frW6fxme*JJpO7e-ywEmaR^P$}49>-#7n;I1 z=!Y!aCb+}XO8NVPvwO5uUND^V;hbf>l>J7Y)z;brYk#_>9kg9w{_@9t#<(}9Sg+py zzK7}Gbl_h^MJ3hiD|nzS;aGfa2U zwa%qU0=KgSx5j>4R3Q+&wd2;*9l>+Db{^A6x>6x_yzAWV7rjbu(~~Z&lId!-ef{UF z>bF{BVeY+dYYSg`-}#Z4q?YvVP@iA=^{MHC*VV3BeNagTi>nX57&6*&K!t(7pV0$WeH0wdu~G8-Gip=E0c^3=9mOu6{1-oD!M<^rMkD literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b706f0d06a9a1e5e2f6606c01e84094ee6a99b42 GIT binary patch literal 640 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZs>?w&4=Ar*{or=Itc4iq_7 z-#NKsqqOIYfau?Bu7XM*9b3{a_NqK#jS~9IaBJ6Aj{{|^Z#bB_zK9lSUcE}U>+Xuo zgh=zx!)sQ=sp$*{562{fngWvh#wm*EA-p8J@rK zgneFO?1whXHH9}5d&D=nZP83P=w0{7HKtbUh|=M9Yu>ue-cyAtH>GF>EIKApvyW6K<{ayXe>&x7KU_ z1EHx(?YrKy&M5r3ba{EFb71{Z)5@MVFWxY>FJ8`*)VMHYd+*At5qsNiuwA(F!d7vD zZ|#h}sg7;JEAs^$qW%|$9X!R56=bj8+MnwCR%Dw`v3BgV4;Lq0((}AAUC+0sWNC;h zXX(pJ2aivj;-#`sC${Rx3vW&SYtwnwX{`I8D5)^}L_*N!5BGUXTw3c*mG-7YH&5`s zmL9d)jH7Z{cF-;Fln;R-yr27bE2igu=!pKj;BrjHG+o{jhx8>J57yS5H40;k+~-+4 z*WsB7H?MZ{w2<~QNegr*F@E02EB1Br&en-d$KFm+Dy%qDezWoFt_tXDTW6 z@p)Z3Ygc+|hmTRG_e?8U&$WE*oJoNXrSu=|N@3;xlw-`5YnRe3xu)_I+w)`3($^S! wCtcFHxWu_%+pTBEBKLMS)u21fazD6LA2Vgmnzvt^fq{X+)78&qol`;+0ATJJT>t<8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c571b2e3e776762bb90733f664f9d66e2c7f321c GIT binary patch literal 655 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZs>VV*9IAr*{ouRD6*u9G?b z@&0}_(Ngiu&K&`%hgI{1BA%&xmPUGsZ}{oApS?^oP{DngP9@irn~x{f>4jZ7 zpy8fTaEeR0MLv8{FI#($8s`m}g|?qLxkYVrR_*zsy5`3L^DSob0i0&e>}Pwqbr%0U z;HY|s<#>jCC*Ok)+!y^mexCF~;oV7B_A-I|8v`d-e;wQ2JE=Mnp0urRAn; zp3GCaV+>vTywIIUD+Hc~F zC9T^ezCU`psC0&_>J1t3jOZ0R8jdR7;x^3@xp+Egd3)J`fKAimJv)p~^|;;%6FtZH zwqfFu_3y>>{>t54?j+cr*jA_!wD6$t-)$0J-x)U@vGu&WDdQ98)MV*?3tKZu^Zy(* z=dHqBcBc5gi@MyPbJ)gIYRitphadNe@vahiGBHig;70a=g#C>B`879-IcaTVU|?YI MboFyt=akR{09(Nt%K!iX 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 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4B7kj!mhEy=Vy%C=+5-xH4 zV}8fGuypl_8*Yo{JI6Kr;5T*Ic(wHo$643-*0xX;MSaH&jVcQQq=cgkuS%yb6%kp~ z))ufNe}T#~uf?a+KAyYFYqz&{#!H`bXXYqZZ=8SsZSf5Gb8~EuU%0%rs>(*|%!V^; zw@orGFFR-^tz&#xl<%|X)SU;Va#hrH8dPBF*%?m`Dad^kY{iU+dhm_E?*ts@5{ME3oPx;y)TRXxSw8~74}{1)xYp- zqVlERa`m;QZLj^%elb?@@8KO8WhHAYbiYKbovCz5)b!iI$b}Cc{<59Ox7hq&W_atF z_3|I0di5-{m;8UUCF|eDw^q9j{W7`Qm3>6(a^PX}xgxWIKIeA5S-kAn{ooC884dke z|2Th3IfuN9&^KvcG3Ri@zxnR%!eQKV6VJbCzNV+3HJc|&yQF1%);I3whF=&0S6=Mr zW&gn(7|va-yTYF<*R@?ld;q|oV) zGLGq!Cod^%+s9Oo#Sx*^hDF>kVU9maDT){VfKwl zO=^=2dDkTc&b)6l<>w(G)iu(OMJFk(?s;@Z!~dm1TjuAM&lcg2tn@P#4S$}|x~}m_ zRQ8e4;I^Bva=2 zNA)#@ug_$hI3snptNy?xeXWfd+u8PTUhDqXH7U5|(R_g|OU?1uPEMQXIZ{(I&rY9@(_z_n}ovV{fuoIMhCg(Ti%eFxy7+I3<{RO{Ux|_QU+pFI&?BW-h#P;=Pk&WDuu8FCY9;c^I@fNz` z&3d=~Pm9+EU0w4&v8I`7?_<&N7g{PpA4I@-6X<=u6^Ccn?3w(gQM z=3PlJsJpab^UCZoGO+3%1^eJ)`Q<6#Ch3Xb{b#2!PZ*+2x+_rEG zXZ*;Js^VeB+%fUR0@EYvGLIcC%LG>kIyP?+)H@Y1;f>uQS4Eo_dW-s!RW(k_J@Tu# zV3ov|n#tW^p6JLY_h`=61>GI_GW>j6N@ao9%37ozRTW$?dBl0@(LAMPP0d~%oU0x6 z9t9R$P+OE?n!&SCP-aW7gwshL+3K4HN!k`G`Xn|;O>w`{)NJE&EGs~NQDsFl-~awM z0k&68_S+u%vhjMo;VqsbLGO=ovf5$I^Ay#ZoAVUxqIoXGuiMYUKKFq0ob93_e?K~Z z<1@_H%@7Q9Y-QcqVJoBUXerfkJ5c^pbVqeqlbWF1&kqW&k{g|`RRqY#3!D!ObX?p} zkztb|#yGvHIpuYSSb7gz?1HuS7Ydu!DJ<~Xzj49j8kd^qMHLg?7&4t|+t+7#ikn4e z@gugmiy9m3d;YxK+|yvy`a`rb@oJMkXW34FvuD=XUHT7BpIpAP z=Hy4I{kCE|PE4F2T-xa>^<(c5&uZJLiG9siE4ofg9DefhZv4E&V+ECNz0H4*85)aM zJT~!Iea?4s_?+fHu0{7FmnHlYs+h7}B{1>GJ=32YtG9HVer!4Y?SEy*M(aHbJ!j;H z?9gSu6F1?F#r2tw1f_EV7w_CJ6`ix;h>HDZX04kdW!HIMv6VLeV=tC_?vZh;ZwCVd O1B0ilpUXO@geCxD_PN3U 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 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0D>toL+r45?szd+V%!NVvrD zkL!Js=18*nJaH9DKEfAPvbZok>G85q=i0L_yuE6F*=rP=yB4YCneI|n;K&SCR^3tB zm@n<@by=4)m(g&6Q@|5fsjdSr|JmHRGkxcGxr}$ux7YDKzgT>}cBZq6P^XKM-7OE% z@QlZ*wLQkimAcOu`pN5OJmm_VvPtRbs&L$tytk<4jKS5GH0|?&j7z$8TOPQl1u&*|8MA1e zJI^QP%w8&!ES`F#Q&8DYa<}EVP%DXzYx5Ij8E>AD*Ep^7vFr1ajll<9cLhc~H}~Q= zV7A8mjQ<_AUTM)giKmM8IPN=?ZY{a>N3!shiHEwGUd`Mh@l|%}kBr$zzldihFz?h@ z)T!DncIn5GGf5x+oQ?alp`Wq4HKd9Ek=XQi`YIp2yUIk2Z0p{5*z4{t$@*}6Y3lS# z&3E#ny`IDyu{;#{tTA!(lLT$!6EExi1Ns!AXDpLBKTlcQzfEDTo8A&V8>?oam5LeD z1C_2;On9PO^t{mLiq&`RPd~L9v#(G1U1v0fjcdaDYtsu>HgInI_1#H>_kj5$uOAL} z>r#%gNGWV#TM&6A{y^)Dii#Nt&MSCDy3?;-fATOw)XQ78JWZpRwQTq zB<-I%Uye(rXYwgN!|6%iqhA=P?Y*8>ED#;OWfI?+iPBZN+ipG4J+t8Tt(;jKzXoYr zwpkW=*SD>;Tbm-@Fi~3afL!jBxSZZ>)l@g>8%uhP+SXdGRmt1Z*zzOw?6pNyyX{H5Sx-kx3mjRLza1tqR&oEsOHu^?W1SH_ygsE=MRUv7@uzLBl&`?t!^ zEkD)-B`#_7GCY7^q%3R1Gc#>l8-E|DEdtlX}Ncx>f`S_&N9|5mxAQ0n71F{ zxc@Lh&N=F%it9qNfKyw3+^yr)yVAjF(t68b^$PoTZB4tby&3Ttyl=#>K2|AQ>BnQ_ zpf!b!OH$}+WQ@0gPFp7b(ni0)-)$ChA&Xt~1h-^sOq^VCMb-aUVVA;Rg?_0IEa$hK zD9$}o+_FICF}GLFgV~IKZ1fG!zijyTb^nh7Kc|WYp~y!?4B7RZs+U~OUAx#kY2jRv zI?W}*s?O%wA00WHH6+8hE#BK7mMqMDG*i%m&g zvqc^qt;#dZHGIW!`(xy+>l&xk-hDF73n@BZ^r@@v6sLoxt;XvM&eLRX-1J;qas8_P zT_vVXes<5g_AHi zCOj*vEzmBlV{(hm!Bo2iH76`0A4yC+&$@Hxu2TxnJ(5D?*ei~tPdR<&!-V|E!(0;| zX!QPREt+5_W#KbNbY)BPl}l%gt0a@Bv5L(|?TMRLG+&2ZLk(1T2;Tq09x#*V^1(&! RN(>AP44$rjF6*2UngARXKcN5s 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 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0D>9P@N>45?szd;4UzNTy8t z$Ll5@yR5qt#F&+idCE-he&k#5!AMy3vDe~BicS9;ykD#+WL@8NuSu%+%z_s!6<-Cm zceiv_h#Dp52tV?Qxzs7>{ul53iro%Q-mM2u- z8J%H~KRNNSvM^6^dmP^-FI%k>noW_cntBrs_6kPLr9i^7*?rLzoqsGIm)1#Ciez`@}yLty>;lBeX zEow_P&J;BfP~_s7?9ypX`Jwot;pv_u_o8>z)J+aAZ1H?!o1Qs=v76hbt}pM|qOJ(u=Nq_G zOjt!P9FQoJ+bAV^;MxZXYsptl937t-tfzY}Vv16|zQNd~@+0H^$@=c>S`XeHSGs>R ziMedz`}m+00^8>LKe-+0b3kcXuH66d5Rcm#lMP%K|8KbcqG(BgVjjELF2?S`QgeQ`_PHSAhta<&n@Ldbe_l)fqZIvWF9YiHde(z-q`6#A&JS#+nv1|T@ z330&>brpKoNsC4{EMmVKr_X43Y{ESw^Ggj;n>_qAisdhKt>YHWY+o}y-_KiGOWMyd3VZOnF+Wq@Tg3OHL&RKag2Svrz7vW^d)SUl z*jMwP@%~MBjZTx#_CJ*6+@gN&t?j+%cq_?7p|#|)wf8s4emxvxw}AbFNYsy7SCWc{`})PtdvMBDO*7E{kVAW0zh@|H>Yf1+xN6GN)W` zV0rvvovYA={=A+Y-x*w`e|}r+bT9E!?&s<&2@G?OmW6cu=H1zpwU_*RQ?7>@R*Yy-=a${65!x#VeASPe~p+XQ~y*xOP_E z8y=(GKNwtPCEwVj@oF`!O%<_LzQR)aW>e?$8yjCN6bn7_YG=#dbQ8A952cxnEGLTQ zWKHQm`;aC0M)j#l3;N~EKeBMS&3WeDpC6q*W6=abslyh3V$Tc28@tR()%&-6;pE>n zXBA8nW%uhZ7gyJR@mxbQ%y2%7VUo(s^IAbNkFE3zi>BRt7}s#)q)6h-CAwYO8ofLM zZPGd&?}P%RAG2gm$qkG@>{ZZkoh7l!q|jLWq1KJl!d(*+%{?!N3r6oaRlR4{GcJ4M z*U>w9W+e!x|716Bdzse!fbH5aHx9>@6)2~Tv3%7Zq_T2NbldjeYO`r85 k2}MgAf*dp#hH;I3=9kmp00i_>zopr08UCbZvX%Q literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 794365a3d..31eda4fbc 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -3,6 +3,7 @@ + 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