diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 00c7105c2..7375e8715 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -10,6 +10,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; + import org.schabi.newpipelegacy.R; import java.lang.reflect.Field; diff --git a/app/src/main/java/org/schabi/newpipelegacy/App.java b/app/src/main/java/org/schabi/newpipelegacy/App.java index 0e2eaf497..198b22e34 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/App.java +++ b/app/src/main/java/org/schabi/newpipelegacy/App.java @@ -6,26 +6,16 @@ import android.content.SharedPreferences; import android.os.Build; import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.multidex.MultiDexApplication; import androidx.preference.PreferenceManager; + import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.exceptions.CompositeException; -import io.reactivex.rxjava3.exceptions.MissingBackpressureException; -import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; -import io.reactivex.rxjava3.exceptions.UndeliverableException; -import io.reactivex.rxjava3.functions.Consumer; -import io.reactivex.rxjava3.plugins.RxJavaPlugins; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.SocketException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; + import org.acra.ACRA; import org.acra.config.ACRAConfigurationException; import org.acra.config.CoreConfiguration; @@ -41,6 +31,21 @@ import org.schabi.newpipelegacy.util.ServiceHelper; import org.schabi.newpipelegacy.util.StateSaver; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.CompositeException; +import io.reactivex.rxjava3.exceptions.MissingBackpressureException; +import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; +import io.reactivex.rxjava3.exceptions.UndeliverableException; +import io.reactivex.rxjava3.functions.Consumer; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; + /* * Copyright (C) Hans-Christoph Steiner 2016 * App.java is part of NewPipe. diff --git a/app/src/main/java/org/schabi/newpipelegacy/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipelegacy/DownloaderImpl.java index 2f8fad3b1..03142b59a 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipelegacy/DownloaderImpl.java @@ -2,10 +2,10 @@ import android.content.Context; import android.os.Build; -import androidx.preference.PreferenceManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.preference.PreferenceManager; import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Request; diff --git a/app/src/main/java/org/schabi/newpipelegacy/ImageDownloader.java b/app/src/main/java/org/schabi/newpipelegacy/ImageDownloader.java index 5ca4dd251..ac6add0a0 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/ImageDownloader.java +++ b/app/src/main/java/org/schabi/newpipelegacy/ImageDownloader.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; + import androidx.preference.PreferenceManager; import com.nostra13.universalimageloader.core.download.BaseImageDownloader; diff --git a/app/src/main/java/org/schabi/newpipelegacy/MainActivity.java b/app/src/main/java/org/schabi/newpipelegacy/MainActivity.java index 3fb42ec63..e3f2ef829 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipelegacy/MainActivity.java @@ -20,8 +20,6 @@ package org.schabi.newpipelegacy; -import static org.schabi.newpipelegacy.util.Localization.assureCorrectAppLanguage; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -43,6 +41,7 @@ import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.Spinner; + import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBarDrawerToggle; @@ -53,10 +52,9 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; + import com.google.android.material.bottomsheet.BottomSheetBehavior; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; + import org.schabi.newpipelegacy.databinding.ActivityMainBinding; import org.schabi.newpipelegacy.databinding.DrawerHeaderBinding; import org.schabi.newpipelegacy.databinding.DrawerLayoutBinding; @@ -89,6 +87,12 @@ import org.schabi.newpipelegacy.util.ThemeHelper; import org.schabi.newpipelegacy.views.FocusOverlayView; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static org.schabi.newpipelegacy.util.Localization.assureCorrectAppLanguage; + public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); diff --git a/app/src/main/java/org/schabi/newpipelegacy/RouterActivity.java b/app/src/main/java/org/schabi/newpipelegacy/RouterActivity.java index 660991272..9441f3d99 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipelegacy/RouterActivity.java @@ -188,7 +188,7 @@ private void showUnsupportedUrlDialog(final String url) { .setPositiveButton(R.string.open_in_browser, (dialog, which) -> ShareUtils.openUrlInBrowser(this, url)) .setNegativeButton(R.string.share, - (dialog, which) -> ShareUtils.shareUrl(this, "", url)) // no subject + (dialog, which) -> ShareUtils.shareText(this, "", url)) // no subject .setNeutralButton(R.string.cancel, null) .setOnDismissListener(dialog -> finish()) .show(); diff --git a/app/src/main/java/org/schabi/newpipelegacy/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipelegacy/about/AboutActivity.java index d60f83e61..93ebb3331 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/about/AboutActivity.java +++ b/app/src/main/java/org/schabi/newpipelegacy/about/AboutActivity.java @@ -146,16 +146,17 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME); aboutBinding.githubLink.setOnClickListener(nv -> - openUrlInBrowser(context, context.getString(R.string.github_url))); + openUrlInBrowser(context, context.getString(R.string.github_url), false)); aboutBinding.donationLink.setOnClickListener(v -> - openUrlInBrowser(context, context.getString(R.string.donation_url))); + openUrlInBrowser(context, context.getString(R.string.donation_url), false)); aboutBinding.websiteLink.setOnClickListener(nv -> - openUrlInBrowser(context, context.getString(R.string.website_url))); + openUrlInBrowser(context, context.getString(R.string.website_url), false)); aboutBinding.privacyPolicyLink.setOnClickListener(v -> - openUrlInBrowser(context, context.getString(R.string.privacy_policy_url))); + openUrlInBrowser(context, context.getString(R.string.privacy_policy_url), + false)); return aboutBinding.getRoot(); } diff --git a/app/src/main/java/org/schabi/newpipelegacy/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipelegacy/fragments/detail/VideoDetailFragment.java index dee53e64c..a962e8a9d 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipelegacy/fragments/detail/VideoDetailFragment.java @@ -17,7 +17,6 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; -import android.text.util.Linkify; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; @@ -102,6 +101,7 @@ import org.schabi.newpipelegacy.util.NavigationHelper; import org.schabi.newpipelegacy.util.PermissionHelper; import org.schabi.newpipelegacy.util.ShareUtils; +import org.schabi.newpipelegacy.util.TextLinkifier; import org.schabi.newpipelegacy.util.ThemeHelper; import org.schabi.newpipelegacy.views.AnimatedProgressBar; import org.schabi.newpipelegacy.views.LargeTextMovementMethod; @@ -113,10 +113,7 @@ import java.util.concurrent.TimeUnit; import icepick.State; -import io.noties.markwon.Markwon; -import io.noties.markwon.linkify.LinkifyPlugin; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; @@ -1233,28 +1230,20 @@ private void prepareDescription(final Description description) { return; } - if (description.getType() == Description.HTML) { - disposables.add(Single.just(description.getContent()) - .map(descriptionText -> - HtmlCompat.fromHtml(descriptionText, - HtmlCompat.FROM_HTML_MODE_LEGACY)) - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(spanned -> { - videoDescriptionView.setText(spanned); - videoDescriptionView.setVisibility(View.VISIBLE); - })); - } else if (description.getType() == Description.MARKDOWN) { - final Markwon markwon = Markwon.builder(requireContext()) - .usePlugin(LinkifyPlugin.create()) - .build(); - markwon.setMarkdown(videoDescriptionView, description.getContent()); - videoDescriptionView.setVisibility(View.VISIBLE); - } else { - //== Description.PLAIN_TEXT - videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS); - videoDescriptionView.setText(description.getContent(), TextView.BufferType.SPANNABLE); - videoDescriptionView.setVisibility(View.VISIBLE); + switch (description.getType()) { + case Description.HTML: + disposables.add(TextLinkifier.createLinksFromHtmlBlock(requireContext(), + description.getContent(), videoDescriptionView, + HtmlCompat.FROM_HTML_MODE_LEGACY)); + break; + case Description.MARKDOWN: + disposables.add(TextLinkifier.createLinksFromMarkdownText(requireContext(), + description.getContent(), videoDescriptionView)); + break; + case Description.PLAIN_TEXT: default: + disposables.add(TextLinkifier.createLinksFromPlainText(requireContext(), + description.getContent(), videoDescriptionView)); + break; } } @@ -1570,8 +1559,8 @@ public void handleResult(@NonNull final StreamInfo info) { prepareDescription(info.getDescription()); updateProgressInfo(info); initThumbnailViews(info); - showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator); - + disposables.add(showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, + detailMetaInfoSeparator)); if (player == null || player.isStopped()) { updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl()); diff --git a/app/src/main/java/org/schabi/newpipelegacy/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipelegacy/fragments/list/channel/ChannelFragment.java index 0e207fae1..930544600 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipelegacy/fragments/list/channel/ChannelFragment.java @@ -1,8 +1,6 @@ package org.schabi.newpipelegacy.fragments.list.channel; import android.content.Context; -import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -188,8 +186,7 @@ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { private void openRssFeed() { final ChannelInfo info = currentInfo; if (info != null) { - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl())); - startActivity(intent); + ShareUtils.openUrlInBrowser(requireContext(), info.getFeedUrl(), false); } } @@ -209,7 +206,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { break; case R.id.menu_item_share: if (currentInfo != null) { - ShareUtils.shareUrl(requireContext(), name, currentInfo.getOriginalUrl()); + ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl()); } break; default: diff --git a/app/src/main/java/org/schabi/newpipelegacy/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipelegacy/fragments/list/playlist/PlaylistFragment.java index b9449f489..538f7991f 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipelegacy/fragments/list/playlist/PlaylistFragment.java @@ -242,7 +242,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { ShareUtils.openUrlInBrowser(requireContext(), url); break; case R.id.menu_item_share: - ShareUtils.shareUrl(requireContext(), name, url); + ShareUtils.shareText(requireContext(), name, url); break; case R.id.menu_item_bookmark: onBookmarkClicked(); diff --git a/app/src/main/java/org/schabi/newpipelegacy/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipelegacy/fragments/list/search/SearchFragment.java index 54ad1556b..8e243d55c 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipelegacy/fragments/list/search/SearchFragment.java @@ -280,8 +280,8 @@ public void onResume() { handleSearchSuggestion(); - showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo), - metaInfoTextView, metaInfoSeparator); + disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo), + metaInfoTextView, metaInfoSeparator)); if (suggestionDisposable == null || suggestionDisposable.isDisposed()) { initSuggestionObserver(); @@ -1002,11 +1002,11 @@ public void handleResult(@NonNull final SearchInfo result) { // List cannot be bundled without creating some containers metaInfo = new MetaInfo[result.getMetaInfo().size()]; metaInfo = result.getMetaInfo().toArray(metaInfo); + disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, + metaInfoSeparator)); handleSearchSuggestion(); - showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, metaInfoSeparator); - lastSearchedString = searchString; nextPage = result.getNextPage(); diff --git a/app/src/main/java/org/schabi/newpipelegacy/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipelegacy/info_list/holder/CommentsMiniInfoItemHolder.java index bacc396c3..ea4f5f5a8 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipelegacy/info_list/holder/CommentsMiniInfoItemHolder.java @@ -13,15 +13,14 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.preference.PreferenceManager; - import org.schabi.newpipelegacy.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipelegacy.info_list.InfoItemBuilder; import org.schabi.newpipelegacy.local.history.HistoryRecordManager; import org.schabi.newpipelegacy.report.ErrorActivity; -import org.schabi.newpipelegacy.util.DeviceUtils; import org.schabi.newpipelegacy.util.CommentTextOnTouchListener; +import org.schabi.newpipelegacy.util.DeviceUtils; import org.schabi.newpipelegacy.util.ImageDisplayConstants; import org.schabi.newpipelegacy.util.Localization; import org.schabi.newpipelegacy.util.NavigationHelper; diff --git a/app/src/main/java/org/schabi/newpipelegacy/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipelegacy/info_list/holder/StreamInfoItemHolder.java index 461674d15..d143ab4a9 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/info_list/holder/StreamInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipelegacy/info_list/holder/StreamInfoItemHolder.java @@ -1,10 +1,11 @@ package org.schabi.newpipelegacy.info_list.holder; -import androidx.preference.PreferenceManager; import android.text.TextUtils; import android.view.ViewGroup; import android.widget.TextView; +import androidx.preference.PreferenceManager; + import org.schabi.newpipelegacy.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; diff --git a/app/src/main/java/org/schabi/newpipelegacy/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipelegacy/local/BaseLocalListFragment.java index 7938fa972..73ed3f207 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipelegacy/local/BaseLocalListFragment.java @@ -4,16 +4,15 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.View; +import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; diff --git a/app/src/main/java/org/schabi/newpipelegacy/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipelegacy/local/bookmark/BookmarkFragment.java index f99838022..5cdb588f9 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipelegacy/local/bookmark/BookmarkFragment.java @@ -33,9 +33,9 @@ import java.util.List; import icepick.State; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; diff --git a/app/src/main/java/org/schabi/newpipelegacy/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipelegacy/local/subscription/SubscriptionFragment.kt index 418b22a52..9ce036b33 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipelegacy/local/subscription/SubscriptionFragment.kt @@ -30,11 +30,11 @@ import com.xwray.groupie.Section import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder import icepick.State import io.reactivex.rxjava3.disposables.CompositeDisposable +import org.schabi.newpipe.extractor.channel.ChannelInfoItem import org.schabi.newpipelegacy.R import org.schabi.newpipelegacy.database.feed.model.FeedGroupEntity import org.schabi.newpipelegacy.databinding.DialogTitleBinding import org.schabi.newpipelegacy.databinding.FragmentSubscriptionBinding -import org.schabi.newpipe.extractor.channel.ChannelInfoItem import org.schabi.newpipelegacy.fragments.BaseStateFragment import org.schabi.newpipelegacy.ktx.animate import org.schabi.newpipelegacy.local.subscription.SubscriptionViewModel.SubscriptionState @@ -300,7 +300,7 @@ class SubscriptionFragment : BaseStateFragment() { val actions = DialogInterface.OnClickListener { _, i -> when (i) { - 0 -> ShareUtils.shareUrl(requireContext(), selectedItem.name, selectedItem.url) + 0 -> ShareUtils.shareText(requireContext(), selectedItem.name, selectedItem.url) 1 -> deleteChannel(selectedItem) } } diff --git a/app/src/main/java/org/schabi/newpipelegacy/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipelegacy/player/MainPlayer.java index 5a267f33c..7c2139096 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipelegacy/player/MainPlayer.java @@ -34,8 +34,8 @@ import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import org.schabi.newpipelegacy.databinding.PlayerBinding; import org.schabi.newpipelegacy.App; +import org.schabi.newpipelegacy.databinding.PlayerBinding; import org.schabi.newpipelegacy.util.ThemeHelper; import static org.schabi.newpipelegacy.util.Localization.assureCorrectAppLanguage; diff --git a/app/src/main/java/org/schabi/newpipelegacy/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipelegacy/player/PlayQueueActivity.java index 2a8917460..0d76cece6 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipelegacy/player/PlayQueueActivity.java @@ -46,6 +46,7 @@ import static org.schabi.newpipelegacy.player.helper.PlayerHelper.formatSpeed; import static org.schabi.newpipelegacy.util.Localization.assureCorrectAppLanguage; +import static org.schabi.newpipelegacy.util.ShareUtils.shareText; public final class PlayQueueActivity extends AppCompatActivity implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, @@ -311,7 +312,7 @@ private void buildItemPopupMenu(final PlayQueueItem item, final View view) { final MenuItem share = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 3, Menu.NONE, R.string.share); share.setOnMenuItemClickListener(menuItem -> { - shareUrl(item.getTitle(), item.getUrl()); + shareText(getApplicationContext(), item.getTitle(), item.getUrl()); return true; }); @@ -505,18 +506,6 @@ private void openPlaylistAppendDialog(final List playlist) { () -> PlaylistCreationDialog.newInstance(d).show(getSupportFragmentManager(), TAG)); } - //////////////////////////////////////////////////////////////////////////// - // Share - //////////////////////////////////////////////////////////////////////////// - - private void shareUrl(final String subject, final String url) { - final Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, url); - startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); - } - //////////////////////////////////////////////////////////////////////////// // Binding Service Listener //////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipelegacy/player/Player.java b/app/src/main/java/org/schabi/newpipelegacy/player/Player.java index 69d58cee4..5bfb6f972 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/player/Player.java +++ b/app/src/main/java/org/schabi/newpipelegacy/player/Player.java @@ -3553,7 +3553,7 @@ private void onShareClicked() { && currentMetadata.getMetadata().getServiceId() == YouTube.getServiceId()) { videoUrl += ("&t=" + ts); } - ShareUtils.shareUrl(context, getVideoTitle(), videoUrl); + ShareUtils.shareText(context, getVideoTitle(), videoUrl); } private void onPlayWithKodiClicked() { diff --git a/app/src/main/java/org/schabi/newpipelegacy/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipelegacy/player/event/CustomBottomSheetBehavior.java index c6c0f16d2..31fd69069 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/player/event/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipelegacy/player/event/CustomBottomSheetBehavior.java @@ -6,9 +6,12 @@ import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; + import androidx.annotation.NonNull; import androidx.coordinatorlayout.widget.CoordinatorLayout; + import com.google.android.material.bottomsheet.BottomSheetBehavior; + import org.schabi.newpipelegacy.R; import java.util.Arrays; diff --git a/app/src/main/java/org/schabi/newpipelegacy/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipelegacy/player/helper/PlaybackParameterDialog.java index 189fff55d..1db7e2d6b 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipelegacy/player/helper/PlaybackParameterDialog.java @@ -3,7 +3,6 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; -import androidx.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.widget.CheckBox; @@ -14,6 +13,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; +import androidx.preference.PreferenceManager; import org.schabi.newpipelegacy.R; import org.schabi.newpipelegacy.util.SliderStrategy; diff --git a/app/src/main/java/org/schabi/newpipelegacy/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipelegacy/player/playqueue/PlayQueue.java index 40986b97b..fe8ca33ed 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipelegacy/player/playqueue/PlayQueue.java @@ -21,8 +21,8 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import io.reactivex.rxjava3.core.BackpressureStrategy; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.BackpressureStrategy; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.subjects.BehaviorSubject; diff --git a/app/src/main/java/org/schabi/newpipelegacy/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipelegacy/report/ErrorActivity.java index 21e24a96d..44bbe7b7b 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipelegacy/report/ErrorActivity.java @@ -14,7 +14,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -207,8 +206,7 @@ protected void onCreate(final Bundle savedInstanceState) { openPrivacyPolicyDialog(this, "EMAIL")); activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> { - ShareUtils.copyToClipboard(this, buildMarkdown()); - Toast.makeText(this, R.string.msg_copied, Toast.LENGTH_SHORT).show(); + ShareUtils.copyToClipboard(this, buildMarkdown()); }); activityErrorBinding.errorReportGitHubButton.setOnClickListener(v -> @@ -246,11 +244,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { goToReturnActivity(); break; case R.id.menu_item_share_error: - final Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, buildJson()); - intent.setType("text/plain"); - startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); + ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson()); break; } return false; @@ -273,10 +267,10 @@ private void openPrivacyPolicyDialog(final Context context, final String action) .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT) .putExtra(Intent.EXTRA_TEXT, buildJson()); if (i.resolveActivity(getPackageManager()) != null) { - startActivity(i); + ShareUtils.openIntentInApp(context, i); } } else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub - ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL); + ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL, false); } }) diff --git a/app/src/main/java/org/schabi/newpipelegacy/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipelegacy/settings/BasePreferenceFragment.java index b023def5c..93f14561f 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/settings/BasePreferenceFragment.java +++ b/app/src/main/java/org/schabi/newpipelegacy/settings/BasePreferenceFragment.java @@ -2,12 +2,12 @@ import android.content.SharedPreferences; import android.os.Bundle; -import androidx.preference.PreferenceManager; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; import org.schabi.newpipelegacy.MainActivity; import org.schabi.newpipelegacy.util.ThemeHelper; diff --git a/app/src/main/java/org/schabi/newpipelegacy/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipelegacy/settings/SelectChannelFragment.java index ca8a9bebb..6a250060e 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipelegacy/settings/SelectChannelFragment.java @@ -30,8 +30,8 @@ import java.util.Vector; import de.hdodenhof.circleimageview.CircleImageView; -import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; diff --git a/app/src/main/java/org/schabi/newpipelegacy/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipelegacy/settings/tabs/TabsManager.java index 15efddbde..03b3ccdcc 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/settings/tabs/TabsManager.java +++ b/app/src/main/java/org/schabi/newpipelegacy/settings/tabs/TabsManager.java @@ -2,9 +2,10 @@ import android.content.Context; import android.content.SharedPreferences; -import androidx.preference.PreferenceManager; import android.widget.Toast; +import androidx.preference.PreferenceManager; + import org.schabi.newpipelegacy.R; import java.util.List; diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipelegacy/util/CommentTextOnTouchListener.java index adf457e68..1017abc6b 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/CommentTextOnTouchListener.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/CommentTextOnTouchListener.java @@ -23,8 +23,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.schedulers.Schedulers; public class CommentTextOnTouchListener implements View.OnTouchListener { @@ -69,7 +69,8 @@ public boolean onTouch(final View v, final MotionEvent event) { handled = handleUrl(v.getContext(), (URLSpan) link[0]); } if (!handled) { - link[0].onClick(widget); + ShareUtils.openUrlInBrowser(v.getContext(), + ((URLSpan) link[0]).getURL(), false); } } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipelegacy/util/ExtractorHelper.java index 9057b6225..08db8684d 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/ExtractorHelper.java @@ -22,7 +22,6 @@ import android.content.Context; import android.content.Intent; import android.os.Handler; -import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.View; import android.widget.TextView; @@ -68,6 +67,7 @@ import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.disposables.Disposable; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; @@ -325,10 +325,11 @@ public static void handleGeneralException(final Context context, final int servi * @param metaInfos a list of meta information, can be null or empty * @param metaInfoTextView the text view in which to show the formatted HTML * @param metaInfoSeparator another view to be shown or hidden accordingly to the text view + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed */ - public static void showMetaInfoInTextView(@Nullable final List metaInfos, - final TextView metaInfoTextView, - final View metaInfoSeparator) { + public static Disposable showMetaInfoInTextView(@Nullable final List metaInfos, + final TextView metaInfoTextView, + final View metaInfoSeparator) { final Context context = metaInfoTextView.getContext(); final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.show_meta_info_key), true); @@ -336,6 +337,7 @@ public static void showMetaInfoInTextView(@Nullable final List metaInf if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) { metaInfoTextView.setVisibility(View.GONE); metaInfoSeparator.setVisibility(View.GONE); + return Disposable.empty(); } else { final StringBuilder stringBuilder = new StringBuilder(); @@ -365,11 +367,9 @@ public static void showMetaInfoInTextView(@Nullable final List metaInf } } - metaInfoTextView.setText(HtmlCompat.fromHtml(stringBuilder.toString(), - HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING)); - metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance()); - metaInfoTextView.setVisibility(View.VISIBLE); metaInfoSeparator.setVisibility(View.VISIBLE); + return TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(), + metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); } } diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/FilenameUtils.java b/app/src/main/java/org/schabi/newpipelegacy/util/FilenameUtils.java index 846128652..46f6cb46f 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/FilenameUtils.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/FilenameUtils.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.SharedPreferences; + import androidx.preference.PreferenceManager; import org.schabi.newpipelegacy.R; diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/ListHelper.java b/app/src/main/java/org/schabi/newpipelegacy/util/ListHelper.java index 21a69d11a..d5ae01178 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/ListHelper.java @@ -4,11 +4,10 @@ import android.content.SharedPreferences; import android.net.ConnectivityManager; -import androidx.core.content.ContextCompat; -import androidx.preference.PreferenceManager; - import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import org.schabi.newpipelegacy.R; import org.schabi.newpipe.extractor.MediaFormat; diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/Localization.java b/app/src/main/java/org/schabi/newpipelegacy/util/Localization.java index 5daa1100b..f2d6632b3 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/Localization.java @@ -9,10 +9,19 @@ import android.os.Build; import android.text.TextUtils; import android.util.DisplayMetrics; + import androidx.annotation.NonNull; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; + +import org.ocpsoft.prettytime.PrettyTime; +import org.ocpsoft.prettytime.units.Decade; +import org.schabi.newpipelegacy.R; +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.localization.ContentCountry; +import org.schabi.newpipelegacy.ktx.OffsetDateTimeKt; + import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; @@ -24,12 +33,6 @@ import java.util.Calendar; import java.util.List; import java.util.Locale; -import org.ocpsoft.prettytime.PrettyTime; -import org.ocpsoft.prettytime.units.Decade; -import org.schabi.newpipelegacy.R; -import org.schabi.newpipe.extractor.ListExtractor; -import org.schabi.newpipe.extractor.localization.ContentCountry; -import org.schabi.newpipelegacy.ktx.OffsetDateTimeKt; /* diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipelegacy/util/NavigationHelper.java index 61f972e36..36027a9d9 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/NavigationHelper.java @@ -2,7 +2,6 @@ import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -46,9 +45,9 @@ import org.schabi.newpipelegacy.local.playlist.LocalPlaylistFragment; import org.schabi.newpipelegacy.local.subscription.SubscriptionFragment; import org.schabi.newpipelegacy.local.subscription.SubscriptionsImportFragment; +import org.schabi.newpipelegacy.player.MainPlayer; import org.schabi.newpipelegacy.player.PlayQueueActivity; import org.schabi.newpipelegacy.player.Player; -import org.schabi.newpipelegacy.player.MainPlayer; import org.schabi.newpipelegacy.player.helper.PlayerHelper; import org.schabi.newpipelegacy.player.helper.PlayerHolder; import org.schabi.newpipelegacy.player.playqueue.PlayQueue; @@ -57,6 +56,8 @@ import java.util.ArrayList; +import static org.schabi.newpipelegacy.util.ShareUtils.installApp; + public final class NavigationHelper { public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; @@ -246,16 +247,14 @@ public static void playOnExternalPlayer(final Context context, final String name public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) { if (intent.resolveActivity(context.getPackageManager()) != null) { - context.startActivity(intent); + ShareUtils.openIntentInApp(context, intent); } else { if (context instanceof Activity) { new AlertDialog.Builder(context) .setMessage(R.string.no_player_found) .setPositiveButton(R.string.install, (dialog, which) -> { - final Intent i = new Intent(); - i.setAction(Intent.ACTION_VIEW); - i.setData(Uri.parse(context.getString(R.string.fdroid_vlc_url))); - context.startActivity(i); + ShareUtils.openUrlInBrowser(context, + context.getString(R.string.fdroid_vlc_url), false); }) .setNegativeButton(R.string.cancel, (dialog, which) -> Log.i("NavigationHelper", "You unlocked a secret unicorn.")) @@ -568,30 +567,6 @@ public static Intent getIntentByLink(final Context context, return getOpenIntent(context, url, service.getServiceId(), linkType); } - private static Uri openMarketUrl(final String packageName) { - return Uri.parse("market://details") - .buildUpon() - .appendQueryParameter("id", packageName) - .build(); - } - - private static Uri getGooglePlayUrl(final String packageName) { - return Uri.parse("https://play.google.com/store/apps/details") - .buildUpon() - .appendQueryParameter("id", packageName) - .build(); - } - - private static void installApp(final Context context, final String packageName) { - try { - // Try market:// scheme - context.startActivity(new Intent(Intent.ACTION_VIEW, openMarketUrl(packageName))); - } catch (final ActivityNotFoundException e) { - // Fall back to google play URL (don't worry F-Droid can handle it :) - context.startActivity(new Intent(Intent.ACTION_VIEW, getGooglePlayUrl(packageName))); - } - } - /** * Start an activity to install Kore. * diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipelegacy/util/PeertubeHelper.java index dfebac0bd..8ae9f6584 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/PeertubeHelper.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.SharedPreferences; + import androidx.preference.PreferenceManager; import com.grack.nanojson.JsonArray; diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipelegacy/util/ServiceHelper.java index ba7f73346..d434836f3 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/ServiceHelper.java @@ -2,10 +2,10 @@ import android.content.Context; import android.content.SharedPreferences; -import androidx.preference.PreferenceManager; import androidx.annotation.DrawableRes; import androidx.annotation.StringRes; +import androidx.preference.PreferenceManager; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipelegacy/util/ShareUtils.java index b6e59eae4..9bcac0909 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/ShareUtils.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/ShareUtils.java @@ -1,11 +1,11 @@ package org.schabi.newpipelegacy.util; +import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.net.Uri; import android.widget.Toast; @@ -17,26 +17,107 @@ public final class ShareUtils { private ShareUtils() { } + /** + * Open an Intent to install an app. + *

+ * This method will first try open to Google Play Store with the market scheme and falls back to + * Google Play Store web url if this first cannot be found. + * + * @param context the context to use + * @param packageName the package to be installed + */ + public static void installApp(final Context context, final String packageName) { + try { + // Try market:// scheme + openIntentInApp(context, new Intent(Intent.ACTION_VIEW, + Uri.parse("market://details?id=" + packageName))); + } catch (final ActivityNotFoundException e) { + // Fall back to Google Play Store Web URL (don't worry, F-Droid can handle it :)) + openUrlInBrowser(context, + "https://play.google.com/store/apps/details?id=" + packageName, false); + } + } + /** * Open the url with the system default browser. *

* If no browser is set as default, fallbacks to - * {@link ShareUtils#openInDefaultApp(Context, String)} + * {@link ShareUtils#openInDefaultApp(Context, Intent)} + * + * @param context the context to use + * @param url the url to browse + * @param httpDefaultBrowserTest the boolean to set if the test for the default browser will be + * for HTTP protocol or for the created intent + */ + public static void openUrlInBrowser(final Context context, final String url, + final boolean httpDefaultBrowserTest) { + final String defaultPackageName; + final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (httpDefaultBrowserTest) { + defaultPackageName = getDefaultBrowserPackageName(context); + } else { + defaultPackageName = getDefaultAppPackageName(context, intent); + } + + if (defaultPackageName.equals("android")) { + // No browser set as default (doesn't work on some devices) + openInDefaultApp(context, intent); + } else { + try { + intent.setPackage(defaultPackageName); + context.startActivity(intent); + } catch (final ActivityNotFoundException e) { + // Not a browser but an app chooser because of OEMs changes + intent.setPackage(null); + openInDefaultApp(context, intent); + } + } + } + + /** + * Open the url with the system default browser. + *

+ * If no browser is set as default, fallbacks to + * {@link ShareUtils#openInDefaultApp(Context, Intent)} + *

+ * This calls {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} with true + * for the boolean parameter * * @param context the context to use * @param url the url to browse - */ + **/ public static void openUrlInBrowser(final Context context, final String url) { - final String defaultBrowserPackageName = getDefaultBrowserPackageName(context); + openUrlInBrowser(context, url, true); + } + + /** + * Open an intent with the system default app. + *

+ * The intent can be of every type, excepted a web intent for which + * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} should be used. + *

+ * If no app is set as default, fallbacks to + * {@link ShareUtils#openInDefaultApp(Context, Intent)} + * + * @param context the context to use + * @param intent the intent to open + */ + public static void openIntentInApp(final Context context, final Intent intent) { + final String defaultAppPackageName = getDefaultAppPackageName(context, intent); - if (defaultBrowserPackageName.equals("android")) { - // no browser set as default - openInDefaultApp(context, url); + if (defaultAppPackageName.equals("android")) { + // No app set as default (doesn't work on some devices) + openInDefaultApp(context, intent); } else { - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) - .setPackage(defaultBrowserPackageName) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); + try { + intent.setPackage(defaultAppPackageName); + context.startActivity(intent); + } catch (final ActivityNotFoundException e) { + // Not an app to open the intent but an app chooser because of OEMs changes + intent.setPackage(null); + openInDefaultApp(context, intent); + } } } @@ -45,20 +126,38 @@ public static void openUrlInBrowser(final Context context, final String url) { *

* If no app is set as default, it will open a chooser * + * @param context the context to use + * @param viewIntent the intent to open + */ + private static void openInDefaultApp(final Context context, final Intent viewIntent) { + final Intent intent = new Intent(Intent.ACTION_CHOOSER); + intent.putExtra(Intent.EXTRA_INTENT, viewIntent); + intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.open_with)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + /** + * Get the default app package name. + *

+ * If no app is set as default, it will return "android". + *

+ * Note: it doesn't return "android" on some devices because some OEMs changed the app chooser. + * * @param context the context to use - * @param url the url to browse + * @param intent the intent to get default app + * @return the package name of the default app, or the app chooser if there's no default */ - private static void openInDefaultApp(final Context context, final String url) { - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - context.startActivity(Intent.createChooser( - intent, context.getString(R.string.share_dialog_title)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + private static String getDefaultAppPackageName(final Context context, final Intent intent) { + return context.getPackageManager().resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName; } /** * Get the default browser package name. *

* If no browser is set as default, it will return "android" + * Note: it doesn't return "android" on some devices because some OEMs changed the app chooser. * * @param context the context to use * @return the package name of the default browser, or "android" if there's no default @@ -66,9 +165,8 @@ private static void openInDefaultApp(final Context context, final String url) { private static String getDefaultBrowserPackageName(final Context context) { final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity( - intent, PackageManager.MATCH_DEFAULT_ONLY); - return resolveInfo.activityInfo.packageName; + return context.getPackageManager().resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName; } /** @@ -78,14 +176,17 @@ private static String getDefaultBrowserPackageName(final Context context) { * @param subject the url subject, typically the title * @param url the url to share */ - public static void shareUrl(final Context context, final String subject, final String url) { - final Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, url); - context.startActivity(Intent.createChooser( - intent, context.getString(R.string.share_dialog_title)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + public static void shareText(final Context context, final String subject, final String url) { + final Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + shareIntent.putExtra(Intent.EXTRA_TEXT, url); + + final Intent intent = new Intent(Intent.ACTION_CHOOSER); + intent.putExtra(Intent.EXTRA_INTENT, shareIntent); + intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.share_dialog_title)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); } /** @@ -100,14 +201,11 @@ public static void copyToClipboard(final Context context, final String text) { ContextCompat.getSystemService(context, ClipboardManager.class); if (clipboardManager == null) { - Toast.makeText(context, - R.string.permission_denied, - Toast.LENGTH_LONG).show(); + Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_LONG).show(); return; } clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text)); - Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT) - .show(); + Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show(); } } diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipelegacy/util/StreamDialogEntry.java index c54fd73e0..2f668b347 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/StreamDialogEntry.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/StreamDialogEntry.java @@ -81,7 +81,7 @@ public enum StreamDialogEntry { }), share(R.string.share, (fragment, item) -> - ShareUtils.shareUrl(fragment.getContext(), item.getName(), item.getUrl())); + ShareUtils.shareText(fragment.getContext(), item.getName(), item.getUrl())); /////////////// diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/TextLinkifier.java b/app/src/main/java/org/schabi/newpipelegacy/util/TextLinkifier.java new file mode 100644 index 000000000..62a2af1e8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipelegacy/util/TextLinkifier.java @@ -0,0 +1,145 @@ +package org.schabi.newpipelegacy.util; + +import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; +import android.text.util.Linkify; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.text.HtmlCompat; + +import io.noties.markwon.Markwon; +import io.noties.markwon.linkify.LinkifyPlugin; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public final class TextLinkifier { + public static final String TAG = TextLinkifier.class.getSimpleName(); + + private TextLinkifier() { + } + + /** + * Create web links for contents with an HTML description. + *

+ * This will call + * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}. + * + * @param context the context to use + * @param htmlBlock the htmlBlock to be linked + * @param textView the TextView to set the htmlBlock linked + * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)} + * will be called + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed + */ + public static Disposable createLinksFromHtmlBlock(final Context context, + final String htmlBlock, + final TextView textView, + final int htmlCompatFlag) { + return changeIntentsOfDescriptionLinks(context, + HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView); + } + + /** + * Create web links for contents with a plain text description. + *

+ * This will call + * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and + * {@link TextView#setText(CharSequence, TextView.BufferType)}. + * + * @param context the context to use + * @param plainTextBlock the block of plain text to be linked + * @param textView the TextView to set the plain text block linked + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed + */ + public static Disposable createLinksFromPlainText(final Context context, + final String plainTextBlock, + final TextView textView) { + textView.setAutoLinkMask(Linkify.WEB_URLS); + textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE); + return changeIntentsOfDescriptionLinks(context, textView.getText(), textView); + } + + /** + * Create web links for contents with a markdown description. + *

+ * This will call + * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)} + * after creating an {@link Markwon} object and using + * {@link Markwon#setMarkdown(TextView, String)}. + * + * @param context the context to use + * @param markdownBlock the block of markdown text to be linked + * @param textView the TextView to set the plain text block linked + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed + */ + public static Disposable createLinksFromMarkdownText(final Context context, + final String markdownBlock, + final TextView textView) { + final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build(); + markwon.setMarkdown(textView, markdownBlock); + return changeIntentsOfDescriptionLinks(context, textView.getText(), textView); + } + + /** + * Change links generated by libraries in the description of a content to a custom link action. + *

+ * Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of a + * content, this method will parse the {@link CharSequence} and replace all current web links + * with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}. + *

+ * This method is required in order to intercept links and e.g. show a confirmation dialog + * before opening a web link. + * + * @param context the context to use + * @param chars the CharSequence to be parsed + * @param textView the TextView in which the converted CharSequence will be applied + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed + */ + private static Disposable changeIntentsOfDescriptionLinks(final Context context, + final CharSequence chars, + final TextView textView) { + return Single.fromCallable(() -> { + final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars); + final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class); + + for (final URLSpan span : urls) { + final ClickableSpan clickableSpan = new ClickableSpan() { + public void onClick(@NonNull final View view) { + ShareUtils.openUrlInBrowser(context, span.getURL(), false); + } + }; + + textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span), + textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span)); + textBlockLinked.removeSpan(span); + } + + return textBlockLinked; + }).subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + textBlockLinked -> setTextViewCharSequence(textView, textBlockLinked), + throwable -> { + Log.e(TAG, "Unable to linkify text", throwable); + // this should never happen, but if it does, just fallback to it + setTextViewCharSequence(textView, chars); + }); + } + + private static void setTextViewCharSequence(final TextView textView, + final CharSequence charSequence) { + textView.setText(charSequence); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/org/schabi/newpipelegacy/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipelegacy/util/ThemeHelper.java index 6071ace0f..4cc0873de 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipelegacy/util/ThemeHelper.java @@ -22,7 +22,6 @@ import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; -import androidx.preference.PreferenceManager; import android.util.TypedValue; import android.view.ContextThemeWrapper; @@ -32,6 +31,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import org.schabi.newpipelegacy.R; import org.schabi.newpipe.extractor.NewPipe; diff --git a/app/src/main/java/org/schabi/newpipelegacy/views/CustomCollapsingToolbarLayout.java b/app/src/main/java/org/schabi/newpipelegacy/views/CustomCollapsingToolbarLayout.java index 91e27af88..bfe4a82ea 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/views/CustomCollapsingToolbarLayout.java +++ b/app/src/main/java/org/schabi/newpipelegacy/views/CustomCollapsingToolbarLayout.java @@ -2,10 +2,12 @@ import android.content.Context; import android.util.AttributeSet; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; + import com.google.android.material.appbar.CollapsingToolbarLayout; public class CustomCollapsingToolbarLayout extends CollapsingToolbarLayout { diff --git a/app/src/main/java/org/schabi/newpipelegacy/views/ExpandableSurfaceView.java b/app/src/main/java/org/schabi/newpipelegacy/views/ExpandableSurfaceView.java index e63b0e762..20f2d1180 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/views/ExpandableSurfaceView.java +++ b/app/src/main/java/org/schabi/newpipelegacy/views/ExpandableSurfaceView.java @@ -4,6 +4,7 @@ import android.os.Build; import android.util.AttributeSet; import android.view.SurfaceView; + import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT; diff --git a/app/src/main/java/org/schabi/newpipelegacy/views/FocusAwareCoordinator.java b/app/src/main/java/org/schabi/newpipelegacy/views/FocusAwareCoordinator.java index a11b3a5f6..55a644221 100644 --- a/app/src/main/java/org/schabi/newpipelegacy/views/FocusAwareCoordinator.java +++ b/app/src/main/java/org/schabi/newpipelegacy/views/FocusAwareCoordinator.java @@ -24,11 +24,12 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; - import android.view.WindowInsets; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; + import org.schabi.newpipelegacy.R; public final class FocusAwareCoordinator extends CoordinatorLayout { diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index c17201e4d..1a576e222 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -24,10 +24,6 @@ import android.os.IBinder; import android.os.Message; import android.os.Parcelable; - -import androidx.core.app.ServiceCompat; -import androidx.core.content.ContextCompat; -import androidx.preference.PreferenceManager; import android.util.Log; import android.util.SparseArray; import android.widget.Toast; @@ -37,6 +33,9 @@ import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat.Builder; +import androidx.core.app.ServiceCompat; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import org.schabi.newpipelegacy.R; import org.schabi.newpipelegacy.download.DownloadActivity; diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index dc267a3f1..722867ba3 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.app.NotificationManager; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.graphics.Color; @@ -44,6 +45,7 @@ import org.schabi.newpipelegacy.report.ErrorInfo; import org.schabi.newpipelegacy.report.UserAction; import org.schabi.newpipelegacy.util.NavigationHelper; +import org.schabi.newpipelegacy.util.ShareUtils; import java.io.File; import java.net.URI; @@ -346,10 +348,9 @@ private void viewWithFileProvider(Mission mission) { if (BuildConfig.DEBUG) Log.v(TAG, "Mime: " + mimeType + " package: " + BuildConfig.APPLICATION_ID + ".provider"); - Uri uri = resolveShareableUri(mission); + final Uri uri = resolveShareableUri(mission); - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); + Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(uri, mimeType); intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); @@ -363,7 +364,7 @@ private void viewWithFileProvider(Mission mission) { //mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); if (intent.resolveActivity(mContext.getPackageManager()) != null) { - mContext.startActivity(intent); + ShareUtils.openIntentInApp(mContext, intent); } else { Toast.makeText(mContext, R.string.toast_no_player, Toast.LENGTH_LONG).show(); } @@ -372,12 +373,23 @@ private void viewWithFileProvider(Mission mission) { private void shareFile(Mission mission) { if (checkInvalidFile(mission)) return; - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType(resolveMimeType(mission)); - intent.putExtra(Intent.EXTRA_STREAM, resolveShareableUri(mission)); - intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); + final Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType(resolveMimeType(mission)); + shareIntent.putExtra(Intent.EXTRA_STREAM, resolveShareableUri(mission)); + shareIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); + final Intent intent = new Intent(Intent.ACTION_CHOOSER); + intent.putExtra(Intent.EXTRA_INTENT, shareIntent); + intent.putExtra(Intent.EXTRA_TITLE, mContext.getString(R.string.share_dialog_title)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(Intent.createChooser(intent, null)); + try { + intent.setPackage("android"); + mContext.startActivity(intent); + } catch (final ActivityNotFoundException e) { + // falling back to OEM chooser if Android's system chooser was removed by the OEM + intent.setPackage(null); + mContext.startActivity(intent); + } } /** diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index a0a41d8fc..7a8dfb0cd 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -11,7 +11,6 @@ import android.os.Bundle; import android.os.Environment; import android.os.IBinder; -import androidx.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -21,6 +20,7 @@ import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c128c4214..a6b64da71 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -651,4 +651,5 @@ Demander à Android de personnaliser la couleur de la notification en fonction de la couleur principale de la miniature (noter que cela n’est pas disponible sur tous les appareils) Afficher la miniature Utiliser la miniature pour l\'arrière-plan de l’écran de verrouillage et les notifications + Ouvrir avec \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8ac951b69..82a5bda6c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc Open in browser Open in popup mode + Open with Share Download Download stream file diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 8f699722c..973182690 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -12,14 +12,13 @@ lines="253,325"/> + files="ListHelper.java" + lines="280,312"/> + files="ContentSettingsFragment.java" + lines="227,245"/> - @@ -41,8 +40,8 @@ lines="419,591"/> + files="WebMWriter.java" + lines="156,158"/> + files="Player.java"/> + files="VideoDetailFragment.java"/>