-Added bulk playlist creation and append.
-Added UI to create playlist from service player activity. -Added state saving to playlist dialogs. -Removed access to history activity on service player activity. -Made StreamEntity serializable.
This commit is contained in:
parent
168ac91ab8
commit
776dbc34f7
12 changed files with 182 additions and 101 deletions
|
|
@ -331,7 +331,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
break;
|
||||
case R.id.detail_controls_playlist_append:
|
||||
if (getFragmentManager() != null && currentInfo != null) {
|
||||
PlaylistAppendDialog.newInstance(currentInfo).show(getFragmentManager(), TAG);
|
||||
PlaylistAppendDialog.fromStreamInfo(currentInfo)
|
||||
.show(getFragmentManager(), TAG);
|
||||
}
|
||||
break;
|
||||
case R.id.detail_uploader_root_layout:
|
||||
|
|
|
|||
|
|
@ -33,34 +33,38 @@ public class LocalPlaylistManager {
|
|||
}
|
||||
|
||||
public Maybe<List<Long>> createPlaylist(final String name, final List<StreamEntity> streams) {
|
||||
// Disallow creation of empty playlists until user is able to select thumbnail
|
||||
// Disallow creation of empty playlists
|
||||
if (streams.isEmpty()) return Maybe.empty();
|
||||
final StreamEntity defaultStream = streams.get(0);
|
||||
final PlaylistEntity newPlaylist = new PlaylistEntity(name, defaultStream.getThumbnailUrl());
|
||||
final PlaylistEntity newPlaylist =
|
||||
new PlaylistEntity(name, defaultStream.getThumbnailUrl());
|
||||
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||
final long playlistId = playlistTable.insert(newPlaylist);
|
||||
|
||||
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
|
||||
for (int index = 0; index < streams.size(); index++) {
|
||||
// Upsert streams and get their ids
|
||||
final long streamId = streamTable.upsert(streams.get(index));
|
||||
joinEntities.add(new PlaylistStreamEntity(playlistId, streamId, index));
|
||||
}
|
||||
|
||||
return playlistStreamTable.insertAll(joinEntities);
|
||||
})).subscribeOn(Schedulers.io());
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() ->
|
||||
upsertStreams(playlistTable.insert(newPlaylist), streams, 0))
|
||||
).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Maybe<Long> appendToPlaylist(final long playlistId, final StreamEntity stream) {
|
||||
final Maybe<Long> streamIdFuture = Maybe.fromCallable(() -> streamTable.upsert(stream));
|
||||
final Maybe<Integer> joinIndexFuture =
|
||||
playlistStreamTable.getMaximumIndexOf(playlistId).firstElement();
|
||||
public Maybe<List<Long>> appendToPlaylist(final long playlistId,
|
||||
final List<StreamEntity> streams) {
|
||||
return playlistStreamTable.getMaximumIndexOf(playlistId)
|
||||
.firstElement()
|
||||
.map(maxJoinIndex -> database.runInTransaction(() ->
|
||||
upsertStreams(playlistId, streams, maxJoinIndex + 1))
|
||||
).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
return Maybe.zip(streamIdFuture, joinIndexFuture, (streamId, currentMaxJoinIndex) ->
|
||||
playlistStreamTable.insert(new PlaylistStreamEntity(playlistId,
|
||||
streamId, currentMaxJoinIndex + 1))
|
||||
).subscribeOn(Schedulers.io());
|
||||
private List<Long> upsertStreams(final long playlistId,
|
||||
final List<StreamEntity> streams,
|
||||
final int indexOffset) {
|
||||
|
||||
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
|
||||
for (int index = 0; index < streams.size(); index++) {
|
||||
// Upsert streams and get their ids
|
||||
final long streamId = streamTable.upsert(streams.get(index));
|
||||
joinEntities.add(new PlaylistStreamEntity(playlistId, streamId,
|
||||
index + indexOffset));
|
||||
}
|
||||
return playlistStreamTable.insertAll(joinEntities);
|
||||
}
|
||||
|
||||
public Completable updateJoin(final long playlistId, final List<Long> streamIds) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.content.Context;
|
|||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -19,34 +18,48 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
|
|||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.info_list.stored.LocalPlaylistInfoItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
public class PlaylistAppendDialog extends DialogFragment {
|
||||
public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
private static final String TAG = PlaylistAppendDialog.class.getCanonicalName();
|
||||
private static final String INFO_KEY = "info_key";
|
||||
|
||||
private StreamInfo streamInfo;
|
||||
|
||||
private View newPlaylistButton;
|
||||
private RecyclerView playlistRecyclerView;
|
||||
private InfoListAdapter playlistAdapter;
|
||||
|
||||
public static PlaylistAppendDialog newInstance(final StreamInfo info) {
|
||||
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
dialog.setInfo(info);
|
||||
dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void setInfo(StreamInfo info) {
|
||||
this.streamInfo = info;
|
||||
public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
for (final StreamInfoItem item : items) {
|
||||
entities.add(new StreamEntity(item));
|
||||
}
|
||||
dialog.setInfo(entities);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
for (final PlayQueueItem item : items) {
|
||||
entities.add(new StreamEntity(item));
|
||||
}
|
||||
dialog.setInfo(entities);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -60,14 +73,9 @@ public class PlaylistAppendDialog extends DialogFragment {
|
|||
playlistAdapter.useMiniItemVariants(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
Serializable serial = savedInstanceState.getSerializable(INFO_KEY);
|
||||
if (serial instanceof StreamInfo) streamInfo = (StreamInfo) serial;
|
||||
}
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
|
|
@ -79,7 +87,7 @@ public class PlaylistAppendDialog extends DialogFragment {
|
|||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
newPlaylistButton = view.findViewById(R.id.newPlaylist);
|
||||
final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
|
||||
playlistRecyclerView = view.findViewById(R.id.playlist_list);
|
||||
playlistRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
playlistRecyclerView.setAdapter(playlistAdapter);
|
||||
|
|
@ -92,12 +100,14 @@ public class PlaylistAppendDialog extends DialogFragment {
|
|||
playlistAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<PlaylistInfoItem>() {
|
||||
@Override
|
||||
public void selected(PlaylistInfoItem selectedItem) {
|
||||
if (!(selectedItem instanceof LocalPlaylistInfoItem)) return;
|
||||
if (!(selectedItem instanceof LocalPlaylistInfoItem) || getStreams() == null)
|
||||
return;
|
||||
|
||||
final long playlistId = ((LocalPlaylistInfoItem) selectedItem).getPlaylistId();
|
||||
final Toast successToast =
|
||||
Toast.makeText(getContext(), "Added", Toast.LENGTH_SHORT);
|
||||
|
||||
playlistManager.appendToPlaylist(playlistId, new StreamEntity(streamInfo))
|
||||
playlistManager.appendToPlaylist(playlistId, getStreams())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> successToast.show());
|
||||
|
||||
|
|
@ -127,20 +137,14 @@ public class PlaylistAppendDialog extends DialogFragment {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable(INFO_KEY, streamInfo);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Helper
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void openCreatePlaylistDialog() {
|
||||
if (streamInfo == null || getFragmentManager() == null) return;
|
||||
if (getStreams() == null || getFragmentManager() == null) return;
|
||||
|
||||
PlaylistCreationDialog.newInstance(streamInfo).show(getFragmentManager(), TAG);
|
||||
PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG);
|
||||
getDialog().dismiss();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,63 +5,35 @@ import android.app.Dialog;
|
|||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
public class PlaylistCreationDialog extends DialogFragment {
|
||||
public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||
private static final String TAG = PlaylistCreationDialog.class.getCanonicalName();
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
private static final String INFO_KEY = "info_key";
|
||||
|
||||
private StreamInfo streamInfo;
|
||||
|
||||
public static PlaylistCreationDialog newInstance(final StreamInfo info) {
|
||||
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
||||
PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||
dialog.setInfo(info);
|
||||
dialog.setInfo(streams);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void setInfo(final StreamInfo info) {
|
||||
this.streamInfo = info;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
// Dialog
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (streamInfo != null) {
|
||||
outState.putSerializable(INFO_KEY, streamInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null && streamInfo == null) {
|
||||
final Object infoCandidate = savedInstanceState.getSerializable(INFO_KEY);
|
||||
if (infoCandidate != null && infoCandidate instanceof StreamInfo) {
|
||||
streamInfo = (StreamInfo) infoCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamInfo == null) return super.onCreateDialog(savedInstanceState);
|
||||
if (getStreams() == null) return super.onCreateDialog(savedInstanceState);
|
||||
|
||||
View dialogView = View.inflate(getContext(),
|
||||
R.layout.dialog_create_playlist, null);
|
||||
|
|
@ -76,13 +48,11 @@ public class PlaylistCreationDialog extends DialogFragment {
|
|||
final String name = nameInput.getText().toString();
|
||||
final LocalPlaylistManager playlistManager =
|
||||
new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext()));
|
||||
final List<StreamEntity> streams =
|
||||
Collections.singletonList(new StreamEntity(streamInfo));
|
||||
final Toast successToast = Toast.makeText(getActivity(),
|
||||
"Playlist " + name + " successfully created",
|
||||
"Playlist successfully created",
|
||||
Toast.LENGTH_SHORT);
|
||||
|
||||
playlistManager.createPlaylist(name, streams)
|
||||
playlistManager.createPlaylist(name, getStreams())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(longs -> successToast.show());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
|
||||
|
||||
private List<StreamEntity> streamEntities;
|
||||
|
||||
private StateSaver.SavedState savedState;
|
||||
|
||||
protected void setInfo(final List<StreamEntity> entities) {
|
||||
this.streamEntities = entities;
|
||||
}
|
||||
|
||||
protected List<StreamEntity> getStreams() {
|
||||
return streamEntities;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
savedState = StateSaver.tryToRestore(savedInstanceState, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
StateSaver.onDestroy(savedState);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// State Saving
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public String generateSuffix() {
|
||||
final int size = streamEntities == null ? 0 : streamEntities.size();
|
||||
return "." + size + ".list";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(Queue<Object> objectsToSave) {
|
||||
objectsToSave.add(streamEntities);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
|
||||
streamEntities = (List<StreamEntity>) savedObjects.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (getActivity() != null) {
|
||||
savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(),
|
||||
savedState, outState, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue