Merge remote-tracking branch 'origin/dev' into notifications-1

This commit is contained in:
TobiGr 2021-11-02 07:56:09 +01:00
commit 1130aba7ca
106 changed files with 706 additions and 441 deletions

View file

@ -51,8 +51,12 @@ import java.util.ArrayList;
* <li>{@link #saveState()}</li>
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
* </ul>
*
* @deprecated Switch to {@link androidx.viewpager2.widget.ViewPager2} and use
* {@link androidx.viewpager2.adapter.FragmentStateAdapter} instead.
*/
@SuppressWarnings("deprecation")
@Deprecated
public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapt";
private static final boolean DEBUG = false;
@ -86,9 +90,10 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
private final int mBehavior;
private FragmentTransaction mCurTransaction = null;
private final ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private final ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();
private final ArrayList<Fragment> mFragments = new ArrayList<>();
private Fragment mCurrentPrimaryItem = null;
private boolean mExecutingFinishUpdate;
/**
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}
@ -208,7 +213,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
mFragments.set(position, null);
mCurTransaction.remove(fragment);
if (fragment == mCurrentPrimaryItem) {
if (fragment.equals(mCurrentPrimaryItem)) {
mCurrentPrimaryItem = null;
}
}
@ -247,7 +252,19 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
@Override
public void finishUpdate(@NonNull final ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
// We drop any transactions that attempt to be committed
// from a re-entrant call to finishUpdate(). We need to
// do this as a workaround for Robolectric running measure/layout
// calls inline rather than allowing them to be posted
// as they would on a real device.
if (!mExecutingFinishUpdate) {
try {
mExecutingFinishUpdate = true;
mCurTransaction.commitNowAllowingStateLoss();
} finally {
mExecutingFinishUpdate = false;
}
}
mCurTransaction = null;
}
}

View file

@ -262,4 +262,5 @@ public class App extends MultiDexApplication {
protected boolean isDisposedRxExceptionsReported() {
return false;
}
}

View file

@ -21,7 +21,6 @@ public abstract class BaseFragment extends Fragment {
//These values are used for controlling fragments when they are part of the frontpage
@State
protected boolean useAsFrontPage = false;
private boolean mIsVisibleToUser = false;
public void useAsFrontPage(final boolean value) {
useAsFrontPage = value;
@ -85,12 +84,6 @@ public abstract class BaseFragment extends Fragment {
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@ -109,8 +102,7 @@ public abstract class BaseFragment extends Fragment {
if (DEBUG) {
Log.d(TAG, "setTitle() called with: title = [" + title + "]");
}
if ((!useAsFrontPage || mIsVisibleToUser)
&& (activity != null && activity.getSupportActionBar() != null)) {
if (!useAsFrontPage && activity != null && activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
activity.getSupportActionBar().setTitle(title);
}

View file

@ -7,7 +7,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.util.Log;
@ -15,7 +14,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.PackageInfoCompat;
import androidx.preference.PreferenceManager;
@ -48,7 +46,8 @@ public final class CheckForNewAppVersion extends IntentService {
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = CheckForNewAppVersion.class.getSimpleName();
private static final String GITHUB_APK_SHA1
// Public key of the certificate that is used in NewPipe release versions
private static final String RELEASE_CERT_PUBLIC_KEY_SHA1
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
private static final String NEWPIPE_API_URL = "https://newpipe.net/api/data.json";
@ -129,44 +128,37 @@ public final class CheckForNewAppVersion extends IntentService {
final String versionName,
final String apkLocationUrl,
final int versionCode) {
final int notificationId = 2000;
if (BuildConfig.VERSION_CODE < versionCode) {
// A pending intent to open the apk location url in the browser.
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent pendingIntent
= PendingIntent.getActivity(application, 0, intent, 0);
final String channelId = application
.getString(R.string.app_update_notification_channel_id);
final NotificationCompat.Builder notificationBuilder
= new NotificationCompat.Builder(application, channelId)
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(application
.getString(R.string.app_update_notification_content_title))
.setContentText(application
.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
final NotificationManagerCompat notificationManager
= NotificationManagerCompat.from(application);
notificationManager.notify(notificationId, notificationBuilder.build());
if (BuildConfig.VERSION_CODE >= versionCode) {
return;
}
// A pending intent to open the apk location url in the browser.
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent pendingIntent
= PendingIntent.getActivity(application, 0, intent, 0);
final String channelId = application
.getString(R.string.app_update_notification_channel_id);
final NotificationCompat.Builder notificationBuilder
= new NotificationCompat.Builder(application, channelId)
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(application
.getString(R.string.app_update_notification_content_title))
.setContentText(application
.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
final NotificationManagerCompat notificationManager
= NotificationManagerCompat.from(application);
notificationManager.notify(2000, notificationBuilder.build());
}
private static boolean isConnected(@NonNull final App app) {
final ConnectivityManager connectivityManager =
ContextCompat.getSystemService(app, ConnectivityManager.class);
return connectivityManager != null && connectivityManager.getActiveNetworkInfo() != null
&& connectivityManager.getActiveNetworkInfo().isConnected();
}
public static boolean isGithubApk(@NonNull final App app) {
return getCertificateSHA1Fingerprint(app).equals(GITHUB_APK_SHA1);
public static boolean isReleaseApk(@NonNull final App app) {
return getCertificateSHA1Fingerprint(app).equals(RELEASE_CERT_PUBLIC_KEY_SHA1);
}
private void checkNewVersion() throws IOException, ReCaptchaException {
@ -175,9 +167,8 @@ public final class CheckForNewAppVersion extends IntentService {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
final NewVersionManager manager = new NewVersionManager();
// Check if user has enabled/disabled update checking
// and if the current apk is a github one or not.
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true) || !isGithubApk(app)) {
// Check if the current apk is a github one or not.
if (!isReleaseApk(app)) {
return;
}
@ -213,6 +204,7 @@ public final class CheckForNewAppVersion extends IntentService {
// Parse the json from the response.
try {
final JsonObject githubStableObject = JsonParser.object()
.from(response.responseBody()).getObject("flavors")
.getObject("github").getObject("stable");
@ -235,6 +227,23 @@ public final class CheckForNewAppVersion extends IntentService {
}
}
/**
* Start a new service which
* checks if all conditions for performing a version check are met,
* fetches the API endpoint {@link #NEWPIPE_API_URL} containing info
* about the latest NewPipe version
* and displays a notification about ana available update.
* <br>
* Following conditions need to be met, before data is request from the server:
* <ul>
* <li> The app is signed with the correct signing key (by TeamNewPipe / schabi).
* If the signing key differs from the one used upstream, the update cannot be installed.</li>
* <li>The user enabled searching for and notifying about updates in the settings.</li>
* <li>The app did not recently check for updates.
* We do not want to make unnecessary connections and DOS our servers.</li>
* </ul>
* <b>Must not be executed</b> when the app is in background.
*/
public static void startNewVersionCheckService() {
final Intent intent = new Intent(App.getApp().getApplicationContext(),
CheckForNewAppVersion.class);

View file

@ -165,13 +165,25 @@ public class MainActivity extends AppCompatActivity {
}
openMiniPlayerUponPlayerStarted();
// Check for new version
startNewVersionCheckService();
// shedule worker for checking for new streans and creating corresponding notifications
NotificationWorker.schedule(this);
}
@Override
protected void onPostCreate(final Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
final App app = App.getApp();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
if (prefs.getBoolean(app.getString(R.string.update_app_key), true)) {
// Start the service which is checking all conditions
// and eventually searching for a new version.
// The service searching for a new NewPipe version must not be started in background.
startNewVersionCheckService();
}
}
private void setupDrawer() throws ExtractionException {
addDrawerMenuForCurrentService();

View file

@ -18,6 +18,9 @@ import org.schabi.newpipe.util.NavigationHelper;
import java.util.Collections;
public final class QueueItemMenuUtil {
private QueueItemMenuUtil() {
}
public static void openPopupMenu(final PlayQueue playQueue,
final PlayQueueItem item,
final View view,
@ -57,6 +60,13 @@ public final class QueueItemMenuUtil {
);
return true;
case R.id.menu_item_channel_details:
// An intent must be used here.
// Opening with FragmentManager transactions is not working,
// as PlayQueueActivity doesn't use fragments.
NavigationHelper.openChannelFragmentUsingIntent(context, item.getServiceId(),
item.getUploaderUrl(), item.getUploader());
return true;
case R.id.menu_item_share:
shareText(context, item.getTitle(), item.getUrl(),
item.getThumbnailUrl());
@ -67,6 +77,4 @@ public final class QueueItemMenuUtil {
popupMenu.show();
}
private QueueItemMenuUtil() { }
}

View file

@ -20,8 +20,8 @@ public class BlankFragment extends BaseFragment {
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
public void onResume() {
super.onResume();
setTitle("NewPipe");
// leave this inline. Will make it harder for copy cats.
// If you are a Copy cat FUCK YOU.

View file

@ -1181,7 +1181,7 @@ public final class VideoDetailFragment
addVideoPlayerView();
final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(),
MainPlayer.class, queue, autoPlayEnabled);
MainPlayer.class, queue, true, autoPlayEnabled);
ContextCompat.startForegroundService(activity, playerIntent);
}

View file

@ -143,7 +143,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
final View focusedItem = itemsList.getFocusedChild();
final RecyclerView.ViewHolder itemHolder =
itemsList.findContainingViewHolder(focusedItem);
return itemHolder.getAdapterPosition();
return itemHolder.getBindingAdapterPosition();
} catch (final NullPointerException e) {
return -1;
}

View file

@ -103,11 +103,9 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null
&& useAsFrontPage
&& isVisibleToUser) {
public void onResume() {
super.onResume();
if (activity != null && useAsFrontPage) {
setTitle(currentInfo != null ? currentInfo.getName() : name);
}
}

View file

@ -99,9 +99,12 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (useAsFrontPage && isVisibleToUser && activity != null) {
public void onResume() {
super.onResume();
if (!Localization.getPreferredContentCountry(requireContext()).equals(contentCountry)) {
reloadContent();
}
if (useAsFrontPage && activity != null) {
try {
setTitle(kioskTranslatedName);
} catch (final Exception e) {
@ -117,15 +120,6 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
return inflater.inflate(R.layout.fragment_kiosk, container, false);
}
@Override
public void onResume() {
super.onResume();
if (!Localization.getPreferredContentCountry(requireContext()).equals(contentCountry)) {
reloadContent();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/

View file

@ -1088,7 +1088,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
//////////////////////////////////////////////////////////////////////////*/
public int getSuggestionMovementFlags(@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
final int position = viewHolder.getBindingAdapterPosition();
if (position == RecyclerView.NO_POSITION) {
return 0;
}
@ -1099,7 +1099,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
final int position = viewHolder.getBindingAdapterPosition();
final String query = suggestionListAdapter.getItem(position).query;
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
.observeOn(AndroidSchedulers.mainThread())

View file

@ -78,9 +78,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null && isVisibleToUser) {
public void onResume() {
super.onResume();
if (activity != null) {
setTitle(activity.getString(R.string.tab_bookmarks));
}
}

View file

@ -101,9 +101,9 @@ public class StatisticsPlaylistFragment
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null && isVisibleToUser) {
public void onResume() {
super.onResume();
if (activity != null) {
setTitle(activity.getString(R.string.title_activity_history));
}
}

View file

@ -709,8 +709,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
return false;
}
final int sourceIndex = source.getAdapterPosition();
final int targetIndex = target.getAdapterPosition();
final int sourceIndex = source.getBindingAdapterPosition();
final int targetIndex = target.getBindingAdapterPosition();
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
if (isSwapped) {
saveChanges();

View file

@ -97,11 +97,9 @@ public class SubscriptionsImportFragment extends BaseFragment {
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
setTitle(getString(R.string.import_title));
}
public void onResume() {
super.onResume();
setTitle(getString(R.string.import_title));
}
@Nullable

View file

@ -112,8 +112,8 @@ class FeedGroupReorderDialog : DialogFragment() {
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val sourceIndex = source.adapterPosition
val targetIndex = target.adapterPosition
val sourceIndex = source.bindingAdapterPosition
val targetIndex = target.bindingAdapterPosition
groupAdapter.notifyItemMoved(sourceIndex, targetIndex)
Collections.swap(groupOrderedIdList, sourceIndex, targetIndex)

View file

@ -51,6 +51,6 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
@Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) {
onSwiped(viewHolder.getAdapterPosition());
onSwiped(viewHolder.getBindingAdapterPosition());
}
}

View file

@ -16,8 +16,9 @@ public class MainSettingsFragment extends BasePreferenceFragment {
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.main_settings);
if (!CheckForNewAppVersion.isGithubApk(App.getApp())) {
final Preference update = findPreference(getString(R.string.update_pref_screen_key));
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
final Preference update
= findPreference(getString(R.string.update_pref_screen_key));
getPreferenceScreen().removePreference(update);
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();

View file

@ -303,8 +303,8 @@ public class PeertubeInstanceListFragment extends Fragment {
return false;
}
final int sourceIndex = source.getAdapterPosition();
final int targetIndex = target.getAdapterPosition();
final int sourceIndex = source.getBindingAdapterPosition();
final int targetIndex = target.getBindingAdapterPosition();
instanceListAdapter.swapItems(sourceIndex, targetIndex);
return true;
}
@ -322,7 +322,7 @@ public class PeertubeInstanceListFragment extends Fragment {
@Override
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
final int swipeDir) {
final int position = viewHolder.getAdapterPosition();
final int position = viewHolder.getBindingAdapterPosition();
// do not allow swiping the selected instance
if (instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) {
instanceListAdapter.notifyItemChanged(position);

View file

@ -1,34 +1,48 @@
package org.schabi.newpipe.settings;
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
import android.os.Bundle;
import android.widget.Toast;
import androidx.preference.Preference;
import org.schabi.newpipe.R;
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
public class UpdateSettingsFragment extends BasePreferenceFragment {
private final Preference.OnPreferenceChangeListener updatePreferenceChange
= (preference, checkForUpdates) -> {
defaultPreferences.edit()
.putBoolean(getString(R.string.update_app_key), (boolean) checkForUpdates).apply();
if ((boolean) checkForUpdates) {
// Search for updates immediately when update checks are enabled.
// Reset the expire time. This is necessary to check for an update immediately.
defaultPreferences.edit()
.putLong(getString(R.string.update_expiry_key), 0).apply();
startNewVersionCheckService();
}
if ((boolean) checkForUpdates) {
checkNewVersionNow();
}
return true;
};
private final Preference.OnPreferenceClickListener manualUpdateClick
= preference -> {
Toast.makeText(getContext(), R.string.checking_updates_toast, Toast.LENGTH_SHORT).show();
checkNewVersionNow();
return true;
};
private void checkNewVersionNow() {
// Search for updates immediately when update checks are enabled.
// Reset the expire time. This is necessary to check for an update immediately.
defaultPreferences.edit()
.putLong(getString(R.string.update_expiry_key), 0).apply();
startNewVersionCheckService();
}
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.update_settings);
final String updateToggleKey = getString(R.string.update_app_key);
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
findPreference(getString(R.string.update_app_key))
.setOnPreferenceChangeListener(updatePreferenceChange);
findPreference(getString(R.string.manual_update_key))
.setOnPreferenceClickListener(manualUpdateClick);
}
}

View file

@ -299,8 +299,8 @@ public class ChooseTabsFragment extends Fragment {
return false;
}
final int sourceIndex = source.getAdapterPosition();
final int targetIndex = target.getAdapterPosition();
final int sourceIndex = source.getBindingAdapterPosition();
final int targetIndex = target.getBindingAdapterPosition();
selectedTabsAdapter.swapItems(sourceIndex, targetIndex);
return true;
}
@ -318,7 +318,7 @@ public class ChooseTabsFragment extends Fragment {
@Override
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
final int swipeDir) {
final int position = viewHolder.getAdapterPosition();
final int position = viewHolder.getBindingAdapterPosition();
tabList.remove(position);
selectedTabsAdapter.notifyItemRemoved(position);

View file

@ -77,7 +77,8 @@ public final class NavigationHelper {
@NonNull
public static <T> Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue) {
@Nullable final PlayQueue playQueue,
final boolean resumePlayback) {
final Intent intent = new Intent(context, targetClazz);
if (playQueue != null) {
@ -87,6 +88,7 @@ public final class NavigationHelper {
}
}
intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal());
intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
return intent;
}
@ -95,8 +97,9 @@ public final class NavigationHelper {
public static <T> Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue,
final boolean resumePlayback,
final boolean playWhenReady) {
return getPlayerIntent(context, targetClazz, playQueue)
return getPlayerIntent(context, targetClazz, playQueue, resumePlayback)
.putExtra(Player.PLAY_WHEN_READY, playWhenReady);
}
@ -104,7 +107,14 @@ public final class NavigationHelper {
public static <T> Intent getPlayerEnqueueIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue) {
return getPlayerIntent(context, targetClazz, playQueue)
// when enqueueing `resumePlayback` is always `false` since:
// - if there is a video already playing, the value of `resumePlayback` just doesn't make
// any difference.
// - if there is nothing already playing, it is useful for the enqueue action to have a
// slightly different behaviour than the normal play action: the latter resumes playback,
// the former doesn't. (note that enqueue can be triggered when nothing is playing only
// by long pressing the video detail fragment, playlist or channel controls
return getPlayerIntent(context, targetClazz, playQueue, false)
.putExtra(Player.ENQUEUE, true);
}
@ -112,7 +122,8 @@ public final class NavigationHelper {
public static <T> Intent getPlayerEnqueueNextIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue) {
return getPlayerIntent(context, targetClazz, playQueue)
// see comment in `getPlayerEnqueueIntent` as to why `resumePlayback` is false
return getPlayerIntent(context, targetClazz, playQueue, false)
.putExtra(Player.ENQUEUE_NEXT, true);
}
@ -495,6 +506,27 @@ public final class NavigationHelper {
context.startActivity(intent);
}
/**
* Opens {@link ChannelFragment}.
* Use this instead of {@link #openChannelFragment(FragmentManager, int, String, String)}
* when no fragments are used / no FragmentManager is available.
* @param context
* @param serviceId
* @param url
* @param title
*/
public static void openChannelFragmentUsingIntent(final Context context,
final int serviceId,
final String url,
@NonNull final String title) {
final Intent intent = getOpenIntent(context, url, serviceId,
StreamingService.LinkType.CHANNEL);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Constants.KEY_TITLE, title);
context.startActivity(intent);
}
public static void openMainActivity(final Context context) {
final Intent mIntent = new Intent(context, MainActivity.class);
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

View file

@ -0,0 +1,61 @@
package org.schabi.newpipe.util;
import android.content.Context;
import android.text.Selection;
import android.text.Spannable;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.NewPipeEditText;
import org.schabi.newpipe.views.NewPipeTextView;
public final class NewPipeTextViewHelper {
private NewPipeTextViewHelper() {
}
/**
* Share the selected text of {@link NewPipeTextView NewPipeTextViews} and
* {@link NewPipeEditText NewPipeEditTexts} with
* {@link ShareUtils#shareText(Context, String, String)}.
*
* <p>
* This allows EMUI users to get the Android share sheet instead of the EMUI share sheet when
* using the {@code Share} command of the popup menu which appears when selecting text.
* </p>
*
* @param textView the {@link TextView} on which sharing the selected text. It should be a
* {@link NewPipeTextView} or a {@link NewPipeEditText} (even if
* {@link TextView standard TextViews} are supported).
*/
public static void shareSelectedTextWithShareUtils(@NonNull final TextView textView) {
final CharSequence textViewText = textView.getText();
shareSelectedTextIfNotNullAndNotEmpty(textView, getSelectedText(textView, textViewText));
if (textViewText instanceof Spannable) {
Selection.setSelection((Spannable) textViewText, textView.getSelectionEnd());
}
}
@Nullable
private static CharSequence getSelectedText(@NonNull final TextView textView,
@Nullable final CharSequence text) {
if (!textView.hasSelection() || text == null) {
return null;
}
final int start = textView.getSelectionStart();
final int end = textView.getSelectionEnd();
return String.valueOf(start > end ? text.subSequence(end, start)
: text.subSequence(start, end));
}
private static void shareSelectedTextIfNotNullAndNotEmpty(
@NonNull final TextView textView,
@Nullable final CharSequence selectedText) {
if (selectedText != null && selectedText.length() != 0) {
ShareUtils.shareText(textView.getContext(), "", selectedText.toString());
}
}
}

View file

@ -0,0 +1,45 @@
package org.schabi.newpipe.views;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText;
import org.schabi.newpipe.util.NewPipeTextViewHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
/**
* An {@link AppCompatEditText} which uses {@link ShareUtils#shareText(Context, String, String)}
* when sharing selected text by using the {@code Share} command of the floating actions.
* <p>
* This allows NewPipe to show Android share sheet instead of EMUI share sheet when sharing text
* from {@link AppCompatEditText} on EMUI devices.
* </p>
*/
public class NewPipeEditText extends AppCompatEditText {
public NewPipeEditText(@NonNull final Context context) {
super(context);
}
public NewPipeEditText(@NonNull final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public NewPipeEditText(@NonNull final Context context,
@Nullable final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTextContextMenuItem(final int id) {
if (id == android.R.id.shareText) {
NewPipeTextViewHelper.shareSelectedTextWithShareUtils(this);
return true;
}
return super.onTextContextMenuItem(id);
}
}

View file

@ -0,0 +1,45 @@
package org.schabi.newpipe.views;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import org.schabi.newpipe.util.NewPipeTextViewHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
/**
* An {@link AppCompatTextView} which uses {@link ShareUtils#shareText(Context, String, String)}
* when sharing selected text by using the {@code Share} command of the floating actions.
* <p>
* This allows NewPipe to show Android share sheet instead of EMUI share sheet when sharing text
* from {@link AppCompatTextView} on EMUI devices.
* </p>
*/
public class NewPipeTextView extends AppCompatTextView {
public NewPipeTextView(@NonNull final Context context) {
super(context);
}
public NewPipeTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public NewPipeTextView(@NonNull final Context context,
@Nullable final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTextContextMenuItem(final int id) {
if (id == android.R.id.shareText) {
NewPipeTextViewHelper.shareSelectedTextWithShareUtils(this);
return true;
}
return super.onTextContextMenuItem(id);
}
}