mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-22 19:12:45 +01:00
-Added history record manager as single entry for all database history transactions.
-Merged stream record manager into history record manager. -Removed subject-based history database actions. -Merged normalized history table into watch history fragment. -Modified history fragments to use long click for delete actions. -Refactored DAO operations from search fragment to record manager. -Added index to search history table on search string. -Fix baseplayer round repeat not detected by discontinuity.
This commit is contained in:
parent
f0829f9ef3
commit
388ec3e3d3
@ -26,8 +26,6 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
@ -42,40 +40,21 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
import org.schabi.newpipe.database.history.dao.HistoryDAO;
|
||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
||||
import org.schabi.newpipe.database.history.dao.WatchHistoryDAO;
|
||||
import org.schabi.newpipe.database.history.model.HistoryEntry;
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.database.history.model.WatchHistoryEntry;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||
import org.schabi.newpipe.history.HistoryListener;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private ActionBarDrawerToggle toggle = null;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@ -86,7 +65,6 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -98,7 +76,6 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
setupDrawer();
|
||||
initHistory();
|
||||
}
|
||||
|
||||
private void setupDrawer() {
|
||||
@ -149,8 +126,6 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||
if (!isChangingConfigurations()) {
|
||||
StateSaver.clearStateFiles();
|
||||
}
|
||||
|
||||
disposeHistory();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -357,75 +332,4 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// History
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private WatchHistoryDAO watchHistoryDAO;
|
||||
private SearchHistoryDAO searchHistoryDAO;
|
||||
private PublishSubject<HistoryEntry> historyEntrySubject;
|
||||
private Disposable disposable;
|
||||
|
||||
private void initHistory() {
|
||||
final AppDatabase database = NewPipeDatabase.getInstance();
|
||||
watchHistoryDAO = database.watchHistoryDAO();
|
||||
searchHistoryDAO = database.searchHistoryDAO();
|
||||
historyEntrySubject = PublishSubject.create();
|
||||
disposable = historyEntrySubject
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(getHistoryEntryConsumer());
|
||||
}
|
||||
|
||||
private void disposeHistory() {
|
||||
if (disposable != null) disposable.dispose();
|
||||
watchHistoryDAO = null;
|
||||
searchHistoryDAO = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Consumer<HistoryEntry> getHistoryEntryConsumer() {
|
||||
return new Consumer<HistoryEntry>() {
|
||||
@Override
|
||||
public void accept(HistoryEntry historyEntry) throws Exception {
|
||||
//noinspection unchecked
|
||||
HistoryDAO<HistoryEntry> historyDAO = (HistoryDAO<HistoryEntry>)
|
||||
(historyEntry instanceof SearchHistoryEntry ? searchHistoryDAO : watchHistoryDAO);
|
||||
|
||||
HistoryEntry latestEntry = historyDAO.getLatestEntry();
|
||||
if (historyEntry.hasEqualValues(latestEntry)) {
|
||||
latestEntry.setCreationDate(historyEntry.getCreationDate());
|
||||
historyDAO.update(latestEntry);
|
||||
} else {
|
||||
historyDAO.insert(historyEntry);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void addWatchHistoryEntry(StreamInfo streamInfo) {
|
||||
if (sharedPreferences.getBoolean(getString(R.string.enable_watch_history_key), true)) {
|
||||
WatchHistoryEntry entry = new WatchHistoryEntry(streamInfo);
|
||||
historyEntrySubject.onNext(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoPlayed(StreamInfo streamInfo, @Nullable VideoStream videoStream) {
|
||||
addWatchHistoryEntry(streamInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioPlayed(StreamInfo streamInfo, AudioStream audioStream) {
|
||||
addWatchHistoryEntry(streamInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearch(int serviceId, String query) {
|
||||
// Add search history entry
|
||||
if (sharedPreferences.getBoolean(getString(R.string.enable_search_history_key), true)) {
|
||||
SearchHistoryEntry searchHistoryEntry = new SearchHistoryEntry(new Date(), serviceId, query);
|
||||
historyEntrySubject.onNext(searchHistoryEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,10 @@ import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamHistoryDAO;
|
||||
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||
|
@ -20,6 +20,7 @@ public class Migrations {
|
||||
|
||||
// Not much we can do about this, since room doesn't create tables before migration.
|
||||
// It's either this or blasting the entire database anew.
|
||||
database.execSQL("CREATE INDEX `index_search_history_search` ON `search_history` (`search`)");
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `streams` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, `stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, `thumbnail_url` TEXT)");
|
||||
database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` ON `streams` (`service_id`, `url`)");
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.database.history.dao;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
|
||||
@ -22,6 +23,7 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
||||
@Override
|
||||
@Nullable
|
||||
SearchHistoryEntry getLatestEntry();
|
||||
|
||||
@Query("DELETE FROM " + TABLE_NAME)
|
||||
|
@ -1,14 +1,13 @@
|
||||
package org.schabi.newpipe.database.stream.dao;
|
||||
package org.schabi.newpipe.database.history.dao;
|
||||
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Transaction;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.stream.StreamHistoryEntry;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -18,9 +17,9 @@ import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LA
|
||||
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||
|
||||
@Dao
|
||||
public abstract class StreamHistoryDAO implements BasicDAO<StreamHistoryEntity> {
|
@ -3,10 +3,14 @@ package org.schabi.newpipe.database.history.model;
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.Ignore;
|
||||
import android.arch.persistence.room.Index;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Entity(tableName = SearchHistoryEntry.TABLE_NAME)
|
||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
|
||||
|
||||
@Entity(tableName = SearchHistoryEntry.TABLE_NAME,
|
||||
indices = {@Index(value = SEARCH)})
|
||||
public class SearchHistoryEntry extends HistoryEntry {
|
||||
|
||||
public static final String TABLE_NAME = "search_history";
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.schabi.newpipe.database.stream.model;
|
||||
package org.schabi.newpipe.database.history.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
@ -6,12 +6,14 @@ import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||
|
||||
@Entity(tableName = STREAM_HISTORY_TABLE,
|
||||
primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE},
|
@ -1,9 +1,8 @@
|
||||
package org.schabi.newpipe.database.stream;
|
||||
package org.schabi.newpipe.database.history.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
import java.util.Date;
|
||||
@ -44,4 +43,8 @@ public class StreamHistoryEntry {
|
||||
this.streamId = streamId;
|
||||
this.accessDate = accessDate;
|
||||
}
|
||||
|
||||
public StreamHistoryEntity toStreamHistoryEntity() {
|
||||
return new StreamHistoryEntity(streamId, accessDate);
|
||||
}
|
||||
}
|
@ -2,9 +2,8 @@ package org.schabi.newpipe.database.stream;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
||||
|
||||
|
@ -9,7 +9,7 @@ import android.arch.persistence.room.Transaction;
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -22,7 +22,7 @@ import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_SERVICE_ID;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
|
||||
|
||||
@Dao
|
||||
|
@ -44,6 +44,7 @@ import com.nirhart.parallaxscroll.views.ParallaxScrollView;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
@ -60,6 +61,7 @@ import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.fragments.local.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.history.HistoryListener;
|
||||
import org.schabi.newpipe.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
||||
@ -649,9 +651,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||
public void onActionSelected(int selectedStreamId) {
|
||||
try {
|
||||
NavigationHelper.playWithKore(activity, Uri.parse(info.getUrl().replace("https", "http")));
|
||||
if(activity instanceof HistoryListener) {
|
||||
((HistoryListener) activity).onVideoPlayed(info, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if(DEBUG) Log.i(TAG, "Failed to start kore", e);
|
||||
showInstallKoreDialog(activity);
|
||||
@ -805,10 +804,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||
private void openBackgroundPlayer(final boolean append) {
|
||||
AudioStream audioStream = currentInfo.getAudioStreams().get(ListHelper.getDefaultAudioFormat(activity, currentInfo.getAudioStreams()));
|
||||
|
||||
if (activity instanceof HistoryListener) {
|
||||
((HistoryListener) activity).onAudioPlayed(currentInfo, audioStream);
|
||||
}
|
||||
|
||||
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
|
||||
|
||||
@ -825,10 +820,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||
return;
|
||||
}
|
||||
|
||||
if (activity instanceof HistoryListener) {
|
||||
((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream());
|
||||
}
|
||||
|
||||
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
|
||||
if (append) {
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue);
|
||||
@ -844,10 +835,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||
private void openVideoPlayer() {
|
||||
VideoStream selectedVideoStream = getSelectedVideoStream();
|
||||
|
||||
if (activity instanceof HistoryListener) {
|
||||
((HistoryListener) activity).onVideoPlayed(currentInfo, selectedVideoStream);
|
||||
}
|
||||
|
||||
if (PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
|
||||
NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), selectedVideoStream);
|
||||
} else {
|
||||
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.fragments.list.search;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
@ -30,10 +29,8 @@ import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
@ -44,7 +41,7 @@ import org.schabi.newpipe.extractor.search.SearchEngine;
|
||||
import org.schabi.newpipe.extractor.search.SearchResult;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.history.HistoryListener;
|
||||
import org.schabi.newpipe.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
@ -64,16 +61,11 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import icepick.State;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Notification;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.ObservableSource;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.BiFunction;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.functions.Function;
|
||||
import io.reactivex.functions.Predicate;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
@ -121,7 +113,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private SuggestionListAdapter suggestionListAdapter;
|
||||
private SearchHistoryDAO searchHistoryDAO;
|
||||
private HistoryRecordManager historyRecordManager;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
@ -166,8 +158,8 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
||||
|
||||
searchHistoryDAO = NewPipeDatabase.getInstance().searchHistoryDAO();
|
||||
|
||||
historyRecordManager = new HistoryRecordManager(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -535,36 +527,24 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||
}
|
||||
|
||||
private void showDeleteSuggestionDialog(final SuggestionItem item) {
|
||||
final Disposable onDelete = historyRecordManager.deleteSearchHistory(item.query)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> suggestionPublisher
|
||||
.onNext(searchEditText.getText().toString()),
|
||||
|
||||
throwable -> showSnackBarError(throwable,
|
||||
UserAction.SOMETHING_ELSE, "none",
|
||||
"Deleting item failed", R.string.general_error)
|
||||
);
|
||||
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(item.query)
|
||||
.setMessage(R.string.delete_item_search_history)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
disposables.add(Observable
|
||||
.fromCallable(new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() throws Exception {
|
||||
return searchHistoryDAO.deleteAllWhereQuery(item.query);
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Integer>() {
|
||||
@Override
|
||||
public void accept(Integer howManyDeleted) throws Exception {
|
||||
suggestionPublisher.onNext(searchEditText.getText().toString());
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
showSnackBarError(throwable, UserAction.SOMETHING_ELSE, "none", "Deleting item failed", R.string.general_error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}).show();
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> disposables.add(onDelete))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -589,83 +569,67 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||
final Observable<String> observable = suggestionPublisher
|
||||
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
||||
.startWith(searchQuery != null ? searchQuery : "")
|
||||
.filter(new Predicate<String>() {
|
||||
@Override
|
||||
public boolean test(@io.reactivex.annotations.NonNull String query) throws Exception {
|
||||
return isSuggestionsEnabled;
|
||||
}
|
||||
});
|
||||
.filter(query -> isSuggestionsEnabled);
|
||||
|
||||
suggestionDisposable = observable
|
||||
.switchMap(new Function<String, ObservableSource<Notification<List<SuggestionItem>>>>() {
|
||||
@Override
|
||||
public ObservableSource<Notification<List<SuggestionItem>>> apply(@io.reactivex.annotations.NonNull final String query) throws Exception {
|
||||
final Flowable<List<SearchHistoryEntry>> flowable = query.length() > 0
|
||||
? searchHistoryDAO.getSimilarEntries(query, 3)
|
||||
: searchHistoryDAO.getUniqueEntries(25);
|
||||
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
||||
.map(new Function<List<SearchHistoryEntry>, List<SuggestionItem>>() {
|
||||
@Override
|
||||
public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<SearchHistoryEntry> searchHistoryEntries) throws Exception {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (SearchHistoryEntry entry : searchHistoryEntries)
|
||||
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||
return result;
|
||||
}
|
||||
});
|
||||
.switchMap(query -> {
|
||||
final Flowable<List<SearchHistoryEntry>> flowable = historyRecordManager
|
||||
.getRelatedSearches(query, 3, 25);
|
||||
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
||||
.map(searchHistoryEntries -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (SearchHistoryEntry entry : searchHistoryEntries)
|
||||
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||
return result;
|
||||
});
|
||||
|
||||
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
|
||||
// Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION
|
||||
return local.materialize();
|
||||
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
|
||||
// Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION
|
||||
return local.materialize();
|
||||
}
|
||||
|
||||
final Observable<List<SuggestionItem>> network = ExtractorHelper
|
||||
.suggestionsFor(serviceId, query, contentCountry)
|
||||
.toObservable()
|
||||
.map(strings -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (String entry : strings) {
|
||||
result.add(new SuggestionItem(false, entry));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
return Observable.zip(local, network, (localResult, networkResult) -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
if (localResult.size() > 0) result.addAll(localResult);
|
||||
|
||||
// Remove duplicates
|
||||
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
||||
while (iterator.hasNext() && localResult.size() > 0) {
|
||||
final SuggestionItem next = iterator.next();
|
||||
for (SuggestionItem item : localResult) {
|
||||
if (item.query.equals(next.query)) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Observable<List<SuggestionItem>> network = ExtractorHelper.suggestionsFor(serviceId, query, contentCountry).toObservable()
|
||||
.map(new Function<List<String>, List<SuggestionItem>>() {
|
||||
@Override
|
||||
public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<String> strings) throws Exception {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (String entry : strings) result.add(new SuggestionItem(false, entry));
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
return Observable.zip(local, network, new BiFunction<List<SuggestionItem>, List<SuggestionItem>, List<SuggestionItem>>() {
|
||||
@Override
|
||||
public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<SuggestionItem> localResult, @io.reactivex.annotations.NonNull List<SuggestionItem> networkResult) throws Exception {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
if (localResult.size() > 0) result.addAll(localResult);
|
||||
|
||||
// Remove duplicates
|
||||
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
||||
while (iterator.hasNext() && localResult.size() > 0) {
|
||||
final SuggestionItem next = iterator.next();
|
||||
for (SuggestionItem item : localResult) {
|
||||
if (item.query.equals(next.query)) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (networkResult.size() > 0) result.addAll(networkResult);
|
||||
return result;
|
||||
}
|
||||
}).materialize();
|
||||
}
|
||||
if (networkResult.size() > 0) result.addAll(networkResult);
|
||||
return result;
|
||||
}).materialize();
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Notification<List<SuggestionItem>>>() {
|
||||
@Override
|
||||
public void accept(@io.reactivex.annotations.NonNull Notification<List<SuggestionItem>> listNotification) throws Exception {
|
||||
if (listNotification.isOnNext()) {
|
||||
handleSuggestions(listNotification.getValue());
|
||||
} else if (listNotification.isOnError()) {
|
||||
Throwable error = listNotification.getError();
|
||||
if (!ExtractorHelper.hasAssignableCauseThrowable(error,
|
||||
IOException.class, SocketException.class, InterruptedException.class, InterruptedIOException.class)) {
|
||||
onSuggestionError(error);
|
||||
}
|
||||
.subscribe(listNotification -> {
|
||||
if (listNotification.isOnNext()) {
|
||||
handleSuggestions(listNotification.getValue());
|
||||
} else if (listNotification.isOnError()) {
|
||||
Throwable error = listNotification.getError();
|
||||
if (!ExtractorHelper.hasAssignableCauseThrowable(error,
|
||||
IOException.class, SocketException.class,
|
||||
InterruptedException.class, InterruptedIOException.class)) {
|
||||
onSuggestionError(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -718,11 +682,14 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||
hideSuggestionsPanel();
|
||||
hideKeyboardSearch();
|
||||
|
||||
if (activity instanceof HistoryListener) {
|
||||
((HistoryListener) activity).onSearch(serviceId, query);
|
||||
suggestionPublisher.onNext(query);
|
||||
}
|
||||
|
||||
historyRecordManager.onSearched(serviceId, query)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
ignored -> {},
|
||||
error -> showSnackBarError(error, UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId), query, 0)
|
||||
);
|
||||
suggestionPublisher.onNext(query);
|
||||
startLoading(false);
|
||||
}
|
||||
|
||||
|
@ -13,12 +13,12 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
||||
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
||||
@ -49,7 +49,7 @@ public abstract class StatisticsPlaylistFragment
|
||||
|
||||
/* Used for independent events */
|
||||
private Subscription databaseSubscription;
|
||||
private StreamRecordManager recordManager;
|
||||
private HistoryRecordManager recordManager;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Abstracts
|
||||
@ -68,7 +68,7 @@ public abstract class StatisticsPlaylistFragment
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
recordManager = new StreamRecordManager(NewPipeDatabase.getInstance(context));
|
||||
recordManager = new HistoryRecordManager(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -205,7 +205,7 @@ public abstract class StatisticsPlaylistFragment
|
||||
super.startLoading(forceLoad);
|
||||
resetFragment();
|
||||
|
||||
recordManager.getStatistics()
|
||||
recordManager.getStreamStatistics()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHistoryObserver());
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamHistoryDAO;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class StreamRecordManager {
|
||||
|
||||
private final AppDatabase database;
|
||||
private final StreamDAO streamTable;
|
||||
private final StreamHistoryDAO historyTable;
|
||||
|
||||
public StreamRecordManager(final AppDatabase db) {
|
||||
database = db;
|
||||
streamTable = db.streamDAO();
|
||||
historyTable = db.streamHistoryDAO();
|
||||
}
|
||||
|
||||
public Single<Long> onViewed(final StreamInfo info) {
|
||||
return Single.fromCallable(() -> database.runInTransaction(() -> {
|
||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||
return historyTable.insert(new StreamHistoryEntity(streamId, new Date()));
|
||||
})).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public int removeHistory(final long streamId) {
|
||||
return historyTable.deleteStreamHistory(streamId);
|
||||
}
|
||||
|
||||
public Flowable<List<StreamStatisticsEntry>> getStatistics() {
|
||||
return historyTable.getStatistics();
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
@ -50,8 +51,10 @@ public class HistoryActivity extends AppCompatActivity {
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.title_activity_history);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.title_activity_history);
|
||||
}
|
||||
// Create the adapter that will return a fragment for each of the three
|
||||
// primary sections of the activity.
|
||||
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
|
||||
@ -66,17 +69,11 @@ public class HistoryActivity extends AppCompatActivity {
|
||||
final FloatingActionButton fab = findViewById(R.id.fab);
|
||||
RxView.clicks(fab)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Object>() {
|
||||
@Override
|
||||
public void accept(Object o) {
|
||||
int currentItem = mViewPager.getCurrentItem();
|
||||
HistoryFragment fragment = (HistoryFragment) mSectionsPagerAdapter.instantiateItem(mViewPager, currentItem);
|
||||
if(fragment != null) {
|
||||
fragment.onHistoryCleared();
|
||||
} else {
|
||||
Log.w(TAG, "Couldn't find current fragment");
|
||||
}
|
||||
}
|
||||
.subscribe(ignored -> {
|
||||
int currentItem = mViewPager.getCurrentItem();
|
||||
HistoryFragment fragment = (HistoryFragment) mSectionsPagerAdapter
|
||||
.instantiateItem(mViewPager, currentItem);
|
||||
fragment.onHistoryCleared();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,8 @@ import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.database.history.model.HistoryEntry;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
@ -19,7 +18,7 @@ import java.util.Date;
|
||||
* @param <E> the type of the entries
|
||||
* @param <VH> the type of the view holder
|
||||
*/
|
||||
public abstract class HistoryEntryAdapter<E extends HistoryEntry, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
|
||||
public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
|
||||
|
||||
private final ArrayList<E> mEntries;
|
||||
private final DateFormat mDateFormat;
|
||||
@ -29,9 +28,8 @@ public abstract class HistoryEntryAdapter<E extends HistoryEntry, VH extends Rec
|
||||
public HistoryEntryAdapter(Context context) {
|
||||
super();
|
||||
mEntries = new ArrayList<>();
|
||||
mDateFormat = android.text.format.DateFormat.getDateFormat(context.getApplicationContext());
|
||||
|
||||
setHasStableIds(true);
|
||||
mDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM,
|
||||
Localization.getPreferredLocale(context));
|
||||
}
|
||||
|
||||
public void setEntries(@NonNull Collection<E> historyEntries) {
|
||||
@ -53,11 +51,6 @@ public abstract class HistoryEntryAdapter<E extends HistoryEntry, VH extends Rec
|
||||
return mDateFormat.format(date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return mEntries.get(position).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mEntries.size();
|
||||
@ -66,15 +59,20 @@ public abstract class HistoryEntryAdapter<E extends HistoryEntry, VH extends Rec
|
||||
@Override
|
||||
public void onBindViewHolder(VH holder, int position) {
|
||||
final E entry = mEntries.get(position);
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final OnHistoryItemClickListener<E> historyItemClickListener = onHistoryItemClickListener;
|
||||
if(historyItemClickListener != null) {
|
||||
historyItemClickListener.onHistoryItemClick(entry);
|
||||
}
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if(onHistoryItemClickListener != null) {
|
||||
onHistoryItemClickListener.onHistoryItemClick(entry);
|
||||
}
|
||||
});
|
||||
|
||||
holder.itemView.setOnLongClickListener(view -> {
|
||||
if (onHistoryItemClickListener != null) {
|
||||
onHistoryItemClickListener.onHistoryItemLongClick(entry);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
onBindViewHolder(holder, entry, position);
|
||||
}
|
||||
|
||||
@ -94,13 +92,8 @@ public abstract class HistoryEntryAdapter<E extends HistoryEntry, VH extends Rec
|
||||
return mEntries.isEmpty();
|
||||
}
|
||||
|
||||
public E removeItemAt(int position) {
|
||||
E entry = mEntries.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public interface OnHistoryItemClickListener<E extends HistoryEntry> {
|
||||
void onHistoryItemClick(E historyItem);
|
||||
public interface OnHistoryItemClickListener<E> {
|
||||
void onHistoryItemClick(E item);
|
||||
void onHistoryItemLongClick(E item);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.history;
|
||||
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.preference.PreferenceManager;
|
||||
@ -12,34 +11,31 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.history.dao.HistoryDAO;
|
||||
import org.schabi.newpipe.database.history.model.HistoryEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.State;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragment
|
||||
public abstract class HistoryFragment<E> extends BaseFragment
|
||||
implements HistoryEntryAdapter.OnHistoryItemClickListener<E> {
|
||||
|
||||
private SharedPreferences mSharedPreferences;
|
||||
@ -54,12 +50,11 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||
Parcelable mRecyclerViewState;
|
||||
private RecyclerView mRecyclerView;
|
||||
private HistoryEntryAdapter<E, ? extends RecyclerView.ViewHolder> mHistoryAdapter;
|
||||
private ItemTouchHelper.SimpleCallback mHistoryItemSwipeCallback;
|
||||
// private int allowedSwipeToDeleteDirections = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
|
||||
|
||||
private HistoryDAO<E> mHistoryDataSource;
|
||||
private PublishSubject<Collection<E>> mHistoryEntryDeleteSubject;
|
||||
private PublishSubject<Collection<E>> mHistoryEntryInsertSubject;
|
||||
private Subscription historySubscription;
|
||||
|
||||
protected HistoryRecordManager historyRecordManager;
|
||||
protected CompositeDisposable disposables;
|
||||
|
||||
@StringRes
|
||||
abstract int getEnabledConfigKey();
|
||||
@ -77,88 +72,47 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||
// Register history enabled listener
|
||||
mSharedPreferences.registerOnSharedPreferenceChangeListener(mHistoryIsEnabledChangeListener);
|
||||
|
||||
mHistoryDataSource = createHistoryDAO();
|
||||
|
||||
mHistoryEntryDeleteSubject = PublishSubject.create();
|
||||
mHistoryEntryDeleteSubject
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(new Consumer<Collection<E>>() {
|
||||
@Override
|
||||
public void accept(Collection<E> historyEntries) throws Exception {
|
||||
mHistoryDataSource.delete(historyEntries);
|
||||
}
|
||||
});
|
||||
|
||||
mHistoryEntryInsertSubject = PublishSubject.create();
|
||||
mHistoryEntryInsertSubject
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(new Consumer<Collection<E>>() {
|
||||
@Override
|
||||
public void accept(Collection<E> historyEntries) throws Exception {
|
||||
mHistoryDataSource.insertAll(historyEntries);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected void historyItemSwipeCallback(int swipeDirection) {
|
||||
mHistoryItemSwipeCallback = new ItemTouchHelper.SimpleCallback(0, swipeDirection) {
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
|
||||
if (mHistoryAdapter != null) {
|
||||
final E historyEntry = mHistoryAdapter.removeItemAt(viewHolder.getAdapterPosition());
|
||||
mHistoryEntryDeleteSubject.onNext(Collections.singletonList(historyEntry));
|
||||
|
||||
View view = getActivity().findViewById(R.id.main_content);
|
||||
if (view == null) view = mRecyclerView.getRootView();
|
||||
|
||||
Snackbar.make(view, R.string.item_deleted, 5 * 1000)
|
||||
.setActionTextColor(Color.WHITE)
|
||||
.setAction(R.string.undo, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mHistoryEntryInsertSubject.onNext(Collections.singletonList(historyEntry));
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
historyRecordManager = new HistoryRecordManager(getContext());
|
||||
disposables = new CompositeDisposable();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected abstract HistoryEntryAdapter<E, ? extends RecyclerView.ViewHolder> createAdapter();
|
||||
|
||||
protected abstract Single<List<Long>> insert(final Collection<E> entries);
|
||||
|
||||
protected abstract Single<Integer> delete(final Collection<E> entries);
|
||||
|
||||
@NonNull
|
||||
protected abstract Flowable<List<E>> getAll();
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mHistoryDataSource.getAll()
|
||||
.toObservable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHistoryListConsumer());
|
||||
boolean newEnabled = isHistoryEnabled();
|
||||
|
||||
getAll().observeOn(AndroidSchedulers.mainThread()).subscribe(getHistorySubscriber());
|
||||
|
||||
final boolean newEnabled = isHistoryEnabled();
|
||||
if (newEnabled != mHistoryIsEnabled) {
|
||||
onHistoryIsEnabledChanged(newEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Observer<List<E>> getHistoryListConsumer() {
|
||||
return new Observer<List<E>>() {
|
||||
private Subscriber<List<E>> getHistorySubscriber() {
|
||||
return new Subscriber<List<E>>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
public void onSubscribe(Subscription s) {
|
||||
if (historySubscription != null) historySubscription.cancel();
|
||||
|
||||
historySubscription = s;
|
||||
historySubscription.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull List<E> historyEntries) {
|
||||
if (!historyEntries.isEmpty()) {
|
||||
mHistoryAdapter.setEntries(historyEntries);
|
||||
public void onNext(List<E> entries) {
|
||||
if (!entries.isEmpty()) {
|
||||
mHistoryAdapter.setEntries(entries);
|
||||
animateView(mEmptyHistoryView, false, 200);
|
||||
|
||||
if (mRecyclerViewState != null) {
|
||||
@ -169,11 +123,13 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||
mHistoryAdapter.clear();
|
||||
showEmptyHistory();
|
||||
}
|
||||
|
||||
if (historySubscription != null) historySubscription.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
// TODO: error handling like in (see e.g. subscription fragment)
|
||||
public void onError(Throwable t) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -192,30 +148,33 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||
*/
|
||||
@MainThread
|
||||
public void onHistoryCleared() {
|
||||
final Parcelable stateBeforeClear = mRecyclerView.getLayoutManager().onSaveInstanceState();
|
||||
final Collection<E> itemsToDelete = new ArrayList<>(mHistoryAdapter.getItems());
|
||||
mHistoryEntryDeleteSubject.onNext(itemsToDelete);
|
||||
if (getContext() == null) return;
|
||||
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.delete_all)
|
||||
.setMessage(R.string.delete_all_history_prompt)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete, (dialog, i) -> clearHistory())
|
||||
.show();
|
||||
}
|
||||
|
||||
protected void makeSnackbar(@StringRes final int text) {
|
||||
if (getActivity() == null) return;
|
||||
|
||||
View view = getActivity().findViewById(R.id.main_content);
|
||||
if (view == null) view = mRecyclerView.getRootView();
|
||||
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
if (!itemsToDelete.isEmpty()) {
|
||||
Snackbar.make(view, R.string.history_cleared, 5 * 1000)
|
||||
.setActionTextColor(Color.WHITE)
|
||||
.setAction(R.string.undo, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mRecyclerViewState = stateBeforeClear;
|
||||
mHistoryEntryInsertSubject.onNext(itemsToDelete);
|
||||
}
|
||||
}).show();
|
||||
} else {
|
||||
Snackbar.make(view, R.string.history_cleared, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
private void clearHistory() {
|
||||
final Collection<E> itemsToDelete = new ArrayList<>(mHistoryAdapter.getItems());
|
||||
disposables.add(delete(itemsToDelete).observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe());
|
||||
|
||||
makeSnackbar(R.string.history_cleared);
|
||||
mHistoryAdapter.clear();
|
||||
showEmptyHistory();
|
||||
|
||||
}
|
||||
|
||||
private void showEmptyHistory() {
|
||||
@ -227,18 +186,18 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||
@Nullable
|
||||
@CallSuper
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_history, container, false);
|
||||
mRecyclerView = rootView.findViewById(R.id.history_view);
|
||||
|
||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
|
||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(),
|
||||
LinearLayoutManager.VERTICAL, false);
|
||||
mRecyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
mHistoryAdapter = createAdapter();
|
||||
mHistoryAdapter.setOnHistoryItemClickListener(this);
|
||||
mRecyclerView.setAdapter(mHistoryAdapter);
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mHistoryItemSwipeCallback);
|
||||
itemTouchHelper.attachToRecyclerView(mRecyclerView);
|
||||
mDisabledView = rootView.findViewById(R.id.history_disabled_view);
|
||||
mEmptyHistoryView = rootView.findViewById(R.id.history_empty);
|
||||
|
||||
@ -260,7 +219,7 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||
mSharedPreferences = null;
|
||||
mHistoryIsEnabledChangeListener = null;
|
||||
mHistoryIsEnabledKey = null;
|
||||
mHistoryDataSource = null;
|
||||
if (disposables != null) disposables.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -290,15 +249,8 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new history DAO
|
||||
*
|
||||
* @return the history DAO
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract HistoryDAO<E> createHistoryDAO();
|
||||
|
||||
private class HistoryIsEnabledChangeListener implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private class HistoryIsEnabledChangeListener
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (key.equals(mHistoryIsEnabledKey)) {
|
||||
|
@ -0,0 +1,147 @@
|
||||
package org.schabi.newpipe.history;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class HistoryRecordManager {
|
||||
|
||||
private final AppDatabase database;
|
||||
private final StreamDAO streamTable;
|
||||
private final StreamHistoryDAO streamHistoryTable;
|
||||
private final SearchHistoryDAO searchHistoryTable;
|
||||
private final SharedPreferences sharedPreferences;
|
||||
private final String searchHistoryKey;
|
||||
private final String streamHistoryKey;
|
||||
|
||||
public HistoryRecordManager(final Context context) {
|
||||
database = NewPipeDatabase.getInstance(context);
|
||||
streamTable = database.streamDAO();
|
||||
streamHistoryTable = database.streamHistoryDAO();
|
||||
searchHistoryTable = database.searchHistoryDAO();
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
searchHistoryKey = context.getString(R.string.enable_search_history_key);
|
||||
streamHistoryKey = context.getString(R.string.enable_watch_history_key);
|
||||
}
|
||||
|
||||
public Maybe<Long> onViewed(final StreamInfo info) {
|
||||
if (!isStreamHistoryEnabled()) return Maybe.empty();
|
||||
|
||||
final Date currentTime = new Date();
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||
return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime));
|
||||
})).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<Integer> deleteStreamHistory(final long streamId) {
|
||||
return Single.fromCallable(() -> streamHistoryTable.deleteStreamHistory(streamId))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Flowable<List<StreamHistoryEntry>> getStreamHistory() {
|
||||
return streamHistoryTable.getHistory().subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Flowable<List<StreamStatisticsEntry>> getStreamStatistics() {
|
||||
return streamHistoryTable.getStatistics().subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<List<Long>> insertStreamHistory(final Collection<StreamHistoryEntry> entries) {
|
||||
List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
||||
for (final StreamHistoryEntry entry : entries) {
|
||||
entities.add(entry.toStreamHistoryEntity());
|
||||
}
|
||||
return Single.fromCallable(() -> streamHistoryTable.insertAll(entities))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<Integer> deleteStreamHistory(final Collection<StreamHistoryEntry> entries) {
|
||||
List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
||||
for (final StreamHistoryEntry entry : entries) {
|
||||
entities.add(entry.toStreamHistoryEntity());
|
||||
}
|
||||
return Single.fromCallable(() -> streamHistoryTable.delete(entities))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
private boolean isStreamHistoryEnabled() {
|
||||
return sharedPreferences.getBoolean(streamHistoryKey, false);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Search History
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
public Single<List<Long>> insertSearches(final Collection<SearchHistoryEntry> entries) {
|
||||
return Single.fromCallable(() -> searchHistoryTable.insertAll(entries))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<Integer> deleteSearches(final Collection<SearchHistoryEntry> entries) {
|
||||
return Single.fromCallable(() -> searchHistoryTable.delete(entries))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Flowable<List<SearchHistoryEntry>> getSearchHistory() {
|
||||
return searchHistoryTable.getAll();
|
||||
}
|
||||
|
||||
public Maybe<Long> onSearched(final int serviceId, final String search) {
|
||||
if (!isSearchHistoryEnabled()) return Maybe.empty();
|
||||
|
||||
final Date currentTime = new Date();
|
||||
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
||||
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||
SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
||||
if (latestEntry != null && latestEntry.hasEqualValues(newEntry)) {
|
||||
latestEntry.setCreationDate(currentTime);
|
||||
return (long) searchHistoryTable.update(latestEntry);
|
||||
} else {
|
||||
return searchHistoryTable.insert(newEntry);
|
||||
}
|
||||
})).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<Integer> deleteSearchHistory(final String search) {
|
||||
return Single.fromCallable(() -> searchHistoryTable.deleteAllWhereQuery(search))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Flowable<List<SearchHistoryEntry>> getRelatedSearches(final String query,
|
||||
final int similarQueryLimit,
|
||||
final int uniqueQueryLimit) {
|
||||
return query.length() > 0
|
||||
? searchHistoryTable.getSimilarEntries(query, similarQueryLimit)
|
||||
: searchHistoryTable.getUniqueEntries(uniqueQueryLimit);
|
||||
}
|
||||
|
||||
private boolean isSearchHistoryEnabled() {
|
||||
return sharedPreferences.getBoolean(searchHistoryKey, false);
|
||||
}
|
||||
}
|
@ -5,22 +5,27 @@ import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.history.dao.HistoryDAO;
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
public class SearchHistoryFragment extends HistoryFragment<SearchHistoryEntry> {
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
private static int allowedSwipeToDeleteDirections = ItemTouchHelper.RIGHT;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
public class SearchHistoryFragment extends HistoryFragment<SearchHistoryEntry> {
|
||||
|
||||
@NonNull
|
||||
public static SearchHistoryFragment newInstance() {
|
||||
@ -30,7 +35,6 @@ public class SearchHistoryFragment extends HistoryFragment<SearchHistoryEntry> {
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
historyItemSwipeCallback(allowedSwipeToDeleteDirections);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -39,21 +43,58 @@ public class SearchHistoryFragment extends HistoryFragment<SearchHistoryEntry> {
|
||||
return new SearchHistoryAdapter(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<List<Long>> insert(Collection<SearchHistoryEntry> entries) {
|
||||
return historyRecordManager.insertSearches(entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<Integer> delete(Collection<SearchHistoryEntry> entries) {
|
||||
return historyRecordManager.deleteSearches(entries);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Flowable<List<SearchHistoryEntry>> getAll() {
|
||||
return historyRecordManager.getSearchHistory();
|
||||
}
|
||||
|
||||
@StringRes
|
||||
@Override
|
||||
int getEnabledConfigKey() {
|
||||
return R.string.enable_search_history_key;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected HistoryDAO<SearchHistoryEntry> createHistoryDAO() {
|
||||
return NewPipeDatabase.getInstance().searchHistoryDAO();
|
||||
public void onHistoryItemClick(final SearchHistoryEntry historyItem) {
|
||||
NavigationHelper.openSearch(getContext(), historyItem.getServiceId(),
|
||||
historyItem.getSearch());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistoryItemClick(SearchHistoryEntry historyItem) {
|
||||
NavigationHelper.openSearch(getContext(), historyItem.getServiceId(), historyItem.getSearch());
|
||||
public void onHistoryItemLongClick(final SearchHistoryEntry item) {
|
||||
if (activity == null) return;
|
||||
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(item.getSearch())
|
||||
.setMessage(R.string.delete_item_search_history)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete_one, (dialog, i) -> {
|
||||
final Single<Integer> onDelete = historyRecordManager
|
||||
.deleteSearches(Collections.singleton(item))
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
disposables.add(onDelete.subscribe());
|
||||
makeSnackbar(R.string.item_deleted);
|
||||
})
|
||||
.setNegativeButton(R.string.delete_all, (dialog, i) -> {
|
||||
final Single<Integer> onDeleteAll = historyRecordManager
|
||||
.deleteSearchHistory(item.getSearch())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
disposables.add(onDeleteAll.subscribe());
|
||||
makeSnackbar(R.string.item_deleted);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
@ -6,8 +6,8 @@ import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -16,18 +16,22 @@ import android.widget.TextView;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.history.dao.HistoryDAO;
|
||||
import org.schabi.newpipe.database.history.model.WatchHistoryEntry;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
||||
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class WatchedHistoryFragment extends HistoryFragment<WatchHistoryEntry> {
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
private static int allowedSwipeToDeleteDirections = ItemTouchHelper.LEFT;
|
||||
|
||||
public class WatchedHistoryFragment extends HistoryFragment<StreamHistoryEntry> {
|
||||
|
||||
@NonNull
|
||||
public static WatchedHistoryFragment newInstance() {
|
||||
@ -37,7 +41,6 @@ public class WatchedHistoryFragment extends HistoryFragment<WatchHistoryEntry> {
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
historyItemSwipeCallback(allowedSwipeToDeleteDirections);
|
||||
}
|
||||
|
||||
@StringRes
|
||||
@ -48,27 +51,59 @@ public class WatchedHistoryFragment extends HistoryFragment<WatchHistoryEntry> {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected WatchedHistoryAdapter createAdapter() {
|
||||
return new WatchedHistoryAdapter(getContext());
|
||||
protected StreamHistoryAdapter createAdapter() {
|
||||
return new StreamHistoryAdapter(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<List<Long>> insert(Collection<StreamHistoryEntry> entries) {
|
||||
return historyRecordManager.insertStreamHistory(entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<Integer> delete(Collection<StreamHistoryEntry> entries) {
|
||||
return historyRecordManager.deleteStreamHistory(entries);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected HistoryDAO<WatchHistoryEntry> createHistoryDAO() {
|
||||
return NewPipeDatabase.getInstance().watchHistoryDAO();
|
||||
protected Flowable<List<StreamHistoryEntry>> getAll() {
|
||||
return historyRecordManager.getStreamHistory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistoryItemClick(WatchHistoryEntry historyItem) {
|
||||
NavigationHelper.openVideoDetail(getContext(),
|
||||
historyItem.getServiceId(),
|
||||
historyItem.getUrl(),
|
||||
historyItem.getTitle());
|
||||
public void onHistoryItemClick(StreamHistoryEntry historyItem) {
|
||||
NavigationHelper.openVideoDetail(getContext(), historyItem.serviceId, historyItem.url,
|
||||
historyItem.title);
|
||||
}
|
||||
|
||||
private static class WatchedHistoryAdapter extends HistoryEntryAdapter<WatchHistoryEntry, ViewHolder> {
|
||||
@Override
|
||||
public void onHistoryItemLongClick(StreamHistoryEntry item) {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(item.title)
|
||||
.setMessage(R.string.delete_stream_history_prompt)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete_one, (dialog, i) -> {
|
||||
final Single<Integer> onDelete = historyRecordManager
|
||||
.deleteStreamHistory(Collections.singleton(item))
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
disposables.add(onDelete.subscribe());
|
||||
makeSnackbar(R.string.item_deleted);
|
||||
})
|
||||
.setNegativeButton(R.string.delete_all, (dialog, i) -> {
|
||||
final Single<Integer> onDeleteAll = historyRecordManager
|
||||
.deleteStreamHistory(item.streamId)
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
disposables.add(onDeleteAll.subscribe());
|
||||
makeSnackbar(R.string.item_deleted);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
public WatchedHistoryAdapter(Context context) {
|
||||
private static class StreamHistoryAdapter extends HistoryEntryAdapter<StreamHistoryEntry, ViewHolder> {
|
||||
|
||||
StreamHistoryAdapter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@ -87,13 +122,13 @@ public class WatchedHistoryFragment extends HistoryFragment<WatchHistoryEntry> {
|
||||
}
|
||||
|
||||
@Override
|
||||
void onBindViewHolder(ViewHolder holder, WatchHistoryEntry entry, int position) {
|
||||
holder.date.setText(getFormattedDate(entry.getCreationDate()));
|
||||
holder.streamTitle.setText(entry.getTitle());
|
||||
holder.uploader.setText(entry.getUploader());
|
||||
holder.duration.setText(Localization.getDurationString(entry.getDuration()));
|
||||
ImageLoader.getInstance()
|
||||
.displayImage(entry.getThumbnailURL(), holder.thumbnailView, StreamInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
void onBindViewHolder(ViewHolder holder, StreamHistoryEntry entry, int position) {
|
||||
holder.date.setText(getFormattedDate(entry.accessDate));
|
||||
holder.streamTitle.setText(entry.title);
|
||||
holder.uploader.setText(entry.uploader);
|
||||
holder.duration.setText(Localization.getDurationString(entry.duration));
|
||||
ImageLoader.getInstance().displayImage(entry.thumbnailUrl, holder.thumbnailView,
|
||||
StreamInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,10 +61,9 @@ import com.google.android.exoplayer2.util.Util;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.local.StreamRecordManager;
|
||||
import org.schabi.newpipe.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.helper.AudioReactor;
|
||||
import org.schabi.newpipe.player.helper.CacheFactory;
|
||||
import org.schabi.newpipe.player.helper.LoadController;
|
||||
@ -150,7 +149,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||
protected Disposable progressUpdateReactor;
|
||||
protected CompositeDisposable databaseUpdateReactor;
|
||||
|
||||
protected StreamRecordManager recordManager;
|
||||
protected HistoryRecordManager recordManager;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@ -176,9 +175,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||
public void initPlayer() {
|
||||
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
||||
|
||||
if (recordManager == null) {
|
||||
recordManager = new StreamRecordManager(NewPipeDatabase.getInstance(context));
|
||||
}
|
||||
if (recordManager == null) recordManager = new HistoryRecordManager(context);
|
||||
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
|
||||
databaseUpdateReactor = new CompositeDisposable();
|
||||
|
||||
@ -614,7 +611,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||
// If the user selects a new track, then the discontinuity occurs after the index is changed.
|
||||
// Therefore, the only source that causes a discrepancy would be gapless transition,
|
||||
// which can only offset the current track by +1.
|
||||
if (newWindowIndex == playQueue.getIndex() + 1) {
|
||||
if (newWindowIndex == playQueue.getIndex() + 1 ||
|
||||
(newWindowIndex == 0 && playQueue.getIndex() == playQueue.size() - 1)) {
|
||||
playQueue.offsetIndex(+1);
|
||||
}
|
||||
playbackManager.load();
|
||||
|
@ -229,6 +229,8 @@
|
||||
<string name="view">Play</string>
|
||||
<string name="create">Create</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="delete_one">Delete One</string>
|
||||
<string name="delete_all">Delete All</string>
|
||||
<string name="checksum">Checksum</string>
|
||||
|
||||
<!-- Fragment -->
|
||||
@ -307,6 +309,8 @@
|
||||
<string name="history_cleared">History cleared</string>
|
||||
<string name="item_deleted">Item deleted</string>
|
||||
<string name="delete_item_search_history">Do you want to delete this item from search history?</string>
|
||||
<string name="delete_stream_history_prompt">Do you want to delete this item from watch history?</string>
|
||||
<string name="delete_all_history_prompt">Are you sure you want to delete all items from history?</string>
|
||||
<string name="title_watch_history">Watch History</string>
|
||||
<string name="title_most_played">Most Played</string>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user