Merge pull request #2288 from nv95/playback_state_list

Playback positions in lists
This commit is contained in:
Tobias Groza 2019-06-23 19:44:13 +02:00 committed by GitHub
commit 61472a995f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1663 additions and 682 deletions

View File

@ -574,7 +574,7 @@ public class RouterActivity extends AppCompatActivity {
playQueue = new SinglePlayQueue((StreamInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue);
NavigationHelper.playOnMainPlayer(this, playQueue, true);
} else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) {
@ -587,11 +587,11 @@ public class RouterActivity extends AppCompatActivity {
playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue);
NavigationHelper.playOnMainPlayer(this, playQueue, true);
} else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.playOnBackgroundPlayer(this, playQueue);
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) {
NavigationHelper.playOnPopupPlayer(this, playQueue);
NavigationHelper.playOnPopupPlayer(this, playQueue, true);
}
}
};

View File

@ -50,6 +50,11 @@ public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity
" ORDER BY " + STREAM_ACCESS_DATE + " DESC")
public abstract Flowable<List<StreamHistoryEntry>> getHistory();
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID +
" = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
@Nullable
public abstract StreamHistoryEntity getLatestEntry(final long streamId);
@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
public abstract int deleteStreamHistory(final long streamId);

View File

@ -4,6 +4,9 @@ package org.schabi.newpipe.database.stream.model;
import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.ForeignKey;
import android.support.annotation.Nullable;
import java.util.concurrent.TimeUnit;
import static android.arch.persistence.room.ForeignKey.CASCADE;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID;
@ -22,6 +25,12 @@ public class StreamStateEntity {
final public static String JOIN_STREAM_ID = "stream_id";
final public static String STREAM_PROGRESS_TIME = "progress_time";
/** Playback state will not be saved, if playback time less than this threshold */
private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5;
/** Playback state will not be saved, if time left less than this threshold */
private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10;
@ColumnInfo(name = JOIN_STREAM_ID)
private long streamUid;
@ -48,4 +57,18 @@ public class StreamStateEntity {
public void setProgressTime(long progressTime) {
this.progressTime = progressTime;
}
public boolean isValid(int durationInSeconds) {
final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime);
return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS
&& seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof StreamStateEntity) {
return ((StreamStateEntity) obj).streamUid == streamUid
&& ((StreamStateEntity) obj).progressTime == progressTime;
} else return false;
}
}

View File

@ -89,12 +89,14 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import icepick.State;
import io.reactivex.Single;
@ -118,7 +120,7 @@ public class VideoDetailFragment
private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1;
private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2;
private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4;
private static final int COMMENTS_UPDATE_FLAG = 0x4;
private static final int COMMENTS_UPDATE_FLAG = 0x8;
private boolean autoPlayEnabled;
private boolean showRelatedStreams;
@ -136,6 +138,8 @@ public class VideoDetailFragment
private Disposable currentWorker;
@NonNull
private CompositeDisposable disposables = new CompositeDisposable();
@Nullable
private Disposable positionSubscriber = null;
private List<VideoStream> sortedVideoStreams;
private int selectedVideoStreamIndex = -1;
@ -153,6 +157,7 @@ public class VideoDetailFragment
private View thumbnailBackgroundButton;
private ImageView thumbnailImageView;
private ImageView thumbnailPlayButton;
private AnimatedProgressBar positionView;
private View videoTitleRoot;
private TextView videoTitleTextView;
@ -165,6 +170,7 @@ public class VideoDetailFragment
private TextView detailControlsDownload;
private TextView appendControlsDetail;
private TextView detailDurationView;
private TextView detailPositionView;
private LinearLayout videoDescriptionRootLayout;
private TextView videoUploadDateView;
@ -259,6 +265,8 @@ public class VideoDetailFragment
// Check if it was loading when the fragment was stopped/paused,
if (wasLoading.getAndSet(false)) {
selectAndLoadVideo(serviceId, url, name);
} else if (currentInfo != null) {
updateProgressInfo(currentInfo);
}
}
@ -268,8 +276,10 @@ public class VideoDetailFragment
PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this);
if (positionSubscriber != null) positionSubscriber.dispose();
if (currentWorker != null) currentWorker.dispose();
if (disposables != null) disposables.clear();
positionSubscriber = null;
currentWorker = null;
disposables = null;
}
@ -462,6 +472,7 @@ public class VideoDetailFragment
videoTitleTextView = rootView.findViewById(R.id.detail_video_title_view);
videoTitleToggleArrow = rootView.findViewById(R.id.detail_toggle_description_view);
videoCountView = rootView.findViewById(R.id.detail_view_count_view);
positionView = rootView.findViewById(R.id.position_view);
detailControlsBackground = rootView.findViewById(R.id.detail_controls_background);
detailControlsPopup = rootView.findViewById(R.id.detail_controls_popup);
@ -469,6 +480,7 @@ public class VideoDetailFragment
detailControlsDownload = rootView.findViewById(R.id.detail_controls_download);
appendControlsDetail = rootView.findViewById(R.id.touch_append_detail);
detailDurationView = rootView.findViewById(R.id.detail_duration_view);
detailPositionView = rootView.findViewById(R.id.detail_position_view);
videoDescriptionRootLayout = rootView.findViewById(R.id.detail_description_root_layout);
videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view);
@ -536,10 +548,10 @@ public class VideoDetailFragment
final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> {
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item), true);
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(getActivity(), new SinglePlayQueue(item));
NavigationHelper.enqueueOnPopupPlayer(getActivity(), new SinglePlayQueue(item), true);
break;
case 2:
if (getFragmentManager() != null) {
@ -890,11 +902,11 @@ public class VideoDetailFragment
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
if (append) {
NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue);
NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue, false);
} else {
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
final Intent intent = NavigationHelper.getPlayerIntent(
activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution
activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution, true
);
activity.startService(intent);
}
@ -914,9 +926,9 @@ public class VideoDetailFragment
private void openNormalBackgroundPlayer(final boolean append) {
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
if (append) {
NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue);
NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue, false);
} else {
NavigationHelper.playOnBackgroundPlayer(activity, itemQueue);
NavigationHelper.playOnBackgroundPlayer(activity, itemQueue, true);
}
}
@ -926,7 +938,7 @@ public class VideoDetailFragment
mIntent = NavigationHelper.getPlayerIntent(activity,
MainVideoPlayer.class,
playQueue,
getSelectedVideoStream().getResolution());
getSelectedVideoStream().getResolution(), true);
startActivity(mIntent);
}
@ -1032,6 +1044,8 @@ public class VideoDetailFragment
animateView(spinnerToolbar, false, 200);
animateView(thumbnailPlayButton, false, 50);
animateView(detailDurationView, false, 100);
animateView(detailPositionView, false, 100);
animateView(positionView, false, 50);
videoTitleTextView.setText(name != null ? name : "");
videoTitleTextView.setMaxLines(1);
@ -1146,6 +1160,7 @@ public class VideoDetailFragment
videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate()));
}
prepareDescription(info.getDescription());
updateProgressInfo(info);
animateView(spinnerToolbar, true, 500);
setupActionBar(info);
@ -1250,4 +1265,37 @@ public class VideoDetailFragment
showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema);
}
private void updateProgressInfo(@NonNull final StreamInfo info) {
if (positionSubscriber != null) {
positionSubscriber.dispose();
}
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
final boolean playbackResumeEnabled =
prefs.getBoolean(activity.getString(R.string.enable_watch_history_key), true)
&& prefs.getBoolean(activity.getString(R.string.enable_playback_resume_key), true);
if (!playbackResumeEnabled || info.getDuration() <= 0) {
positionView.setVisibility(View.INVISIBLE);
detailPositionView.setVisibility(View.GONE);
return;
}
final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext());
positionSubscriber = recordManager.loadStreamState(info)
.subscribeOn(Schedulers.io())
.onErrorComplete()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(state -> {
final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime());
positionView.setMax((int) info.getDuration());
positionView.setProgressAnimated(seconds);
detailPositionView.setText(Localization.getDurationString(seconds));
animateView(positionView, true, 500);
animateView(detailPositionView, true, 500);
}, e -> {
if (DEBUG) e.printStackTrace();
}, () -> {
animateView(positionView, false, 500);
animateView(detailPositionView, false, 500);
});
}
}

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);
@ -94,6 +100,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
updateFlags = 0;
}
itemsList.post(infoListAdapter::updateStates);
}
/*//////////////////////////////////////////////////////////////////////////
@ -266,13 +274,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
switch (i) {
case 0:
NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item));
NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item), true);
break;
case 1:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item), true);
break;
case 2:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item), true);
break;
case 3:
if (getFragmentManager() != null) {

View File

@ -170,19 +170,19 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item), false);
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item), false);
break;
case 2:
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index), true);
break;
case 3:
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index), true);
break;
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index), true);
break;
case 5:
if (getFragmentManager() != null) {
@ -440,11 +440,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
monitorSubscription(result);
headerPlayAllButton.setOnClickListener(
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnClickListener(
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
}
private PlayQueue getPlayQueue() {

View File

@ -154,22 +154,22 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item), false);
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item), false);
break;
case 2:
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index), true);
break;
case 3:
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index), true);
break;
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index), true);
break;
case 5:
ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl());
ShareUtils.shareUrl(requireContext(), item.getName(), item.getUrl());
break;
default:
break;
@ -301,19 +301,19 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
.subscribe(getPlaylistBookmarkSubscriber());
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue());
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
return true;
});
headerBackgroundButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue());
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
return true;
});
}

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

@ -1,22 +1,25 @@
package org.schabi.newpipe.info_list;
import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
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;
@ -50,7 +53,7 @@ import java.util.List;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public class InfoListAdapter extends StateObjectsListAdapter {
private static final String TAG = InfoListAdapter.class.getSimpleName();
private static final boolean DEBUG = false;
@ -87,6 +90,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
public InfoListAdapter(Activity a) {
super(a.getApplicationContext());
infoItemBuilder = new InfoItemBuilder(a);
infoItemList = new ArrayList<>();
}
@ -115,50 +119,64 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
this.useGridVariant = useGridVariant;
}
public void addInfoItemList(List<InfoItem> data) {
public void addInfoItemList(@Nullable final List<InfoItem> data) {
if (data != null) {
if (DEBUG) {
Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + infoItemList.size() + ", data.size() = " + data.size());
}
int offsetStart = sizeConsideringHeaderOffset();
infoItemList.addAll(data);
if (DEBUG) {
Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
}
notifyItemRangeInserted(offsetStart, data.size());
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(offsetStart, footerNow);
if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + " to " + footerNow);
}
loadStates(data, infoItemList.size(), () -> addInfoItemListImpl(data));
}
}
public void addInfoItem(InfoItem data) {
private void addInfoItemListImpl(@NonNull List<InfoItem> data) {
if (DEBUG) {
Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + infoItemList.size() + ", data.size() = " + data.size());
}
int offsetStart = sizeConsideringHeaderOffset();
infoItemList.addAll(data);
if (DEBUG) {
Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
}
notifyItemRangeInserted(offsetStart, data.size());
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(offsetStart, footerNow);
if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + " to " + footerNow);
}
}
public void addInfoItem(@Nullable InfoItem data) {
if (data != null) {
if (DEBUG) {
Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + infoItemList.size() + ", thread = " + Thread.currentThread());
}
loadState(data, infoItemList.size(), () -> addInfoItemImpl(data));
}
}
int positionInserted = sizeConsideringHeaderOffset();
infoItemList.add(data);
private void addInfoItemImpl(@NonNull InfoItem data) {
if (DEBUG) {
Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + infoItemList.size() + ", thread = " + Thread.currentThread());
}
if (DEBUG) {
Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
}
notifyItemInserted(positionInserted);
int positionInserted = sizeConsideringHeaderOffset();
infoItemList.add(data);
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(positionInserted, footerNow);
if (DEBUG) {
Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
}
notifyItemInserted(positionInserted);
if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + " to " + footerNow);
}
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(positionInserted, footerNow);
if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + " to " + footerNow);
}
}
public void updateStates() {
if (!infoItemList.isEmpty()) {
updateAllStates(infoItemList);
}
}
@ -167,6 +185,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return;
}
infoItemList.clear();
clearStates();
notifyDataSetChanged();
}
@ -240,8 +259,9 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
if (DEBUG)
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
switch (type) {
@ -278,13 +298,13 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + holder.getClass().getSimpleName() + "], position = [" + position + "]");
if (holder instanceof InfoItemHolder) {
// 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), getState(position));
} else if (holder instanceof HFHolder && position == 0 && header != null) {
((HFHolder) holder).view = header;
} else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) {
@ -292,6 +312,28 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
for (Object payload : payloads) {
if (payload instanceof StreamStateEntity) {
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1),
(StreamStateEntity) payload);
} else if (payload instanceof Boolean) {
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1),
null);
}
}
} else {
onBindViewHolder(holder, position);
}
}
@Override
protected void onItemStateChanged(int position, @Nullable StreamStateEntity state) {
notifyItemChanged(header == null ? position : position + 1, state != null ? state : false);
}
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() {
@Override

View File

@ -0,0 +1,189 @@
package org.schabi.newpipe.info_list;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.SparseArrayUtils;
import java.util.List;
import java.util.Objects;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
public abstract class StateObjectsListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final SparseArray<StreamStateEntity> states;
private final HistoryRecordManager recordManager;
private final CompositeDisposable stateLoaders;
private final Context context;
public StateObjectsListAdapter(Context context) {
this.states = new SparseArray<>();
this.recordManager = new HistoryRecordManager(context);
this.context = context;
this.stateLoaders = new CompositeDisposable();
}
@Nullable
public StreamStateEntity getState(int position) {
return states.get(position);
}
protected void clearStates() {
states.clear();
}
private void appendStates(List<StreamStateEntity> statesEntities, int offset) {
for (int i = 0; i < statesEntities.size(); i++) {
final StreamStateEntity state = statesEntities.get(i);
if (state != null) {
states.append(offset + i, state);
}
}
}
private void appendState(StreamStateEntity statesEntity, int offset) {
if (statesEntity != null) {
states.append(offset, statesEntity);
}
}
protected void removeState(int index) {
states.remove(index);
}
protected void moveState(int from, int to) {
final StreamStateEntity item = states.get(from);
if (from < to) {
SparseArrayUtils.shiftItemsDown(states, from, to);
} else {
SparseArrayUtils.shiftItemsUp(states, to, from);
}
states.put(to, item);
}
protected void loadStates(List<InfoItem> list, int offset, Runnable callback) {
if (isPlaybackStatesVisible()) {
stateLoaders.add(
recordManager.loadStreamStateBatch(list)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(streamStateEntities -> {
appendStates(streamStateEntities, offset);
callback.run();
}, throwable -> {
if (BuildConfig.DEBUG) throwable.printStackTrace();
callback.run();
})
);
} else {
callback.run();
}
}
protected void loadState(InfoItem item, int offset, Runnable callback) {
if (isPlaybackStatesVisible()) {
stateLoaders.add(
recordManager.loadStreamState(item)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(streamStateEntities -> {
appendState(streamStateEntities[0], offset);
callback.run();
}, throwable -> {
if (BuildConfig.DEBUG) throwable.printStackTrace();
callback.run();
})
);
} else {
callback.run();
}
}
protected void loadStatesForLocal(List<? extends LocalItem> list, int offset, Runnable callback) {
if (isPlaybackStatesVisible()) {
stateLoaders.add(
recordManager.loadLocalStreamStateBatch(list)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(streamStateEntities -> {
appendStates(streamStateEntities, offset);
callback.run();
}, throwable -> {
if (BuildConfig.DEBUG) throwable.printStackTrace();
callback.run();
})
);
} else {
callback.run();
}
}
private void processStatesUpdates(List<StreamStateEntity> streamStateEntities) {
for (int i = 0; i < streamStateEntities.size(); i++) {
final StreamStateEntity newState = streamStateEntities.get(i);
if (!Objects.equals(states.get(i), newState)) {
if (newState == null) {
states.remove(i);
} else {
states.put(i, newState);
}
onItemStateChanged(i, newState);
}
}
}
protected void updateAllStates(List<InfoItem> list) {
if (isPlaybackStatesVisible()) {
stateLoaders.add(
recordManager.loadStreamStateBatch(list)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::processStatesUpdates, throwable -> {
if (BuildConfig.DEBUG) throwable.printStackTrace();
})
);
} else {
final int[] positions = SparseArrayUtils.getKeys(states);
states.clear();
for (int pos : positions) onItemStateChanged(pos, null);
}
}
protected void updateAllLocalStates(List<? extends LocalItem> list) {
if (isPlaybackStatesVisible()) {
stateLoaders.add(
recordManager.loadLocalStreamStateBatch(list)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::processStatesUpdates, throwable -> {
if (BuildConfig.DEBUG) throwable.printStackTrace();
})
);
} else {
final int[] positions = SparseArrayUtils.getKeys(states);
states.clear();
for (int pos : positions) onItemStateChanged(pos, null);
}
}
public void dispose() {
stateLoaders.dispose();
}
protected boolean isPlaybackStatesVisible() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)
&& prefs.getBoolean(context.getString(R.string.enable_playback_resume_key), true)
&& prefs.getBoolean(context.getString(R.string.enable_playback_state_lists_key), true);
}
protected abstract void onItemStateChanged(int position, @Nullable StreamStateEntity state);
}

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,8 @@ 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);
public void updateState(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,5 +1,6 @@
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;
@ -7,12 +8,17 @@ 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.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.util.concurrent.TimeUnit;
public class StreamMiniInfoItemHolder extends InfoItemHolder {
@ -20,6 +26,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
public final TextView itemVideoTitleView;
public final TextView itemUploaderView;
public final TextView itemDurationView;
public final AnimatedProgressBar itemProgressView;
StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
@ -28,6 +35,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 +43,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 +55,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
@ -83,6 +100,22 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
}
@Override
public void updateState(final InfoItem infoItem, @Nullable final StreamStateEntity state) {
final StreamInfoItem item = (StreamInfoItem) infoItem;
if (state != null && item.getDuration() > 0 && item.getStreamType() != StreamType.LIVE_STREAM) {
itemProgressView.setMax((int) item.getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
AnimationUtils.animateView(itemProgressView, false, 500);
}
}
private void enableLongClick(final StreamInfoItem item) {
itemView.setLongClickable(true);
itemView.setOnLongClickListener(view -> {

View File

@ -76,6 +76,8 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
}
updateFlags = 0;
}
itemsList.post(itemListAdapter::updateStates);
}
/*//////////////////////////////////////////////////////////////////////////
@ -150,6 +152,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
public void onDestroyView() {
super.onDestroyView();
itemsList = null;
itemListAdapter.dispose();
itemListAdapter = null;
}

View File

@ -1,6 +1,8 @@
package org.schabi.newpipe.local;
import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
@ -8,6 +10,8 @@ import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.info_list.StateObjectsListAdapter;
import org.schabi.newpipe.local.holder.LocalItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
@ -45,7 +49,7 @@ import java.util.List;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public class LocalItemListAdapter extends StateObjectsListAdapter {
private static final String TAG = LocalItemListAdapter.class.getSimpleName();
private static final boolean DEBUG = false;
@ -72,6 +76,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
private View footer = null;
public LocalItemListAdapter(Activity activity) {
super(activity.getApplicationContext());
localItemBuilder = new LocalItemBuilder(activity);
localItems = new ArrayList<>();
dateFormat = DateFormat.getDateInstance(DateFormat.SHORT,
@ -86,39 +91,49 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
localItemBuilder.setOnItemSelectedListener(null);
}
public void addItems(List<? extends LocalItem> data) {
public void addItems(@Nullable List<? extends LocalItem> data) {
if (data != null) {
if (DEBUG) {
Log.d(TAG, "addItems() before > localItems.size() = " +
localItems.size() + ", data.size() = " + data.size());
}
loadStatesForLocal(data, localItems.size(), () -> addItemsImpl(data));
}
}
int offsetStart = sizeConsideringHeader();
localItems.addAll(data);
private void addItemsImpl(@NonNull List<? extends LocalItem> data) {
if (DEBUG) {
Log.d(TAG, "addItems() before > localItems.size() = " +
localItems.size() + ", data.size() = " + data.size());
}
if (DEBUG) {
Log.d(TAG, "addItems() after > offsetStart = " + offsetStart +
", localItems.size() = " + localItems.size() +
", header = " + header + ", footer = " + footer +
", showFooter = " + showFooter);
}
int offsetStart = sizeConsideringHeader();
localItems.addAll(data);
notifyItemRangeInserted(offsetStart, data.size());
if (DEBUG) {
Log.d(TAG, "addItems() after > offsetStart = " + offsetStart +
", localItems.size() = " + localItems.size() +
", header = " + header + ", footer = " + footer +
", showFooter = " + showFooter);
}
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeader();
notifyItemMoved(offsetStart, footerNow);
notifyItemRangeInserted(offsetStart, data.size());
if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart +
" to " + footerNow);
}
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeader();
notifyItemMoved(offsetStart, footerNow);
if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart +
" to " + footerNow);
}
}
public void updateStates() {
if (!localItems.isEmpty()) {
updateAllLocalStates(localItems);
}
}
public void removeItem(final LocalItem data) {
final int index = localItems.indexOf(data);
localItems.remove(index);
removeState(index);
notifyItemRemoved(index + (header != null ? 1 : 0));
}
@ -130,6 +145,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
if (actualFrom >= localItems.size() || actualTo >= localItems.size()) return false;
localItems.add(actualTo, localItems.remove(actualFrom));
moveState(actualFrom, actualTo);
notifyItemMoved(fromAdapterPosition, toAdapterPosition);
return true;
}
@ -139,6 +155,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
return;
}
localItems.clear();
clearStates();
notifyDataSetChanged();
}
@ -259,7 +276,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
// If header isn't null, offset the items by -1
if (header != null) position--;
((LocalItemHolder) holder).updateFromItem(localItems.get(position), dateFormat);
((LocalItemHolder) holder).updateFromItem(localItems.get(position), getState(position), dateFormat);
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
((HeaderFooterHolder) holder).view = header;
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
@ -268,6 +285,28 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
for (Object payload : payloads) {
if (payload instanceof StreamStateEntity) {
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1),
(StreamStateEntity) payload);
} else if (payload instanceof Boolean) {
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1),
null);
}
}
} else {
onBindViewHolder(holder, position);
}
}
@Override
protected void onItemStateChanged(int position, @Nullable StreamStateEntity state) {
notifyItemChanged(header == null ? position : position + 1, state != null ? state : false);
}
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() {
@Override

View File

@ -112,7 +112,10 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
public void onDestroyView() {
super.onDestroyView();
if (playlistReactor != null) playlistReactor.dispose();
if (playlistAdapter != null) playlistAdapter.unsetSelectedListener();
if (playlistAdapter != null) {
playlistAdapter.dispose();
playlistAdapter.unsetSelectedListener();
}
playlistReactor = null;
playlistRecyclerView = null;

View File

@ -26,23 +26,29 @@ import android.support.annotation.NonNull;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.AppDatabase;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Single;
@ -80,9 +86,9 @@ public class HistoryRecordManager {
final Date currentTime = new Date();
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
final long streamId = streamTable.upsert(new StreamEntity(info));
StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry();
StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
if (latestEntry != null && latestEntry.getStreamUid() == streamId) {
if (latestEntry != null) {
streamHistoryTable.delete(latestEntry);
latestEntry.setAccessDate(currentTime);
latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1);
@ -99,7 +105,7 @@ public class HistoryRecordManager {
}
public Single<Integer> deleteWholeStreamHistory() {
return Single.fromCallable(() -> streamHistoryTable.deleteAll())
return Single.fromCallable(streamHistoryTable::deleteAll)
.subscribeOn(Schedulers.io());
}
@ -160,7 +166,7 @@ public class HistoryRecordManager {
}
public Single<Integer> deleteWholeSearchHistory() {
return Single.fromCallable(() -> searchHistoryTable.deleteAll())
return Single.fromCallable(searchHistoryTable::deleteAll)
.subscribeOn(Schedulers.io());
}
@ -180,21 +186,104 @@ public class HistoryRecordManager {
// Stream State History
///////////////////////////////////////////////////////
@SuppressWarnings("unused")
public Maybe<StreamStateEntity> loadStreamState(final StreamInfo info) {
return Maybe.fromCallable(() -> streamTable.upsert(new StreamEntity(info)))
.flatMap(streamId -> streamStateTable.getState(streamId).firstElement())
.flatMap(states -> states.isEmpty() ? Maybe.empty() : Maybe.just(states.get(0)))
public Maybe<StreamHistoryEntity> getStreamHistory(final StreamInfo info) {
return Maybe.fromCallable(() -> {
final long streamId = streamTable.upsert(new StreamEntity(info));
return streamHistoryTable.getLatestEntry(streamId);
}).subscribeOn(Schedulers.io());
}
public Maybe<StreamStateEntity> loadStreamState(final PlayQueueItem queueItem) {
return queueItem.getStream()
.map((info) -> streamTable.upsert(new StreamEntity(info)))
.flatMapPublisher(streamStateTable::getState)
.firstElement()
.flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0)))
.filter(state -> state.isValid((int) queueItem.getDuration()))
.subscribeOn(Schedulers.io());
}
public Maybe<Long> saveStreamState(@NonNull final StreamInfo info, final long progressTime) {
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
public Maybe<StreamStateEntity> loadStreamState(final StreamInfo info) {
return Single.fromCallable(() -> streamTable.upsert(new StreamEntity(info)))
.flatMapPublisher(streamStateTable::getState)
.firstElement()
.flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0)))
.filter(state -> state.isValid((int) info.getDuration()))
.subscribeOn(Schedulers.io());
}
public Completable saveStreamState(@NonNull final StreamInfo info, final long progressTime) {
return Completable.fromAction(() -> database.runInTransaction(() -> {
final long streamId = streamTable.upsert(new StreamEntity(info));
return streamStateTable.upsert(new StreamStateEntity(streamId, progressTime));
final StreamStateEntity state = new StreamStateEntity(streamId, progressTime);
if (state.isValid((int) info.getDuration())) {
streamStateTable.upsert(state);
} else {
streamStateTable.deleteState(streamId);
}
})).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 new StreamStateEntity[]{null};
}
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst();
if (states.isEmpty()) {
return new StreamStateEntity[]{null};
}
return new StreamStateEntity[]{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());
}
public Single<List<StreamStateEntity>> loadLocalStreamStateBatch(final List<? extends LocalItem> items) {
return Single.fromCallable(() -> {
final List<StreamStateEntity> result = new ArrayList<>(items.size());
for (LocalItem item : items) {
long streamId;
if (item instanceof StreamStatisticsEntry) {
streamId = ((StreamStatisticsEntry) item).streamId;
} else if (item instanceof PlaylistStreamEntity) {
streamId = ((PlaylistStreamEntity) item).getStreamUid();
} else if (item instanceof PlaylistStreamEntry) {
streamId = ((PlaylistStreamEntry) item).streamId;
} else {
result.add(null);
continue;
}
final List<StreamStateEntity> states = streamStateTable.getState(streamId).blockingFirst();
if (states.isEmpty()) {
result.add(null);
continue;
}
result.add(states.get(0));
}
return result;
}).subscribeOn(Schedulers.io());
}
///////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////

View File

@ -310,11 +310,11 @@ public class StatisticsPlaylistFragment
}
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
sortButton.setOnClickListener(view -> toggleSortMode());
hideLoading();
@ -377,19 +377,19 @@ public class StatisticsPlaylistFragment
final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0);
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem));
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem), false);
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(infoItem));
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(infoItem), false);
break;
case 2:
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index), true);
break;
case 3:
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index), true);
break;
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index), true);
break;
case 5:
deleteEntry(index);

View File

@ -1,10 +1,12 @@
package org.schabi.newpipe.local.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.LocalItem;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.local.LocalItemBuilder;
import java.text.DateFormat;
@ -38,5 +40,8 @@ public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
this.itemBuilder = itemBuilder;
}
public abstract void updateFromItem(final LocalItem item, final DateFormat dateFormat);
public abstract void updateFromItem(final LocalItem item, @Nullable final StreamStateEntity state, final DateFormat dateFormat);
public void updateState(final LocalItem localItem, @Nullable final StreamStateEntity state) {
}
}

View File

@ -1,10 +1,12 @@
package org.schabi.newpipe.local.holder;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.util.ImageDisplayConstants;
@ -21,7 +23,7 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
}
@Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
public void updateFromItem(final LocalItem localItem, @Nullable final StreamStateEntity state, final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistMetadataEntry)) return;
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
@ -32,6 +34,6 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView,
ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS);
super.updateFromItem(localItem, dateFormat);
super.updateFromItem(localItem, state, dateFormat);
}
}

View File

@ -1,5 +1,6 @@
package org.schabi.newpipe.local.holder;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.view.MotionEvent;
import android.view.View;
@ -10,12 +11,16 @@ import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.text.DateFormat;
import java.util.concurrent.TimeUnit;
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
@ -24,6 +29,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
public final TextView itemAdditionalDetailsView;
public final TextView itemDurationView;
public final View itemHandleView;
public final AnimatedProgressBar itemProgressView;
LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
@ -33,6 +39,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
itemAdditionalDetailsView = itemView.findViewById(R.id.itemAdditionalDetails);
itemDurationView = itemView.findViewById(R.id.itemDurationView);
itemHandleView = itemView.findViewById(R.id.itemHandle);
itemProgressView = itemView.findViewById(R.id.itemProgressView);
}
public LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
@ -40,7 +47,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
}
@Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
public void updateFromItem(final LocalItem localItem, @Nullable final StreamStateEntity state, final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistStreamEntry)) return;
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
@ -53,6 +60,13 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
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.duration);
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
} else {
itemDurationView.setVisibility(View.GONE);
}
@ -79,6 +93,23 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
itemHandleView.setOnTouchListener(getOnTouchListener(item));
}
@Override
public void updateState(LocalItem localItem, @Nullable StreamStateEntity state) {
if (!(localItem instanceof PlaylistStreamEntry)) return;
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
if (state != null && item.duration > 0) {
itemProgressView.setMax((int) item.duration);
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
AnimationUtils.animateView(itemProgressView, false, 500);
}
}
private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) {
return (view, motionEvent) -> {
view.performClick();

View File

@ -10,12 +10,16 @@ import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.text.DateFormat;
import java.util.concurrent.TimeUnit;
/*
* Created by Christian Schabesberger on 01.08.16.
@ -45,6 +49,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
public final TextView itemDurationView;
@Nullable
public final TextView itemAdditionalDetails;
public final AnimatedProgressBar itemProgressView;
public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup parent) {
this(itemBuilder, R.layout.list_stream_item, parent);
@ -58,6 +63,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
itemDurationView = itemView.findViewById(R.id.itemDurationView);
itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails);
itemProgressView = itemView.findViewById(R.id.itemProgressView);
}
private String getStreamInfoDetailLine(final StreamStatisticsEntry entry,
@ -70,7 +76,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
}
@Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
public void updateFromItem(final LocalItem localItem, @Nullable final StreamStateEntity state, final DateFormat dateFormat) {
if (!(localItem instanceof StreamStatisticsEntry)) return;
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
@ -82,8 +88,16 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
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.duration);
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
} else {
itemDurationView.setVisibility(View.GONE);
itemProgressView.setVisibility(View.GONE);
}
if (itemAdditionalDetails != null) {
@ -108,4 +122,21 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
return true;
});
}
@Override
public void updateState(LocalItem localItem, @Nullable StreamStateEntity state) {
if (!(localItem instanceof StreamStatisticsEntry)) return;
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
if (state != null && item.duration > 0) {
itemProgressView.setMax((int) item.duration);
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
AnimationUtils.animateView(itemProgressView, false, 500);
}
}
}

View File

@ -1,11 +1,13 @@
package org.schabi.newpipe.local.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.LocalItem;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.local.LocalItemBuilder;
import java.text.DateFormat;
@ -31,7 +33,7 @@ public abstract class PlaylistItemHolder extends LocalItemHolder {
}
@Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
public void updateFromItem(final LocalItem localItem, @Nullable final StreamStateEntity state, final DateFormat dateFormat) {
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnItemSelectedListener() != null) {
itemBuilder.getOnItemSelectedListener().selected(localItem);

View File

@ -1,9 +1,11 @@
package org.schabi.newpipe.local.holder;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.util.ImageDisplayConstants;
@ -21,7 +23,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
}
@Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
public void updateFromItem(final LocalItem localItem, @Nullable final StreamStateEntity state, final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistRemoteEntity)) return;
final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
@ -33,6 +35,6 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView,
ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS);
super.updateFromItem(localItem, dateFormat);
super.updateFromItem(localItem, state, dateFormat);
}
}

View File

@ -319,11 +319,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
setVideoCount(itemListAdapter.getItemsList().size());
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
hideLoading();
}
@ -534,20 +534,20 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
switch (i) {
case 0:
NavigationHelper.enqueueOnBackgroundPlayer(context,
new SinglePlayQueue(infoItem));
new SinglePlayQueue(infoItem), false);
break;
case 1:
NavigationHelper.enqueueOnPopupPlayer(activity, new
SinglePlayQueue(infoItem));
SinglePlayQueue(infoItem), false);
break;
case 2:
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index), true);
break;
case 3:
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index), true);
break;
case 4:
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index), true);
break;
case 5:
changeThumbnailUrl(item.thumbnailUrl);

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) {
@ -150,6 +153,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
}
updateFlags = 0;
}
itemsList.post(infoListAdapter::updateStates);
}
@Override

View File

@ -150,6 +150,7 @@ public final class BackgroundPlayer extends Service {
lockManager.releaseWifiAndCpu();
}
if (basePlayerImpl != null) {
basePlayerImpl.savePlaybackState();
basePlayerImpl.stopActivityBinding();
basePlayerImpl.destroy();
}

View File

@ -23,9 +23,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
@ -145,6 +147,8 @@ public abstract class BasePlayer implements
@NonNull
public static final String APPEND_ONLY = "append_only";
@NonNull
public static final String RESUME_PLAYBACK = "resume_playback";
@NonNull
public static final String SELECT_ON_APPEND = "select_on_append";
/*//////////////////////////////////////////////////////////////////////////
@ -279,8 +283,23 @@ public abstract class BasePlayer implements
) {
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
return;
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled()) {
final PlayQueueItem item = queue.getItem();
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET && isPlaybackResumeEnabled()) {
final Disposable stateLoader = recordManager.loadStreamState(item)
.observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
/*playOnInit=*/true))
.subscribe(
state -> queue.setRecovery(queue.getIndex(), state.getProgressTime()),
error -> {
if (DEBUG) error.printStackTrace();
}
);
databaseUpdateReactor.add(stateLoader);
return;
}
}
// Good to go...
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
/*playOnInit=*/true);
@ -615,6 +634,9 @@ public abstract class BasePlayer implements
break;
case Player.STATE_ENDED: // 4
changeState(STATE_COMPLETED);
if (currentMetadata != null) {
resetPlaybackState(currentMetadata.getMetadata());
}
isPrepared = false;
break;
}
@ -721,6 +743,7 @@ public abstract class BasePlayer implements
case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
case DISCONTINUITY_REASON_INTERNAL:
if (playQueue.getIndex() != newWindowIndex) {
resetPlaybackState(playQueue.getItem());
playQueue.setIndex(newWindowIndex);
}
break;
@ -750,6 +773,9 @@ public abstract class BasePlayer implements
@Override
public void onSeekProcessed() {
if (DEBUG) Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
if (isPrepared) {
savePlaybackState();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
@ -1017,27 +1043,40 @@ public abstract class BasePlayer implements
}
}
protected void savePlaybackState(final StreamInfo info, final long progress) {
private void savePlaybackState(final StreamInfo info, final long progress) {
if (info == null) return;
if (DEBUG) Log.d(TAG, "savePlaybackState() called");
final Disposable stateSaver = recordManager.saveStreamState(info, progress)
.observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> {
if (DEBUG) e.printStackTrace();
})
.onErrorComplete()
.subscribe(
ignored -> {/* successful */},
error -> Log.e(TAG, "savePlaybackState() failure: ", error)
);
.subscribe();
databaseUpdateReactor.add(stateSaver);
}
private void savePlaybackState() {
private void resetPlaybackState(final PlayQueueItem queueItem) {
if (queueItem == null) return;
final Disposable stateSaver = queueItem.getStream()
.flatMapCompletable(info -> recordManager.saveStreamState(info, 0))
.observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> {
if (DEBUG) e.printStackTrace();
})
.onErrorComplete()
.subscribe();
databaseUpdateReactor.add(stateSaver);
}
public void resetPlaybackState(final StreamInfo info) {
savePlaybackState(info, 0);
}
public void savePlaybackState() {
if (simpleExoPlayer == null || currentMetadata == null) return;
final StreamInfo currentInfo = currentMetadata.getMetadata();
if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD_MILLIS &&
simpleExoPlayer.getCurrentPosition() <
simpleExoPlayer.getDuration() - RECOVERY_SKIP_THRESHOLD_MILLIS) {
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
}
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
}
private void maybeUpdateCurrentMetadata() {
@ -1225,4 +1264,10 @@ public abstract class BasePlayer implements
public boolean gotDestroyed() {
return simpleExoPlayer == null;
}
private boolean isPlaybackResumeEnabled() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)
&& prefs.getBoolean(context.getString(R.string.enable_playback_resume_key), true);
}
}

View File

@ -248,6 +248,12 @@ public final class MainVideoPlayer extends AppCompatActivity
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(newBase));
}
@Override
protected void onPause() {
playerImpl.savePlaybackState();
super.onPause();
}
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
@ -583,7 +589,8 @@ public final class MainVideoPlayer extends AppCompatActivity
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackSkipSilence(),
this.getPlaybackQuality()
this.getPlaybackQuality(),
false
);
context.startService(intent);
@ -605,7 +612,8 @@ public final class MainVideoPlayer extends AppCompatActivity
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackSkipSilence(),
this.getPlaybackQuality()
this.getPlaybackQuality(),
false
);
context.startService(intent);

View File

@ -325,6 +325,7 @@ public final class PopupVideoPlayer extends Service {
isPopupClosing = true;
if (playerImpl != null) {
playerImpl.savePlaybackState();
if (playerImpl.getRootView() != null) {
windowManager.removeView(playerImpl.getRootView());
}
@ -565,7 +566,8 @@ public final class PopupVideoPlayer extends Service {
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackSkipSilence(),
this.getPlaybackQuality()
this.getPlaybackQuality(),
false
);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);

View File

@ -188,7 +188,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
this.player.getPlaybackSpeed(),
this.player.getPlaybackPitch(),
this.player.getPlaybackSkipSilence(),
null
null,
false
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}

View File

@ -542,6 +542,11 @@ public abstract class VideoPlayer extends BasePlayer
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
super.onPrepared(playWhenReady);
if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) {
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION);
}
}
@Override

View File

@ -124,7 +124,7 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds*1000);
NavigationHelper.playOnPopupPlayer(context, playQueue);
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
});
return true;
}

View File

@ -69,12 +69,14 @@ public class NavigationHelper {
public static Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class targetClazz,
@NonNull final PlayQueue playQueue,
@Nullable final String quality) {
@Nullable final String quality,
final boolean resumePlayback) {
Intent intent = new Intent(context, targetClazz);
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
if (cacheKey != null) intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey);
if (quality != null) intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality);
intent.putExtra(VideoPlayer.RESUME_PLAYBACK, resumePlayback);
return intent;
}
@ -82,16 +84,18 @@ public class NavigationHelper {
@NonNull
public static Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class targetClazz,
@NonNull final PlayQueue playQueue) {
return getPlayerIntent(context, targetClazz, playQueue, null);
@NonNull final PlayQueue playQueue,
final boolean resumePlayback) {
return getPlayerIntent(context, targetClazz, playQueue, null, resumePlayback);
}
@NonNull
public static Intent getPlayerEnqueueIntent(@NonNull final Context context,
@NonNull final Class targetClazz,
@NonNull final PlayQueue playQueue,
final boolean selectOnAppend) {
return getPlayerIntent(context, targetClazz, playQueue)
final boolean selectOnAppend,
final boolean resumePlayback) {
return getPlayerIntent(context, targetClazz, playQueue, resumePlayback)
.putExtra(BasePlayer.APPEND_ONLY, true)
.putExtra(BasePlayer.SELECT_ON_APPEND, selectOnAppend);
}
@ -104,40 +108,41 @@ public class NavigationHelper {
final float playbackSpeed,
final float playbackPitch,
final boolean playbackSkipSilence,
@Nullable final String playbackQuality) {
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality)
@Nullable final String playbackQuality,
final boolean resumePlayback) {
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback)
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed)
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch)
.putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence);
}
public static void playOnMainPlayer(final Context context, final PlayQueue queue) {
final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue);
public static void playOnMainPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue, resumePlayback);
playerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(playerIntent);
}
public static void playOnPopupPlayer(final Context context, final PlayQueue queue) {
public static void playOnPopupPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
if (!PermissionHelper.isPopupEnabled(context)) {
PermissionHelper.showPopupEnablementToast(context);
return;
}
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
startService(context, getPlayerIntent(context, PopupVideoPlayer.class, queue));
startService(context, getPlayerIntent(context, PopupVideoPlayer.class, queue, resumePlayback));
}
public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue) {
public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
startService(context, getPlayerIntent(context, BackgroundPlayer.class, queue));
startService(context, getPlayerIntent(context, BackgroundPlayer.class, queue, resumePlayback));
}
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue) {
enqueueOnPopupPlayer(context, queue, false);
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
enqueueOnPopupPlayer(context, queue, false, resumePlayback);
}
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) {
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend, final boolean resumePlayback) {
if (!PermissionHelper.isPopupEnabled(context)) {
PermissionHelper.showPopupEnablementToast(context);
return;
@ -145,17 +150,17 @@ public class NavigationHelper {
Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
startService(context,
getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend));
getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend, resumePlayback));
}
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue) {
enqueueOnBackgroundPlayer(context, queue, false);
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
enqueueOnBackgroundPlayer(context, queue, false, resumePlayback);
}
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) {
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend, final boolean resumePlayback) {
Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show();
startService(context,
getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend));
getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend, resumePlayback));
}
public static void startService(@NonNull final Context context, @NonNull final Intent intent) {

View File

@ -0,0 +1,30 @@
package org.schabi.newpipe.util;
import android.util.SparseArray;
public abstract class SparseArrayUtils {
public static <T> void shiftItemsDown(SparseArray<T> sparseArray, int lower, int upper) {
for (int i = lower + 1; i <= upper; i++) {
final T o = sparseArray.get(i);
sparseArray.put(i - 1, o);
sparseArray.remove(i);
}
}
public static <T> void shiftItemsUp(SparseArray<T> sparseArray, int lower, int upper) {
for (int i = upper - 1; i >= lower; i--) {
final T o = sparseArray.get(i);
sparseArray.put(i + 1, o);
sparseArray.remove(i);
}
}
public static <T> int[] getKeys(SparseArray<T> sparseArray) {
final int[] result = new int[sparseArray.size()];
for (int i = 0; i < result.length; i++) {
result[i] = sparseArray.keyAt(i);
}
return result;
}
}

View File

@ -0,0 +1,64 @@
package org.schabi.newpipe.views;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ProgressBar;
public final class AnimatedProgressBar extends ProgressBar {
@Nullable
private ProgressBarAnimation animation = null;
public AnimatedProgressBar(Context context) {
super(context);
}
public AnimatedProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AnimatedProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public synchronized void setProgressAnimated(int progress) {
cancelAnimation();
animation = new ProgressBarAnimation(this, getProgress(), progress);
startAnimation(animation);
}
private void cancelAnimation() {
if (animation != null) {
animation.cancel();
animation = null;
}
clearAnimation();
}
private static class ProgressBarAnimation extends Animation {
private final AnimatedProgressBar progressBar;
private final float from;
private final float to;
ProgressBarAnimation(AnimatedProgressBar progressBar, float from, float to) {
super();
this.progressBar = progressBar;
this.from = from;
this.to = to;
setDuration(500);
setInterpolator(new AccelerateDecelerateInterpolator());
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
float value = from + (to - from) * interpolatedTime;
progressBar.setProgress((int) value);
}
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="@color/dark_ripple_color" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="@color/dark_soundcloud_primary_color" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="@color/light_ripple_color" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="@color/light_soundcloud_primary_color" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="@color/dark_ripple_color" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="@color/dark_youtube_primary_color" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="@color/light_ripple_color" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="@color/light_youtube_primary_color" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/video_item_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:orientation="horizontal">
tools:ignore="RtlHardcoded"
android:orientation="horizontal"
android:baselineAligned="false">
<android.support.design.widget.CoordinatorLayout
android:id="@+id/detail_main_content"
@ -19,6 +21,7 @@
android:id="@+id/appbarlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:fitsSystemWindows="true"
app:elevation="0dp"
app:layout_behavior="android.support.design.widget.FlingBehavior">
@ -67,10 +70,10 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#64000000"
android:paddingBottom="10dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingTop="10dp"
android:paddingRight="30dp"
android:paddingBottom="10dp"
android:text="@string/hold_to_append"
android:textColor="@android:color/white"
android:textSize="20sp"
@ -84,17 +87,17 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp"
android:alpha=".6"
android:background="#23000000"
android:gravity="center"
android:paddingBottom="2dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="2dp"
android:paddingRight="6dp"
android:paddingBottom="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
@ -103,10 +106,48 @@
tools:ignore="RtlHardcoded"
tools:text="12:38"
tools:visibility="visible" />
<TextView
android:id="@+id/detail_position_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:layout_marginLeft="12dp"
android:layout_marginTop="8dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp"
android:alpha=".6"
android:background="?colorPrimary"
android:gravity="center"
android:paddingLeft="6dp"
android:paddingTop="2dp"
android:paddingRight="6dp"
android:paddingBottom="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone"
tools:text="12:38"
tools:visibility="visible" />
</FrameLayout>
</android.support.design.widget.CollapsingToolbarLayout>
<org.schabi.newpipe.views.AnimatedProgressBar
android:id="@+id/position_view"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginTop="-2dp"
android:background="@android:color/transparent"
android:progressDrawable="?attr/progress_horizontal_drawable"
android:visibility="invisible"
app:layout_scrollFlags="scroll"
tools:max="100"
tools:progress="40"
tools:visibility="visible" />
<!-- CONTENT -->
<RelativeLayout
android:id="@+id/detail_content_root_layout"
@ -133,11 +174,10 @@
android:layout_marginRight="20dp"
android:ellipsize="end"
android:maxLines="1"
android:paddingBottom="8dp"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_title_text_size"
tools:ignore="RtlHardcoded"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />
<ImageView
@ -147,7 +187,7 @@
android:layout_gravity="center_vertical|right"
android:layout_marginLeft="5dp"
android:src="@drawable/arrow_down"
tools:ignore="ContentDescription,RtlHardcoded" />
tools:ignore="ContentDescription" />
</FrameLayout>
@ -179,9 +219,9 @@
android:id="@+id/detail_content_root_hiding"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="10dp"
android:layout_below="@+id/detail_title_root_layout"
android:orientation="vertical"
android:paddingBottom="10dp"
android:visibility="gone"
tools:visibility="visible">
@ -191,8 +231,8 @@
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="6dp"
android:layout_marginRight="12dp"
android:baselineAligned="false"
android:orientation="horizontal">
@ -201,8 +241,8 @@
android:id="@+id/detail_uploader_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/details_panel"
android:layout_toStartOf="@id/details_panel"
android:layout_toLeftOf="@id/details_panel"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
@ -213,8 +253,7 @@
android:layout_width="@dimen/video_item_detail_uploader_image_size"
android:layout_height="@dimen/video_item_detail_uploader_image_size"
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded" />
android:src="@drawable/buddy" />
<TextView
android:id="@+id/detail_uploader_text_view"
@ -261,8 +300,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_views_text_size"
@ -354,8 +393,8 @@
android:drawableTop="?attr/ic_playlist_add"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:text="@string/controls_add_to_playlist_title"
android:textSize="12sp" />
@ -371,8 +410,8 @@
android:drawableTop="?attr/audio"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:text="@string/controls_background_title"
android:textSize="12sp" />
@ -388,8 +427,8 @@
android:drawableTop="?attr/popup"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:text="@string/controls_popup_title"
android:textSize="12sp" />
@ -405,8 +444,8 @@
android:drawableTop="?attr/download"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:text="@string/download"
android:textSize="12sp" />
@ -444,10 +483,10 @@
android:id="@+id/detail_description_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="3dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textIsSelectable="true"
android:textSize="@dimen/video_item_detail_description_text_size"
@ -490,7 +529,7 @@
</android.support.design.widget.CoordinatorLayout>
<FrameLayout
android:id="@+id/relatedStreamsLayout"
android:id="@+id/relatedStreamsLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginTop="10dp"

View File

@ -1,489 +1,529 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/video_item_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/video_item_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true">
<android.support.design.widget.CoordinatorLayout
android:id="@+id/detail_main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.CoordinatorLayout
android:id="@+id/detail_main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbarlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:elevation="0dp"
app:layout_behavior="android.support.design.widget.FlingBehavior">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbarlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:fitsSystemWindows="true"
app:elevation="0dp"
app:layout_behavior="android.support.design.widget.FlingBehavior">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll">
<!-- THUMBNAIL -->
<FrameLayout
android:id="@+id/detail_thumbnail_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:layout_collapseMode="parallax">
<!-- THUMBNAIL -->
<FrameLayout
android:id="@+id/detail_thumbnail_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:layout_collapseMode="parallax">
<ImageView
android:id="@+id/detail_thumbnail_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="fitCenter"
tools:ignore="RtlHardcoded"
tools:layout_height="200dp"
tools:src="@drawable/dummy_thumbnail" />
<ImageView
android:id="@+id/detail_thumbnail_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="fitCenter"
tools:ignore="RtlHardcoded"
tools:layout_height="200dp"
tools:src="@drawable/dummy_thumbnail" />
<ImageView
android:id="@+id/detail_thumbnail_play_button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:src="@drawable/new_play_arrow"
android:visibility="invisible"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<ImageView
android:id="@+id/detail_thumbnail_play_button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:src="@drawable/new_play_arrow"
android:visibility="invisible"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<TextView
android:id="@+id/touch_append_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#64000000"
android:paddingBottom="10dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingTop="10dp"
android:text="@string/hold_to_append"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
<TextView
android:id="@+id/touch_append_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#64000000"
android:paddingLeft="30dp"
android:paddingTop="10dp"
android:paddingRight="30dp"
android:paddingBottom="10dp"
android:text="@string/hold_to_append"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
<TextView
android:id="@+id/detail_duration_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp"
android:alpha=".6"
android:background="#23000000"
android:gravity="center"
android:paddingBottom="2dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="12:38"
tools:visibility="visible" />
</FrameLayout>
<TextView
android:id="@+id/detail_duration_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginLeft="12dp"
android:layout_marginTop="8dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp"
android:alpha=".6"
android:background="#23000000"
android:gravity="center"
android:paddingLeft="6dp"
android:paddingTop="2dp"
android:paddingRight="6dp"
android:paddingBottom="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="12:38"
tools:visibility="visible" />
</android.support.design.widget.CollapsingToolbarLayout>
<TextView
android:id="@+id/detail_position_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:layout_marginLeft="12dp"
android:layout_marginTop="8dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp"
android:alpha=".6"
android:background="?colorPrimary"
android:gravity="center"
android:paddingLeft="6dp"
android:paddingTop="2dp"
android:paddingRight="6dp"
android:paddingBottom="2dp"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="12:38"
tools:visibility="visible" />
<!-- CONTENT -->
<RelativeLayout
android:id="@+id/detail_content_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
app:layout_scrollFlags="scroll">
</FrameLayout>
<!-- TITLE -->
<FrameLayout
android:id="@+id/detail_title_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingLeft="12dp"
android:paddingRight="12dp">
</android.support.design.widget.CollapsingToolbarLayout>
<TextView
android:id="@+id/detail_video_title_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="20dp"
android:ellipsize="end"
android:maxLines="1"
android:paddingBottom="8dp"
android:paddingTop="12dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_title_text_size"
tools:ignore="RtlHardcoded"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />
<org.schabi.newpipe.views.AnimatedProgressBar
android:id="@+id/position_view"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginTop="-2dp"
android:progressDrawable="?attr/progress_horizontal_drawable"
android:visibility="invisible"
app:layout_scrollFlags="scroll"
tools:max="100"
tools:progress="40"
tools:visibility="visible" />
<ImageView
android:id="@+id/detail_toggle_description_view"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_gravity="center_vertical|right"
android:layout_marginLeft="5dp"
android:src="@drawable/arrow_down"
tools:ignore="ContentDescription,RtlHardcoded" />
<!-- CONTENT -->
<RelativeLayout
android:id="@+id/detail_content_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
app:layout_scrollFlags="scroll">
</FrameLayout>
<!-- TITLE -->
<FrameLayout
android:id="@+id/detail_title_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingLeft="12dp"
android:paddingRight="12dp">
<!-- LOADING INDICATOR-->
<ProgressBar
android:id="@+id/loading_progress_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detail_title_root_layout"
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/detail_video_title_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="20dp"
android:ellipsize="end"
android:maxLines="1"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_title_text_size"
tools:ignore="RtlHardcoded"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />
<!--ERROR PANEL-->
<include
android:id="@+id/error_panel"
layout="@layout/error_retry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detail_title_root_layout"
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/detail_toggle_description_view"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_gravity="center_vertical|right"
android:layout_marginLeft="5dp"
android:src="@drawable/arrow_down"
tools:ignore="ContentDescription,RtlHardcoded" />
<!--HIDING ROOT-->
<LinearLayout
android:id="@+id/detail_content_root_hiding"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="10dp"
android:layout_below="@+id/detail_title_root_layout"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
</FrameLayout>
<!--DETAIL-->
<RelativeLayout
android:id="@+id/detail_root"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="6dp"
android:baselineAligned="false"
android:orientation="horizontal">
<!-- LOADING INDICATOR-->
<ProgressBar
android:id="@+id/loading_progress_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detail_title_root_layout"
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible" />
<!--UPLOADER-->
<LinearLayout
android:id="@+id/detail_uploader_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/details_panel"
android:layout_toStartOf="@id/details_panel"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="6dp">
<!--ERROR PANEL-->
<include
android:id="@+id/error_panel"
layout="@layout/error_retry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detail_title_root_layout"
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
android:visibility="gone"
tools:visibility="visible" />
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/detail_uploader_thumbnail_view"
android:layout_width="@dimen/video_item_detail_uploader_image_size"
android:layout_height="@dimen/video_item_detail_uploader_image_size"
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded" />
<!--HIDING ROOT-->
<LinearLayout
android:id="@+id/detail_content_root_hiding"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/detail_title_root_layout"
android:orientation="vertical"
android:paddingBottom="10dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/detail_uploader_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_uploader_text_size"
android:textStyle="bold"
tools:ignore="RtlHardcoded"
tools:text="Uploader" />
<!--DETAIL-->
<RelativeLayout
android:id="@+id/detail_root"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginLeft="12dp"
android:layout_marginTop="6dp"
android:layout_marginRight="12dp"
android:baselineAligned="false"
android:orientation="horizontal">
<!--<Button
android:id="@+id/detail_uploader_subscribe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="12dp"
android:text="@string/rss_button_title"
android:textSize="12sp"
android:theme="@style/RedButton"
android:drawableLeft="@drawable/ic_rss_feed_white_24dp"
tools:ignore="RtlHardcoded"
android:visibility="gone"/>-->
</LinearLayout>
<!--UPLOADER-->
<LinearLayout
android:id="@+id/detail_uploader_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toStartOf="@id/details_panel"
android:layout_toLeftOf="@id/details_panel"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="6dp">
<!-- VIEW & THUMBS -->
<RelativeLayout
android:id="@+id/details_panel"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:paddingLeft="6dp"
android:paddingRight="6dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/detail_uploader_thumbnail_view"
android:layout_width="@dimen/video_item_detail_uploader_image_size"
android:layout_height="@dimen/video_item_detail_uploader_image_size"
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/detail_view_count_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_views_text_size"
tools:ignore="RtlHardcoded"
tools:text="2,816,821,505 views" />
<TextView
android:id="@+id/detail_uploader_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_uploader_text_size"
android:textStyle="bold"
tools:ignore="RtlHardcoded"
tools:text="Uploader" />
<ImageView
android:id="@+id/detail_thumbs_up_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:contentDescription="@string/detail_likes_img_view_description"
android:src="?attr/thumbs_up" />
<!--<Button
android:id="@+id/detail_uploader_subscribe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="12dp"
android:text="@string/rss_button_title"
android:textSize="12sp"
android:theme="@style/RedButton"
android:drawableLeft="@drawable/ic_rss_feed_white_24dp"
tools:ignore="RtlHardcoded"
android:visibility="gone"/>-->
</LinearLayout>
<TextView
android:id="@+id/detail_thumbs_up_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_up_img_view"
android:gravity="center_vertical"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="12M" />
<!-- VIEW & THUMBS -->
<RelativeLayout
android:id="@+id/details_panel"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:paddingLeft="6dp"
android:paddingRight="6dp">
<ImageView
android:id="@+id/detail_thumbs_down_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="12dp"
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
android:contentDescription="@string/detail_dislikes_img_view_description"
android:src="?attr/thumbs_down"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/detail_view_count_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_views_text_size"
tools:ignore="RtlHardcoded"
tools:text="2,816,821,505 views" />
<TextView
android:id="@+id/detail_thumbs_down_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
android:gravity="center_vertical"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="10K" />
<ImageView
android:id="@+id/detail_thumbs_up_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:contentDescription="@string/detail_likes_img_view_description"
android:src="?attr/thumbs_up" />
<TextView
android:id="@+id/detail_thumbs_disabled_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="12dp"
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
android:gravity="center_vertical"
android:text="@string/disabled"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_likes_text_size"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
</RelativeLayout>
</RelativeLayout>
<TextView
android:id="@+id/detail_thumbs_up_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_up_img_view"
android:gravity="center_vertical"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="12M" />
<LinearLayout
android:id="@+id/detail_control_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="6dp">
<ImageView
android:id="@+id/detail_thumbs_down_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="12dp"
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
android:contentDescription="@string/detail_dislikes_img_view_description"
android:src="?attr/thumbs_down"
tools:ignore="RtlHardcoded" />
<!-- CONTROLS -->
<TextView
android:id="@+id/detail_controls_playlist_append"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/append_playlist"
android:drawableTop="?attr/ic_playlist_add"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_add_to_playlist_title"
android:textSize="12sp" />
<TextView
android:id="@+id/detail_thumbs_down_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
android:gravity="center_vertical"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="10K" />
<TextView
android:id="@+id/detail_controls_background"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/play_audio"
android:drawableTop="?attr/audio"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_background_title"
android:textSize="12sp" />
<TextView
android:id="@+id/detail_thumbs_disabled_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="12dp"
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
android:gravity="center_vertical"
android:text="@string/disabled"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_likes_text_size"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
</RelativeLayout>
</RelativeLayout>
<TextView
android:id="@+id/detail_controls_popup"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/open_in_popup_mode"
android:drawableTop="?attr/popup"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_popup_title"
android:textSize="12sp" />
<LinearLayout
android:id="@+id/detail_control_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="6dp">
<TextView
android:id="@+id/detail_controls_download"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/controls_download_desc"
android:drawableTop="?attr/download"
android:focusable="true"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/download"
android:textSize="12sp" />
<!-- CONTROLS -->
<TextView
android:id="@+id/detail_controls_playlist_append"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/append_playlist"
android:drawableTop="?attr/ic_playlist_add"
android:focusable="true"
android:gravity="center"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:text="@string/controls_add_to_playlist_title"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/detail_controls_background"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/play_audio"
android:drawableTop="?attr/audio"
android:focusable="true"
android:gravity="center"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:text="@string/controls_background_title"
android:textSize="12sp" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separator_color" />
<TextView
android:id="@+id/detail_controls_popup"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/open_in_popup_mode"
android:drawableTop="?attr/popup"
android:focusable="true"
android:gravity="center"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:text="@string/controls_popup_title"
android:textSize="12sp" />
<!--DESCRIPTIONS-->
<LinearLayout
android:id="@+id/detail_description_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/detail_controls_download"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/controls_download_desc"
android:drawableTop="?attr/download"
android:focusable="true"
android:gravity="center"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:text="@string/download"
android:textSize="12sp" />
<TextView
android:id="@+id/detail_upload_date_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_upload_date_text_size"
android:textStyle="bold"
tools:text="Published on Oct 2, 2009" />
</LinearLayout>
<TextView
android:id="@+id/detail_description_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="3dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textIsSelectable="true"
android:textSize="@dimen/video_item_detail_description_text_size"
tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum." />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separator_color" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separator_color" />
<!--DESCRIPTIONS-->
<LinearLayout
android:id="@+id/detail_description_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
</LinearLayout>
<TextView
android:id="@+id/detail_upload_date_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_upload_date_text_size"
android:textStyle="bold"
tools:text="Published on Oct 2, 2009" />
</LinearLayout>
</RelativeLayout>
<TextView
android:id="@+id/detail_description_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="3dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textIsSelectable="true"
android:textSize="@dimen/video_item_detail_description_text_size"
tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum." />
</android.support.design.widget.AppBarLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separator_color" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:background="@color/transparent_background_color"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp">
<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:background="@color/transparent_background_color"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp">
</android.support.design.widget.TabLayout>
</android.support.design.widget.TabLayout>
</android.support.design.widget.CoordinatorLayout>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>

View File

@ -66,4 +66,16 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Uploader"/>
<org.schabi.newpipe.views.AnimatedProgressBar
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"/>
<org.schabi.newpipe.views.AnimatedProgressBar
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" />
<org.schabi.newpipe.views.AnimatedProgressBar
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

@ -81,4 +81,15 @@
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Uploader" />
<org.schabi.newpipe.views.AnimatedProgressBar
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

@ -83,4 +83,15 @@
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Uploader" />
<org.schabi.newpipe.views.AnimatedProgressBar
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

@ -14,7 +14,7 @@
<dimen name="video_item_search_thumbnail_image_width">142dp</dimen>
<dimen name="video_item_search_thumbnail_image_height">80dp</dimen>
<!-- Calculated: 2*video_item_search_padding + video_item_search_thumbnail_image_height -->
<dimen name="video_item_search_height">104dp</dimen>
<dimen name="video_item_search_height">106dp</dimen>
<!-- Paddings & Margins -->
<dimen name="video_item_search_image_right_margin">10dp</dimen>
<dimen name="video_item_search_duration_vertical_padding">1sp</dimen>

View File

@ -167,7 +167,12 @@
<string name="fragment_whats_new">Что нового</string>
<string name="enable_search_history_title">История поиска</string>
<string name="enable_search_history_summary">Хранить запросы поиска локально</string>
<string name="enable_watch_history_title">История и кэш</string>
<string name="enable_watch_history_title">История просмотров</string>
<string name="enable_playback_resume_title">Продолжать воспроизведение</string>
<string name="enable_playback_resume_summary">Восстанавливать с последней позиции</string>
<string name="enable_playback_state_lists_title">Позиции в списках</string>
<string name="enable_playback_state_lists_summary">Отображать индикаторы позиций просмотра в списках</string>
<string name="settings_category_clear_data_title">Очистить данные</string>
<string name="enable_watch_history_summary">Запоминать воспроизведённые потоки</string>
<string name="resume_on_audio_focus_gain_title">Возобновить при фокусе</string>
<string name="resume_on_audio_focus_gain_summary">Возобновлять воспроизведение после перерывов (например, телефонных звонков)</string>

View File

@ -135,8 +135,13 @@
<string name="show_search_suggestions_summary">Показувати пропозиції під час пошуку</string>
<string name="enable_search_history_title">Історія пошуків</string>
<string name="enable_search_history_summary">Зберігати пошукові запити локально</string>
<string name="enable_watch_history_title">Історія та кеш</string>
<string name="enable_watch_history_summary">Запам\'ятовувати переглянуті відео</string>
<string name="enable_playback_resume_title">Продовживати перегляд</string>
<string name="enable_playback_resume_summary">Відновлювати останню позицію</string>
<string name="enable_playback_state_lists_title">Позиції у списках</string>
<string name="enable_playback_state_lists_summary">Відображати індикатори позицій переглядів у списках</string>
<string name="settings_category_clear_data_title">Очистити дані</string>
<string name="enable_watch_history_summary">Вести облік перегляду відеозаписів</string>
<string name="enable_watch_history_title">Історія переглядів</string>
<string name="resume_on_audio_focus_gain_title">Відновити програвання при поверненні з тла</string>
<string name="resume_on_audio_focus_gain_summary">Продовжувати програвання після переривань (наприклад, телефонних дзвінків)</string>
<string name="show_hold_to_append_title">Показати пораду \"Утримуй, щоб додати\"</string>

View File

@ -44,6 +44,7 @@
<attr name="ic_delete" format="reference"/>
<attr name="ic_settings_update" format="reference"/>
<attr name="progress_horizontal_drawable" format="reference"/>
<!-- Can't refer to colors directly in drawable's xml-->
<attr name="toolbar_shadow_drawable" format="reference"/>
<attr name="selector_drawable" format="reference"/>

View File

@ -15,7 +15,7 @@
<dimen name="video_item_grid_thumbnail_image_width">164dp</dimen>
<dimen name="video_item_grid_thumbnail_image_height">92dp</dimen>
<!-- Calculated: 2*video_item_search_padding + video_item_search_thumbnail_image_height -->
<dimen name="video_item_search_height">94dp</dimen>
<dimen name="video_item_search_height">96dp</dimen>
<!-- Paddings & Margins -->
<dimen name="video_item_search_padding">12dp</dimen>
<dimen name="video_item_search_image_right_margin">6dp</dimen>

View File

@ -150,6 +150,8 @@
<string name="enable_search_history_key" translatable="false">enable_search_history</string>
<string name="enable_watch_history_key" translatable="false">enable_watch_history</string>
<string name="main_page_content_key" translatable="false">main_page_content</string>
<string name="enable_playback_resume_key" translatable="false">enable_playback_resume</string>
<string name="enable_playback_state_lists_key" translatable="false">enable_playback_state_lists</string>
<string name="import_data">import_data</string>
<string name="export_data">export_data</string>

View File

@ -89,7 +89,12 @@
<string name="show_search_suggestions_summary">Show suggestions when searching</string>
<string name="enable_search_history_title">Search history</string>
<string name="enable_search_history_summary">Store search queries locally</string>
<string name="enable_watch_history_title">History &amp; Cache</string>
<string name="enable_watch_history_title">Watch history</string>
<string name="enable_playback_resume_title">Resume playback</string>
<string name="enable_playback_resume_summary">Restore last playback position</string>
<string name="enable_playback_state_lists_title">Positions in lists</string>
<string name="enable_playback_state_lists_summary">Show playback position indicators in lists</string>
<string name="settings_category_clear_data_title">Clear data</string>
<string name="enable_watch_history_summary">Keep track of watched videos</string>
<string name="resume_on_audio_focus_gain_title">Resume on focus gain</string>
<string name="resume_on_audio_focus_gain_summary">Continue playing after interruptions (e.g. phone calls)</string>

View File

@ -66,6 +66,7 @@
<item name="toolbar_shadow_drawable">@drawable/toolbar_shadow_light</item>
<item name="selector_drawable">@drawable/light_selector</item>
<item name="colorControlHighlight">@color/light_ripple_color</item>
<item name="progress_horizontal_drawable">@drawable/progress_youtube_horizontal_light</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
@ -130,6 +131,7 @@
<item name="toolbar_shadow_drawable">@drawable/toolbar_shadow_dark</item>
<item name="selector_drawable">@drawable/dark_selector</item>
<item name="colorControlHighlight">@color/dark_ripple_color</item>
<item name="progress_horizontal_drawable">@drawable/progress_youtube_horizontal_dark</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>

View File

@ -15,18 +15,21 @@
<item name="colorPrimary">@color/light_soundcloud_primary_color</item>
<item name="colorPrimaryDark">@color/light_soundcloud_dark_color</item>
<item name="colorAccent">@color/light_soundcloud_accent_color</item>
<item name="progress_horizontal_drawable">@drawable/progress_soundcloud_horizontal_light</item>
</style>
<style name="DarkTheme.SoundCloud" parent="DarkTheme.Switchable">
<item name="colorPrimary">@color/dark_soundcloud_primary_color</item>
<item name="colorPrimaryDark">@color/dark_soundcloud_dark_color</item>
<item name="colorAccent">@color/dark_soundcloud_accent_color</item>
<item name="progress_horizontal_drawable">@drawable/progress_soundcloud_horizontal_dark</item>
</style>
<style name="BlackTheme.SoundCloud" parent="BlackTheme.Switchable">
<item name="colorPrimary">@color/dark_soundcloud_primary_color</item>
<item name="colorPrimaryDark">@color/dark_soundcloud_dark_color</item>
<item name="colorAccent">@color/dark_soundcloud_accent_color</item>
<item name="progress_horizontal_drawable">@drawable/progress_soundcloud_horizontal_dark</item>
</style>
<!-- Media.ccc -->

View File

@ -1,40 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:key="general_preferences"
android:title="@string/settings_category_history_title">
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="true"
android:key="@string/enable_watch_history_key"
android:summary="@string/enable_watch_history_summary"
android:title="@string/enable_watch_history_title"/>
android:title="@string/enable_watch_history_title"
app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="true"
android:dependency="@string/enable_watch_history_key"
android:key="@string/enable_playback_resume_key"
android:summary="@string/enable_playback_resume_summary"
android:title="@string/enable_playback_resume_title"
app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="true"
android:dependency="@string/enable_playback_resume_key"
android:key="@string/enable_playback_state_lists_key"
android:summary="@string/enable_playback_state_lists_summary"
android:title="@string/enable_playback_state_lists_title"
app:iconSpaceReserved="false" />
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="true"
android:key="@string/enable_search_history_key"
android:summary="@string/enable_search_history_summary"
android:title="@string/enable_search_history_title"/>
android:title="@string/enable_search_history_title"
app:iconSpaceReserved="false" />
<Preference
app:iconSpaceReserved="false"
android:key="@string/metadata_cache_wipe_key"
android:summary="@string/metadata_cache_wipe_summary"
android:title="@string/metadata_cache_wipe_title"/>
<PreferenceCategory
android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_clear_data_title"
app:iconSpaceReserved="false">
<Preference
app:iconSpaceReserved="false"
android:key="@string/clear_views_history_key"
android:title="@string/clear_views_history_title"
android:summary="@string/clear_views_history_summary"/>
<Preference
android:key="@string/metadata_cache_wipe_key"
android:summary="@string/metadata_cache_wipe_summary"
android:title="@string/metadata_cache_wipe_title"
app:iconSpaceReserved="false" />
<Preference
app:iconSpaceReserved="false"
android:key="@string/clear_search_history_key"
android:title="@string/clear_search_history_title"
android:summary="@string/clear_search_history_summary"/>
<Preference
android:key="@string/clear_views_history_key"
android:summary="@string/clear_views_history_summary"
android:title="@string/clear_views_history_title"
app:iconSpaceReserved="false" />
</PreferenceScreen>
<Preference
android:key="@string/clear_search_history_key"
android:summary="@string/clear_search_history_summary"
android:title="@string/clear_search_history_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
</PreferenceScreen>