diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 088707b6e..70c81c7b1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -42,10 +42,6 @@ You'll see *exactly* what is sent, be able to add **your comments**, and then se * Create PRs that cover only **one specific issue/solution/bug**. Do not create PRs that are huge monoliths and could have been split into multiple independent contributions. * NewPipe uses [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor) to fetch data from services. If you need to change something there, you must test your changes in NewPipe. Telling NewPipe to use your extractor version can be accomplished by editing the `app/build.gradle` file: the comments under the "NewPipe libraries" section of `dependencies` will help you out. -### Kotlin in NewPipe -* NewPipe will remain mostly Java for time being -* Contributions containing a simple conversion from Java to Kotlin should be avoided. Conversions to Kotlin should only be done if Kotlin actually brings improvements like bug fixes or better performance which are not, or only with much more effort, implementable in Java. The core team sees Java as an easier to learn and generally well adopted programming language. - ### Creating a Pull Request (PR) * Make changes on a **separate branch** with a meaningful name, not on the _master_ branch or the _dev_ branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request (PR) on GitHub. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c750157c..9ae3a77c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: branches: - dev - master + - refactor - release** paths-ignore: - 'README.md' diff --git a/app/build.gradle b/app/build.gradle index 5502025a7..4652cf6e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -287,7 +287,7 @@ dependencies { implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0" // Date and time formatting - implementation "org.ocpsoft.prettytime:prettytime:5.0.7.Final" + implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final" // Jetpack Compose implementation(platform('androidx.compose:compose-bom:2024.02.01')) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1127c55a4..d11de9f47 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -367,6 +367,7 @@ + diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 7e83d9958..fd382adbf 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -22,6 +22,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; +import androidx.core.view.MenuProvider; import androidx.preference.PreferenceManager; import com.google.android.material.snackbar.Snackbar; @@ -99,6 +100,7 @@ public class ChannelFragment extends BaseStateFragment private MenuItem menuRssButton; private MenuItem menuNotifyButton; private SubscriptionEntity channelSubscription; + private MenuProvider menuProvider; public static ChannelFragment getInstance(final int serviceId, final String url, final String name) { @@ -121,7 +123,62 @@ public class ChannelFragment extends BaseStateFragment @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setHasOptionsMenu(true); + menuProvider = new MenuProvider() { + @Override + public void onCreateMenu(@NonNull final Menu menu, + @NonNull final MenuInflater inflater) { + inflater.inflate(R.menu.menu_channel, menu); + + if (DEBUG) { + Log.d(TAG, "onCreateOptionsMenu() called with: " + + "menu = [" + menu + "], inflater = [" + inflater + "]"); + } + + } + + @Override + public void onPrepareMenu(@NonNull final Menu menu) { + menuRssButton = menu.findItem(R.id.menu_item_rss); + menuNotifyButton = menu.findItem(R.id.menu_item_notify); + updateRssButton(); + updateNotifyButton(channelSubscription); + } + + @Override + public boolean onMenuItemSelected(@NonNull final MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_item_notify: + final boolean value = !item.isChecked(); + item.setEnabled(false); + setNotify(value); + break; + case R.id.action_settings: + NavigationHelper.openSettings(requireContext()); + break; + case R.id.menu_item_rss: + if (currentInfo != null) { + ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl()); + } + break; + case R.id.menu_item_openInBrowser: + if (currentInfo != null) { + ShareUtils.openUrlInBrowser(requireContext(), + currentInfo.getOriginalUrl()); + } + break; + case R.id.menu_item_share: + if (currentInfo != null) { + ShareUtils.shareText(requireContext(), name, + currentInfo.getOriginalUrl(), currentInfo.getAvatars()); + } + break; + default: + return false; + } + return true; + } + }; + activity.addMenuProvider(menuProvider); } @Override @@ -183,67 +240,10 @@ public class ChannelFragment extends BaseStateFragment } disposables.clear(); binding = null; + activity.removeMenuProvider(menuProvider); + menuProvider = null; } - - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onCreateOptionsMenu(@NonNull final Menu menu, - @NonNull final MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.menu_channel, menu); - - if (DEBUG) { - Log.d(TAG, "onCreateOptionsMenu() called with: " - + "menu = [" + menu + "], inflater = [" + inflater + "]"); - } - } - - @Override - public void onPrepareOptionsMenu(@NonNull final Menu menu) { - super.onPrepareOptionsMenu(menu); - menuRssButton = menu.findItem(R.id.menu_item_rss); - menuNotifyButton = menu.findItem(R.id.menu_item_notify); - updateNotifyButton(channelSubscription); - } - - @Override - public boolean onOptionsItemSelected(@NonNull final MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_item_notify: - final boolean value = !item.isChecked(); - item.setEnabled(false); - setNotify(value); - break; - case R.id.action_settings: - NavigationHelper.openSettings(requireContext()); - break; - case R.id.menu_item_rss: - if (currentInfo != null) { - ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl()); - } - break; - case R.id.menu_item_openInBrowser: - if (currentInfo != null) { - ShareUtils.openUrlInBrowser(requireContext(), currentInfo.getOriginalUrl()); - } - break; - case R.id.menu_item_share: - if (currentInfo != null) { - ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl(), - currentInfo.getAvatars()); - } - break; - default: - return super.onOptionsItemSelected(item); - } - return true; - } - - /*////////////////////////////////////////////////////////////////////////// // Channel Subscription //////////////////////////////////////////////////////////////////////////*/ @@ -408,6 +408,13 @@ public class ChannelFragment extends BaseStateFragment animate(binding.channelSubscribeButton, true, 100, AnimationType.LIGHT_SCALE_AND_ALPHA); } + private void updateRssButton() { + if (menuRssButton == null || currentInfo == null) { + return; + } + menuRssButton.setVisible(!TextUtils.isEmpty(currentInfo.getFeedUrl())); + } + private void updateNotifyButton(@Nullable final SubscriptionEntity subscription) { if (menuNotifyButton == null) { return; @@ -610,9 +617,7 @@ public class ChannelFragment extends BaseStateFragment binding.subChannelAvatarView.setVisibility(View.VISIBLE); } - if (menuRssButton != null) { - menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl())); - } + updateRssButton(); channelContentNotSupported = false; for (final Throwable throwable : result.getErrors()) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 9afb06344..6410fb9ee 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -352,6 +352,7 @@ public class PlaylistFragment extends BaseListInfoFragment ellipsizer.toggle()); + headerBinding.playlistDescription.setOnClickListener(v -> ellipsizer.toggle()); } else { headerBinding.playlistDescription.setVisibility(View.GONE); headerBinding.playlistDescriptionReadMore.setVisibility(View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java index a3f0384ad..839aa1813 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java @@ -1,9 +1,13 @@ package org.schabi.newpipe.info_list.holder; import static org.schabi.newpipe.util.ServiceHelper.getServiceById; +import static org.schabi.newpipe.util.text.TouchUtils.getOffsetForHorizontalLine; +import android.text.Spanned; import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; import android.text.style.URLSpan; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Button; @@ -25,7 +29,6 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.image.ImageStrategy; import org.schabi.newpipe.util.image.PicassoHelper; -import org.schabi.newpipe.util.text.CommentTextOnTouchListener; import org.schabi.newpipe.util.text.TextEllipsizer; public class CommentInfoItemHolder extends InfoItemHolder { @@ -128,7 +131,26 @@ public class CommentInfoItemHolder extends InfoItemHolder { textEllipsizer.ellipsize(); //noinspection ClickableViewAccessibility - itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE); + itemContentView.setOnTouchListener((v, event) -> { + final CharSequence text = itemContentView.getText(); + if (text instanceof Spanned buffer) { + final int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { + final int offset = getOffsetForHorizontalLine(itemContentView, event); + final var links = buffer.getSpans(offset, offset, ClickableSpan.class); + + if (links.length != 0) { + if (action == MotionEvent.ACTION_UP) { + links[0].onClick(itemContentView); + } + // we handle events that intersect links, so return true + return true; + } + } + } + return false; + }); itemView.setOnClickListener(view -> { textEllipsizer.toggle(); diff --git a/app/src/main/java/org/schabi/newpipe/util/text/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/text/CommentTextOnTouchListener.java deleted file mode 100644 index 5018a6120..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/text/CommentTextOnTouchListener.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.schabi.newpipe.util.text; - -import static org.schabi.newpipe.util.text.TouchUtils.getOffsetForHorizontalLine; - -import android.annotation.SuppressLint; -import android.text.Spanned; -import android.text.style.ClickableSpan; -import android.view.MotionEvent; -import android.view.View; -import android.widget.TextView; - -public class CommentTextOnTouchListener implements View.OnTouchListener { - public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(final View v, final MotionEvent event) { - if (!(v instanceof TextView)) { - return false; - } - final TextView widget = (TextView) v; - final CharSequence text = widget.getText(); - if (text instanceof Spanned) { - final Spanned buffer = (Spanned) text; - final int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { - final int offset = getOffsetForHorizontalLine(widget, event); - final ClickableSpan[] links = buffer.getSpans(offset, offset, ClickableSpan.class); - - if (links.length != 0) { - if (action == MotionEvent.ACTION_UP) { - links[0].onClick(widget); - } - // we handle events that intersect links, so return true - return true; - } - } - } - return false; - } -}