Base implementation of showing playback positions in lists

This commit is contained in:
Vasiliy 2019-04-15 21:19:59 +03:00
parent 4e1423d224
commit 73be8cf074
No known key found for this signature in database
GPG Key ID: 9F74C4D2874D7523
16 changed files with 172 additions and 23 deletions

View File

@ -65,6 +65,12 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter = new InfoListAdapter(activity);
}
@Override
public void onDetach() {
infoListAdapter.dispose();
super.onDetach();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@ -2,12 +2,14 @@ package org.schabi.newpipe.info_list;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
@ -59,13 +61,14 @@ public class InfoItemBuilder {
this.context = context;
}
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem) {
return buildView(parent, infoItem, false);
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, @Nullable StreamStateEntity state) {
return buildView(parent, infoItem, state, false);
}
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, boolean useMiniVariant) {
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem,
@Nullable StreamStateEntity state, boolean useMiniVariant) {
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
holder.updateFromItem(infoItem);
holder.updateFromItem(infoItem, state);
return holder.itemView;
}

View File

@ -7,16 +7,17 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
@ -24,12 +25,16 @@ import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.FallbackViewHolder;
import org.schabi.newpipe.util.OnClickGesture;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
/*
* Created by Christian Schabesberger on 01.08.16.
*
@ -70,7 +75,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private static final int COMMENT_HOLDER_TYPE = 0x401;
private final InfoItemBuilder infoItemBuilder;
private final HistoryRecordManager historyRecordManager;
private final ArrayList<InfoItem> infoItemList;
private final ArrayList<StreamStateEntity> states;
private final CompositeDisposable stateLoaders;
private boolean useMiniVariant = false;
private boolean useGridVariant = false;
private boolean showFooter = false;
@ -88,7 +96,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
public InfoListAdapter(Activity a) {
infoItemBuilder = new InfoItemBuilder(a);
historyRecordManager = new HistoryRecordManager(a);
infoItemList = new ArrayList<>();
states = new ArrayList<>();
stateLoaders = new CompositeDisposable();
}
public void setOnStreamSelectedListener(OnClickGesture<StreamInfoItem> listener) {
@ -115,7 +126,17 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
this.useGridVariant = useGridVariant;
}
public void addInfoItemList(List<InfoItem> data) {
public void addInfoItemList(final List<InfoItem> data) {
stateLoaders.add(
historyRecordManager.loadStreamStateBatch(data)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(streamStateEntities -> {
addInfoItemList(data, streamStateEntities);
})
);
}
private void addInfoItemList(List<InfoItem> data, List<StreamStateEntity> statesEntities) {
if (data != null) {
if (DEBUG) {
Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + infoItemList.size() + ", data.size() = " + data.size());
@ -123,6 +144,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
int offsetStart = sizeConsideringHeaderOffset();
infoItemList.addAll(data);
states.addAll(statesEntities);
if (DEBUG) {
Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
@ -140,6 +162,16 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
public void addInfoItem(InfoItem data) {
stateLoaders.add(
historyRecordManager.loadStreamState(data)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(streamStateEntity -> {
addInfoItem(data, streamStateEntity);
})
);
}
private void addInfoItem(InfoItem data, StreamStateEntity state) {
if (data != null) {
if (DEBUG) {
Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + infoItemList.size() + ", thread = " + Thread.currentThread());
@ -147,6 +179,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
int positionInserted = sizeConsideringHeaderOffset();
infoItemList.add(data);
states.add(state);
if (DEBUG) {
Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
@ -167,6 +200,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return;
}
infoItemList.clear();
states.clear();
notifyDataSetChanged();
}
@ -284,7 +318,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
// If header isn't null, offset the items by -1
if (header != null) position--;
((InfoItemHolder) holder).updateFromItem(infoItemList.get(position));
((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), states.get(position));
} else if (holder instanceof HFHolder && position == 0 && header != null) {
((HFHolder) holder).view = header;
} else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) {
@ -301,4 +335,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
};
}
public void dispose() {
stateLoaders.clear();
}
}

View File

@ -1,9 +1,11 @@
package org.schabi.newpipe.info_list.holder;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
@ -38,8 +40,8 @@ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
}
@Override
public void updateFromItem(final InfoItem infoItem) {
super.updateFromItem(infoItem);
public void updateFromItem(final InfoItem infoItem, @Nullable final StreamStateEntity state) {
super.updateFromItem(infoItem, state);
if (!(infoItem instanceof ChannelInfoItem)) return;
final ChannelInfoItem item = (ChannelInfoItem) infoItem;

View File

@ -1,9 +1,11 @@
package org.schabi.newpipe.info_list.holder;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
@ -30,7 +32,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
}
@Override
public void updateFromItem(final InfoItem infoItem) {
public void updateFromItem(final InfoItem infoItem, @Nullable final StreamStateEntity state) {
if (!(infoItem instanceof ChannelInfoItem)) return;
final ChannelInfoItem item = (ChannelInfoItem) infoItem;

View File

@ -1,14 +1,14 @@
package org.schabi.newpipe.info_list.holder;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.util.Localization;
/*
* Created by Christian Schabesberger on 12.02.17.
@ -41,8 +41,8 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
}
@Override
public void updateFromItem(final InfoItem infoItem) {
super.updateFromItem(infoItem);
public void updateFromItem(final InfoItem infoItem, @Nullable final StreamStateEntity state) {
super.updateFromItem(infoItem, state);
if (!(infoItem instanceof CommentsInfoItem)) return;
final CommentsInfoItem item = (CommentsInfoItem) infoItem;

View File

@ -1,5 +1,6 @@
package org.schabi.newpipe.info_list.holder;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.util.Linkify;
import android.view.View;
@ -8,6 +9,7 @@ import android.widget.TextView;
import org.jsoup.helper.StringUtil;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
@ -65,7 +67,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
}
@Override
public void updateFromItem(final InfoItem infoItem) {
public void updateFromItem(final InfoItem infoItem, @Nullable final StreamStateEntity state) {
if (!(infoItem instanceof CommentsInfoItem)) return;
final CommentsInfoItem item = (CommentsInfoItem) infoItem;

View File

@ -1,9 +1,11 @@
package org.schabi.newpipe.info_list.holder;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
@ -35,5 +37,5 @@ public abstract class InfoItemHolder extends RecyclerView.ViewHolder {
this.itemBuilder = infoItemBuilder;
}
public abstract void updateFromItem(final InfoItem infoItem);
public abstract void updateFromItem(final InfoItem infoItem, @Nullable final StreamStateEntity state);
}

View File

@ -1,10 +1,12 @@
package org.schabi.newpipe.info_list.holder;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
@ -30,7 +32,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
}
@Override
public void updateFromItem(final InfoItem infoItem) {
public void updateFromItem(final InfoItem infoItem, @Nullable final StreamStateEntity state) {
if (!(infoItem instanceof PlaylistInfoItem)) return;
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;

View File

@ -1,10 +1,12 @@
package org.schabi.newpipe.info_list.holder;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
@ -40,8 +42,8 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
}
@Override
public void updateFromItem(final InfoItem infoItem) {
super.updateFromItem(infoItem);
public void updateFromItem(final InfoItem infoItem, @Nullable final StreamStateEntity state) {
super.updateFromItem(infoItem, state);
if (!(infoItem instanceof StreamInfoItem)) return;
final StreamInfoItem item = (StreamInfoItem) infoItem;

View File

@ -1,12 +1,15 @@
package org.schabi.newpipe.info_list.holder;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
@ -14,12 +17,15 @@ import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import java.util.concurrent.TimeUnit;
public class StreamMiniInfoItemHolder extends InfoItemHolder {
public final ImageView itemThumbnailView;
public final TextView itemVideoTitleView;
public final TextView itemUploaderView;
public final TextView itemDurationView;
public final ProgressBar itemProgressView;
StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
@ -28,6 +34,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView);
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
itemDurationView = itemView.findViewById(R.id.itemDurationView);
itemProgressView = itemView.findViewById(R.id.itemProgressView);
}
public StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
@ -35,7 +42,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
@Override
public void updateFromItem(final InfoItem infoItem) {
public void updateFromItem(final InfoItem infoItem, @Nullable final StreamStateEntity state) {
if (!(infoItem instanceof StreamInfoItem)) return;
final StreamInfoItem item = (StreamInfoItem) infoItem;
@ -47,13 +54,22 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
if (state != null) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
} else if (item.getStreamType() == StreamType.LIVE_STREAM) {
itemDurationView.setText(R.string.duration_live);
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
R.color.live_duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
itemProgressView.setVisibility(View.GONE);
} else {
itemDurationView.setVisibility(View.GONE);
itemProgressView.setVisibility(View.GONE);
}
// Default thumbnail is shown on error, while loading and if the url is empty

View File

@ -36,6 +36,7 @@ import org.schabi.newpipe.database.stream.dao.StreamDAO;
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
@ -220,6 +221,40 @@ public class HistoryRecordManager {
})).subscribeOn(Schedulers.io());
}
public Single<StreamStateEntity> loadStreamState(final InfoItem info) {
return Single.fromCallable(() -> {
final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
if (entities.isEmpty()) {
return null;
}
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst();
if (states.isEmpty()) {
return null;
}
return states.get(0);
}).subscribeOn(Schedulers.io());
}
public Single<List<StreamStateEntity>> loadStreamStateBatch(final List<InfoItem> infos) {
return Single.fromCallable(() -> {
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
for (InfoItem info : infos) {
final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
if (entities.isEmpty()) {
result.add(null);
continue;
}
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst();
if (states.isEmpty()) {
result.add(null);
continue;
}
result.add(states.get(0));
}
return result;
}).subscribeOn(Schedulers.io());
}
///////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////

View File

@ -23,7 +23,6 @@ import android.support.annotation.Nullable;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@ -48,10 +47,8 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService;
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.NavigationHelper;
@ -131,6 +128,12 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
subscriptionService = SubscriptionService.getInstance(activity);
}
@Override
public void onDetach() {
infoListAdapter.dispose();
super.onDetach();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {

View File

@ -66,4 +66,16 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Uploader"/>
<ProgressBar
android:id="@+id/itemProgressView"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:progressDrawable="?progress_horizontal_drawable"
android:layout_width="wrap_content"
android:layout_height="4dp"
android:layout_marginTop="-2dp"
android:layout_alignStart="@id/itemThumbnailView"
android:layout_alignEnd="@id/itemThumbnailView"
android:layout_below="@id/itemThumbnailView"/>
</RelativeLayout>

View File

@ -79,4 +79,16 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="2 years ago • 10M views"/>
<ProgressBar
android:id="@+id/itemProgressView"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:progressDrawable="?progress_horizontal_drawable"
android:layout_width="wrap_content"
android:layout_height="4dp"
android:layout_marginTop="-2dp"
android:layout_alignStart="@id/itemThumbnailView"
android:layout_alignEnd="@id/itemThumbnailView"
android:layout_below="@id/itemThumbnailView"/>
</RelativeLayout>

View File

@ -69,4 +69,16 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Uploader" />
<ProgressBar
android:id="@+id/itemProgressView"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:progressDrawable="?progress_horizontal_drawable"
android:layout_width="wrap_content"
android:layout_height="4dp"
android:layout_marginTop="-2dp"
android:layout_alignStart="@id/itemThumbnailView"
android:layout_alignEnd="@id/itemThumbnailView"
android:layout_below="@id/itemThumbnailView"/>
</RelativeLayout>