diff --git a/app/build.gradle b/app/build.gradle index d19b599d6ea..df04615b6e1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,7 +187,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:6a858368c86bc9a55abee586eb6c733e86c26b97' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:eb07d70a2ce03bee3cc74fc33b2e4173e1c21436' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 09e0857910b..805455d7e20 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -52,6 +52,7 @@ import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.PlaybackException; @@ -82,7 +83,7 @@ import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.EmptyFragment; -import org.schabi.newpipe.fragments.list.comments.CommentsFragment; +import org.schabi.newpipe.fragments.list.comments.CommentsFragmentContainer; import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; @@ -162,8 +163,12 @@ public final class VideoDetailFragment private boolean showRelatedItems; private boolean showDescription; private String selectedTabTag; - @AttrRes @NonNull final List tabIcons = new ArrayList<>(); - @StringRes @NonNull final List tabContentDescriptions = new ArrayList<>(); + @AttrRes + @NonNull + final List tabIcons = new ArrayList<>(); + @StringRes + @NonNull + final List tabContentDescriptions = new ArrayList<>(); private boolean tabSettingsChanged = false; private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates @@ -717,8 +722,8 @@ private View.OnTouchListener getOnControlsTouchListener() { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { animate(binding.touchAppendDetail, true, 250, AnimationType.ALPHA, 0, () -> - animate(binding.touchAppendDetail, false, 1500, - AnimationType.ALPHA, 1000)); + animate(binding.touchAppendDetail, false, 1500, + AnimationType.ALPHA, 1000)); } return false; }; @@ -768,6 +773,10 @@ public boolean onBackPressed() { Log.d(TAG, "onBackPressed() called"); } + if (callCommentFragmentOnBack()) { + return true; + } + // If we are in fullscreen mode just exit from it via first back press if (isFullscreen()) { if (!DeviceUtils.isTablet(activity)) { @@ -800,6 +809,18 @@ public boolean onBackPressed() { return true; } + private boolean callCommentFragmentOnBack() { + final String currentPage = pageAdapter.getItemTitle(binding.viewPager.getCurrentItem()); + if (COMMENTS_TAB_TAG.equals(currentPage)) { + final Fragment fragment = getFM() + .findFragmentById(R.id.fragment_container_view); + if (fragment instanceof BackPressable) { + return ((BackPressable) fragment).onBackPressed(); + } + } + return false; + } + private void setupFromHistoryItem(final StackItem item) { setAutoPlay(false); hideMainPlayerOnLoadingNewStream(); @@ -960,7 +981,7 @@ private void initTabs() { if (shouldShowComments()) { pageAdapter.addFragment( - CommentsFragment.getInstance(serviceId, url, title), COMMENTS_TAB_TAG); + CommentsFragmentContainer.getInstance(serviceId, url, title), COMMENTS_TAB_TAG); tabIcons.add(R.drawable.ic_comment); tabContentDescriptions.add(R.string.comments_tab_description); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 9e7cb757ccc..b064e96376e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -246,6 +246,12 @@ protected void onItemSelected(final InfoItem selectedItem) { } } + protected void onItemCallback(final InfoItem selectedItem) throws Exception { + if (DEBUG) { + Log.d(TAG, "onItemCallback() called with: selectedItem = [" + selectedItem + "]"); + } + } + @Override protected void initListeners() { super.initListeners(); @@ -283,6 +289,14 @@ public void held(final StreamInfoItem selectedItem) { infoListAdapter.setOnCommentsSelectedListener(this::onItemSelected); + infoListAdapter.setOnCommentsReplyListener(selectedItem -> { + try { + onItemCallback(selectedItem); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(this, "Opening comment reply fragment", e); + } + }); + // Ensure that there is always a scroll listener (e.g. when rotating the device) useNormalItemListScrollListener(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentReplyFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentReplyFragment.java new file mode 100644 index 00000000000..bd824c2394f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentReplyFragment.java @@ -0,0 +1,101 @@ +package org.schabi.newpipe.fragments.list.comments; + +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.schabi.newpipe.BaseFragment; +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; +import org.schabi.newpipe.fragments.BackPressable; +import org.schabi.newpipe.util.Constants; + +import java.io.IOException; + +import icepick.State; + +public class CommentReplyFragment extends BaseFragment implements BackPressable { + + @State + protected int serviceId = Constants.NO_SERVICE_ID; + @State + protected String name; + @State + protected String url; + @State + protected CommentsInfoItem comment; + @State + protected Page replies; + + public static CommentReplyFragment getInstance( + final int serviceId, final String url, + final String name, + final CommentsInfoItem comment, + final Page replies + ) throws IOException, ClassNotFoundException { + final CommentReplyFragment instance = new CommentReplyFragment(); + instance.setInitialData(serviceId, url, name, comment, replies); + return instance; + } + + public static CommentsFragmentContainer newInstance(final int serviceId, final String url, + final String name) { + final CommentsFragmentContainer fragment = new CommentsFragmentContainer(); + fragment.serviceId = serviceId; + fragment.url = url; + fragment.name = name; + return new CommentsFragmentContainer(); + } + + @Nullable + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.fragment_comments_reply, container, + false); + final ImageButton backButton = view.findViewById(R.id.backButton); + backButton.setOnClickListener(v -> closeSelf()); + final CommentsFragment commentsFragment = CommentsFragment.getInstance( + serviceId, url, name, comment + ); + final CommentsFragment commentsReplyFragment = CommentsFragment.getInstance( + serviceId, url, name, replies + ); + getChildFragmentManager().beginTransaction() + .add(R.id.commentFragment, commentsFragment).commit(); + getChildFragmentManager().beginTransaction() + .add(R.id.commentReplyFragment, commentsReplyFragment).commit(); + return view; + } + + protected void setInitialData(final int sid, final String u, final String title, + final CommentsInfoItem preComment, + final Page repliesPage + ) throws IOException, ClassNotFoundException { + this.serviceId = sid; + this.url = u; + this.name = !TextUtils.isEmpty(title) ? title : ""; + // clone comment object to avoid replies actually set null + this.comment = CommentUtils.clone(preComment); + comment.setReplies(null); + this.replies = repliesPage; + } + + @Override + public boolean onBackPressed() { + closeSelf(); + return true; + } + + private void closeSelf() { + getFM().beginTransaction().remove(this).commit(); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentUtils.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentUtils.java new file mode 100644 index 00000000000..782851f3730 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentUtils.java @@ -0,0 +1,24 @@ +package org.schabi.newpipe.fragments.list.comments; + +import org.schabi.newpipe.extractor.comments.CommentsInfo; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; +import org.schabi.newpipe.util.SerializedUtils; + +import java.io.IOException; + +final class CommentUtils { + private CommentUtils() { + } + + public static CommentsInfo clone( + final CommentsInfo item + ) throws IOException, SecurityException, NullPointerException, ClassNotFoundException { + return SerializedUtils.clone(item, CommentsInfo.class); + } + + public static CommentsInfoItem clone( + final CommentsInfoItem item + ) throws IOException, SecurityException, NullPointerException, ClassNotFoundException { + return SerializedUtils.clone(item, CommentsInfoItem.class); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java index 3b092cc2885..929765f8ce8 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java @@ -13,32 +13,68 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.comments.CommentsInfo; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.util.ExtractorHelper; +import java.util.List; + import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; public class CommentsFragment extends BaseListInfoFragment { private final CompositeDisposable disposables = new CompositeDisposable(); + private Page replies; + private CommentsInfoItem preComment; + private TextView emptyStateDesc; public static CommentsFragment getInstance(final int serviceId, final String url, final String name) { final CommentsFragment instance = new CommentsFragment(); - instance.setInitialData(serviceId, url, name); + instance.setInitialData(serviceId, url, name, null, null); + return instance; + } + + public static CommentsFragment getInstance(final int serviceId, final String url, + final String name, + final CommentsInfoItem preComment) { + final CommentsFragment instance = new CommentsFragment(); + instance.setInitialData(serviceId, url, name, null, preComment); return instance; } + public static CommentsFragment getInstance(final int serviceId, final String url, + final String name, + final Page replyPage) { + final CommentsFragment instance = new CommentsFragment(); + instance.setInitialData(serviceId, url, name, replyPage, null); + return instance; + } + + @Override + protected void onItemCallback(final InfoItem selectedItem) throws Exception { + super.onItemCallback(selectedItem); + CommentsFragmentContainer.setFragment(getFM(), (CommentsInfoItem) selectedItem); + } + public CommentsFragment() { super(UserAction.REQUESTED_COMMENTS); } + protected void setInitialData(final int sid, final String u, final String title, + final Page repliesPage, final CommentsInfoItem comment) { + this.replies = repliesPage; + this.preComment = comment; + super.setInitialData(sid, u, title); + } + @Override protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); @@ -74,7 +110,25 @@ protected Single> loadMoreItemsLog @Override protected Single loadResult(final boolean forceLoad) { - return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad); + if (replies == null) { + if (preComment == null) { + return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad); + } else { + return Single.fromCallable(() -> { + // get a info template + var info = ExtractorHelper.getCommentsInfo( + serviceId, url, forceLoad).blockingGet(); + // clone comment object to avoid relatedItems and nextPage actually set null + info = CommentUtils.clone(info); + // push preComment + info.setRelatedItems(List.of(preComment)); + info.setNextPage(null); + return info; + }); + } + } else { + return ExtractorHelper.getCommentsReplyInfo(serviceId, url, forceLoad, replies); + } } /*////////////////////////////////////////////////////////////////////////// @@ -99,11 +153,13 @@ public void handleResult(@NonNull final CommentsInfo result) { //////////////////////////////////////////////////////////////////////////*/ @Override - public void setTitle(final String title) { } + public void setTitle(final String title) { + } @Override public void onCreateOptionsMenu(@NonNull final Menu menu, - @NonNull final MenuInflater inflater) { } + @NonNull final MenuInflater inflater) { + } @Override protected boolean isGridLayout() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragmentContainer.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragmentContainer.java new file mode 100644 index 00000000000..29100c5c991 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragmentContainer.java @@ -0,0 +1,68 @@ +package org.schabi.newpipe.fragments.list.comments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentManager; + +import org.schabi.newpipe.BaseFragment; +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; +import org.schabi.newpipe.util.Constants; + +import java.io.IOException; + +import icepick.State; + + +public class CommentsFragmentContainer extends BaseFragment { + + @State + protected int serviceId = Constants.NO_SERVICE_ID; + @State + protected String url; + @State + protected String name; + + public static CommentsFragmentContainer getInstance( + final int serviceId, final String url, final String name) { + final CommentsFragmentContainer fragment = new CommentsFragmentContainer(); + fragment.serviceId = serviceId; + fragment.url = url; + fragment.name = name; + return fragment; + } + + @Override + public View onCreateView( + final LayoutInflater inflater, @Nullable final ViewGroup container, + final Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.fragment_container, container, false); + setFragment(getFM(), serviceId, url, name); + return view; + } + + public static void setFragment( + final FragmentManager fm, + final int sid, final String u, final String title) { + final CommentsFragment fragment = CommentsFragment.getInstance( + sid, u, title + ); + fm.beginTransaction().add(R.id.fragment_container_view, fragment).commit(); + } + + public static void setFragment( + final FragmentManager fm, final CommentsInfoItem comment + ) throws IOException, ClassNotFoundException { + final Page reply = comment.getReplies(); + final CommentReplyFragment fragment = CommentReplyFragment.getInstance( + comment.getServiceId(), comment.getUrl(), + comment.getName(), comment, reply + ); + fm.beginTransaction().add(R.id.fragment_container_view, fragment).commit(); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index 68f19ee9714..5e081a101af 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -54,6 +54,7 @@ public class InfoItemBuilder { private OnClickGesture onChannelSelectedListener; private OnClickGesture onPlaylistSelectedListener; private OnClickGesture onCommentsSelectedListener; + private OnClickGesture onCommentsReplyListener; public InfoItemBuilder(final Context context) { this.context = context; @@ -130,4 +131,13 @@ public void setOnCommentsSelectedListener( final OnClickGesture onCommentsSelectedListener) { this.onCommentsSelectedListener = onCommentsSelectedListener; } + + public OnClickGesture getOnCommentsReplyListener() { + return onCommentsReplyListener; + } + + public void setOnCommentsReplyListener( + final OnClickGesture onCommentsReplyListener) { + this.onCommentsReplyListener = onCommentsReplyListener; + } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index fb27593e7e0..714266149a7 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -110,6 +110,10 @@ public void setOnCommentsSelectedListener(final OnClickGesture infoItemBuilder.setOnCommentsSelectedListener(listener); } + public void setOnCommentsReplyListener(final OnClickGesture listener) { + infoItemBuilder.setOnCommentsReplyListener(listener); + } + public void setUseMiniVariant(final boolean useMiniVariant) { this.useMiniVariant = useMiniVariant; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index b900750a8f3..a12df08fe62 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -7,6 +7,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; @@ -43,6 +44,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { private final TextView itemContentView; private final TextView itemLikesCountView; private final TextView itemPublishedTime; + private final Button itemContentReplyButton; private String commentText; private String streamUrl; @@ -77,6 +79,7 @@ public String transformUrl(final Matcher match, final String url) { itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view); itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime); itemContentView = itemView.findViewById(R.id.itemCommentContentView); + itemContentReplyButton = itemView.findViewById(R.id.itemContentReplyButton); commentHorizontalPadding = (int) infoItemBuilder.getContext() .getResources().getDimension(R.dimen.comments_horizontal_padding); @@ -140,6 +143,20 @@ public void updateFromItem(final InfoItem infoItem, itemPublishedTime.setText(item.getTextualUploadDate()); } + if (item.getReplies() != null) { + itemContentReplyButton.setVisibility(View.VISIBLE); + itemContentReplyButton.setOnClickListener( + view -> itemBuilder.getOnCommentsReplyListener().selected(item) + ); + final int replyCount = item.getReplyCount(); + itemContentReplyButton.setText( + itemView.getContext().getResources().getQuantityString( + R.plurals.replies, replyCount, replyCount + )); + } else { + itemContentReplyButton.setVisibility(View.GONE); + } + itemView.setOnClickListener(view -> { toggleEllipsize(); if (itemBuilder.getOnCommentsSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 27009efd192..e6431b3781c 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -150,6 +150,28 @@ public static Single> getFeedInfoFallbackToChannelInfo( return maybeFeedInfo.switchIfEmpty(getChannelInfo(serviceId, url, true)); } + public static Single getCommentsReplyInfo(final int serviceId, final String url, + final boolean forceLoad, + final Page replyPage) { + checkServiceId(serviceId); + return checkCache(forceLoad, serviceId, + url + "?reply_placeholder_id=" + replyPage.getId(), + InfoItem.InfoType.COMMENT, + Single.fromCallable(() -> { + final var info = CommentsInfo.getInfo( + NewPipe.getService(serviceId), url); + // use CommentsInfo make a info template + final var replies = CommentsInfo.getMoreItems( + NewPipe.getService(serviceId), info, replyPage); + // push replies to info, replace original comments + info.setRelatedItems(replies.getItems()); + // set next page + info.setNextPage(replies.getNextPage()); + return info; + } + )); + } + public static Single getCommentsInfo(final int serviceId, final String url, final boolean forceLoad) { checkServiceId(serviceId); @@ -228,7 +250,7 @@ private static Single checkCache(final boolean forceLoad, load = actualLoadFromNetwork; } else { load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType), - actualLoadFromNetwork.toMaybe()) + actualLoadFromNetwork.toMaybe()) .firstElement() // Take the first valid .toSingle(); } @@ -239,10 +261,10 @@ private static Single checkCache(final boolean forceLoad, /** * Default implementation uses the {@link InfoCache} to get cached results. * - * @param the item type's class that extends {@link Info} - * @param serviceId the service to load from - * @param url the URL to load - * @param infoType the {@link InfoItem.InfoType} of the item + * @param the item type's class that extends {@link Info} + * @param serviceId the service to load from + * @param url the URL to load + * @param infoType the {@link InfoItem.InfoType} of the item * @return a {@link Single} that loads the item */ private static Maybe loadFromCache(final int serviceId, final String url, @@ -273,11 +295,12 @@ public static boolean isCached(final int serviceId, final String url, * Formats the text contained in the meta info list as HTML and puts it into the text view, * while also making the separator visible. If the list is null or empty, or the user chose not * to see meta information, both the text view and the separator are hidden - * @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 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 - * @param disposables disposables created by the method are added here and their lifecycle - * should be handled by the calling class + * @param disposables disposables created by the method are added here and their lifecycle + * should be handled by the calling class */ public static void showMetaInfoInTextView(@Nullable final List metaInfos, final TextView metaInfoTextView, @@ -286,7 +309,7 @@ public static void showMetaInfoInTextView(@Nullable final List metaInf final Context context = metaInfoTextView.getContext(); if (metaInfos == null || metaInfos.isEmpty() || !PreferenceManager.getDefaultSharedPreferences(context).getBoolean( - context.getString(R.string.show_meta_info_key), true)) { + context.getString(R.string.show_meta_info_key), true)) { metaInfoTextView.setVisibility(View.GONE); metaInfoSeparator.setVisibility(View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java b/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java index b4c196ce4fa..7c6c5bf87aa 100644 --- a/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java +++ b/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java @@ -8,10 +8,7 @@ import org.schabi.newpipe.MainActivity; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import java.io.IOException; import java.io.Serializable; import java.util.UUID; @@ -97,15 +94,9 @@ private T getItem(@NonNull final CacheData data, @NonNull final Class @NonNull private T clone(@NonNull final T item, - @NonNull final Class type) throws Exception { - final ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream(); - try (ObjectOutputStream objectOutput = new ObjectOutputStream(bytesOutput)) { - objectOutput.writeObject(item); - objectOutput.flush(); - } - final Object clone = new ObjectInputStream( - new ByteArrayInputStream(bytesOutput.toByteArray())).readObject(); - return type.cast(clone); + @NonNull final Class type + ) throws IOException, SecurityException, NullPointerException, ClassNotFoundException { + return SerializedUtils.clone(item, type); } private static final class CacheData { diff --git a/app/src/main/java/org/schabi/newpipe/util/SerializedUtils.java b/app/src/main/java/org/schabi/newpipe/util/SerializedUtils.java new file mode 100644 index 00000000000..dacfad46aff --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/SerializedUtils.java @@ -0,0 +1,29 @@ +package org.schabi.newpipe.util; + +import androidx.annotation.NonNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +public final class SerializedUtils { + private SerializedUtils() { + } + + @NonNull + public static T clone(@NonNull final T item, + @NonNull final Class type + ) throws IOException, SecurityException, ClassNotFoundException { + final ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream(); + try (ObjectOutputStream objectOutput = new ObjectOutputStream(bytesOutput)) { + objectOutput.writeObject(item); + objectOutput.flush(); + } + final Object clone = new ObjectInputStream( + new ByteArrayInputStream(bytesOutput.toByteArray())).readObject(); + return type.cast(clone); + } +} diff --git a/app/src/main/res/layout/fragment_comments.xml b/app/src/main/res/layout/fragment_comments.xml index b1b644d8c05..a798cb91f22 100644 --- a/app/src/main/res/layout/fragment_comments.xml +++ b/app/src/main/res/layout/fragment_comments.xml @@ -2,12 +2,12 @@ + android:layout_height="wrap_content"> diff --git a/app/src/main/res/layout/fragment_comments_reply.xml b/app/src/main/res/layout/fragment_comments_reply.xml new file mode 100644 index 00000000000..9d17952cdd2 --- /dev/null +++ b/app/src/main/res/layout/fragment_comments_reply.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_container.xml b/app/src/main/res/layout/fragment_container.xml new file mode 100644 index 00000000000..35514a6940a --- /dev/null +++ b/app/src/main/res/layout/fragment_container.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index ad73c5ff43e..080f0ee08b0 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -108,4 +108,23 @@ android:textSize="@dimen/video_item_search_upload_date_text_size" tools:text="1 year ago" /> +