-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
22 changed files with 476 additions and 485 deletions
|
@ -26,8 +26,6 @@ import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.design.widget.NavigationView;
|
import android.support.design.widget.NavigationView;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.view.GravityCompat;
|
import android.support.v4.view.GravityCompat;
|
||||||
|
@ -42,40 +40,21 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
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.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.BackPressable;
|
||||||
import org.schabi.newpipe.fragments.MainFragment;
|
import org.schabi.newpipe.fragments.MainFragment;
|
||||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
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.Constants;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
import org.schabi.newpipe.util.StateSaver;
|
import org.schabi.newpipe.util.StateSaver;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.Date;
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
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 {
|
|
||||||
private static final String TAG = "MainActivity";
|
private static final String TAG = "MainActivity";
|
||||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
private ActionBarDrawerToggle toggle = null;
|
private ActionBarDrawerToggle toggle = null;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -86,7 +65,6 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||||
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -98,7 +76,6 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||||
|
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
setupDrawer();
|
setupDrawer();
|
||||||
initHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupDrawer() {
|
private void setupDrawer() {
|
||||||
|
@ -149,8 +126,6 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||||
if (!isChangingConfigurations()) {
|
if (!isChangingConfigurations()) {
|
||||||
StateSaver.clearStateFiles();
|
StateSaver.clearStateFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
disposeHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -357,75 +332,4 @@ public class MainActivity extends AppCompatActivity implements HistoryListener {
|
||||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
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.PlaylistEntity;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||||
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
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.dao.StreamStateDAO;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
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.stream.model.StreamStateEntity;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
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.
|
// 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.
|
// 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 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 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 )");
|
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.Dao;
|
||||||
import android.arch.persistence.room.Query;
|
import android.arch.persistence.room.Query;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
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 + ")")
|
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
SearchHistoryEntry getLatestEntry();
|
SearchHistoryEntry getLatestEntry();
|
||||||
|
|
||||||
@Query("DELETE FROM " + TABLE_NAME)
|
@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.Dao;
|
||||||
import android.arch.persistence.room.Query;
|
import android.arch.persistence.room.Query;
|
||||||
import android.arch.persistence.room.Transaction;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.database.BasicDAO;
|
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.StreamStatisticsEntry;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||||
|
|
||||||
import java.util.List;
|
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.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_ID;
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
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.history.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_ACCESS_DATE;
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public abstract class StreamHistoryDAO implements BasicDAO<StreamHistoryEntity> {
|
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.ColumnInfo;
|
||||||
import android.arch.persistence.room.Entity;
|
import android.arch.persistence.room.Entity;
|
||||||
import android.arch.persistence.room.Ignore;
|
import android.arch.persistence.room.Ignore;
|
||||||
|
import android.arch.persistence.room.Index;
|
||||||
|
|
||||||
import java.util.Date;
|
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 class SearchHistoryEntry extends HistoryEntry {
|
||||||
|
|
||||||
public static final String TABLE_NAME = "search_history";
|
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.ColumnInfo;
|
||||||
import android.arch.persistence.room.Entity;
|
import android.arch.persistence.room.Entity;
|
||||||
|
@ -6,12 +6,14 @@ import android.arch.persistence.room.ForeignKey;
|
||||||
import android.arch.persistence.room.Index;
|
import android.arch.persistence.room.Index;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
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.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
import static org.schabi.newpipe.database.history.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_ACCESS_DATE;
|
||||||
|
|
||||||
@Entity(tableName = STREAM_HISTORY_TABLE,
|
@Entity(tableName = STREAM_HISTORY_TABLE,
|
||||||
primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE},
|
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 android.arch.persistence.room.ColumnInfo;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -44,4 +43,8 @@ public class StreamHistoryEntry {
|
||||||
this.streamId = streamId;
|
this.streamId = streamId;
|
||||||
this.accessDate = accessDate;
|
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 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.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
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.BasicDAO;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
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.stream.model.StreamStateEntity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
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_SERVICE_ID;
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
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.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;
|
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
|
|
@ -44,6 +44,7 @@ import com.nirhart.parallaxscroll.views.ParallaxScrollView;
|
||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
import org.schabi.newpipe.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.download.DownloadDialog;
|
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.BaseStateFragment;
|
||||||
import org.schabi.newpipe.fragments.local.PlaylistAppendDialog;
|
import org.schabi.newpipe.fragments.local.PlaylistAppendDialog;
|
||||||
import org.schabi.newpipe.history.HistoryListener;
|
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.InfoItemBuilder;
|
||||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
||||||
|
@ -649,9 +651,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||||
public void onActionSelected(int selectedStreamId) {
|
public void onActionSelected(int selectedStreamId) {
|
||||||
try {
|
try {
|
||||||
NavigationHelper.playWithKore(activity, Uri.parse(info.getUrl().replace("https", "http")));
|
NavigationHelper.playWithKore(activity, Uri.parse(info.getUrl().replace("https", "http")));
|
||||||
if(activity instanceof HistoryListener) {
|
|
||||||
((HistoryListener) activity).onVideoPlayed(info, null);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if(DEBUG) Log.i(TAG, "Failed to start kore", e);
|
if(DEBUG) Log.i(TAG, "Failed to start kore", e);
|
||||||
showInstallKoreDialog(activity);
|
showInstallKoreDialog(activity);
|
||||||
|
@ -805,10 +804,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||||
private void openBackgroundPlayer(final boolean append) {
|
private void openBackgroundPlayer(final boolean append) {
|
||||||
AudioStream audioStream = currentInfo.getAudioStreams().get(ListHelper.getDefaultAudioFormat(activity, currentInfo.getAudioStreams()));
|
AudioStream audioStream = currentInfo.getAudioStreams().get(ListHelper.getDefaultAudioFormat(activity, currentInfo.getAudioStreams()));
|
||||||
|
|
||||||
if (activity instanceof HistoryListener) {
|
|
||||||
((HistoryListener) activity).onAudioPlayed(currentInfo, audioStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
|
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
|
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
|
||||||
|
|
||||||
|
@ -825,10 +820,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity instanceof HistoryListener) {
|
|
||||||
((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
|
final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
|
||||||
if (append) {
|
if (append) {
|
||||||
NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue);
|
NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue);
|
||||||
|
@ -844,10 +835,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||||
private void openVideoPlayer() {
|
private void openVideoPlayer() {
|
||||||
VideoStream selectedVideoStream = getSelectedVideoStream();
|
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)) {
|
if (PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
|
||||||
NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), selectedVideoStream);
|
NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), selectedVideoStream);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.fragments.list.search;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -30,10 +29,8 @@ import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
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.database.history.model.SearchHistoryEntry;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
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.extractor.search.SearchResult;
|
||||||
import org.schabi.newpipe.fragments.BackPressable;
|
import org.schabi.newpipe.fragments.BackPressable;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
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.report.UserAction;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
|
@ -64,16 +61,11 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.Flowable;
|
import io.reactivex.Flowable;
|
||||||
import io.reactivex.Notification;
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.ObservableSource;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.functions.BiFunction;
|
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.functions.Consumer;
|
||||||
import io.reactivex.functions.Function;
|
|
||||||
import io.reactivex.functions.Predicate;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import io.reactivex.subjects.PublishSubject;
|
import io.reactivex.subjects.PublishSubject;
|
||||||
|
|
||||||
|
@ -121,7 +113,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||||
private CompositeDisposable disposables = new CompositeDisposable();
|
private CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
|
||||||
private SuggestionListAdapter suggestionListAdapter;
|
private SuggestionListAdapter suggestionListAdapter;
|
||||||
private SearchHistoryDAO searchHistoryDAO;
|
private HistoryRecordManager historyRecordManager;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Views
|
// Views
|
||||||
|
@ -167,7 +159,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||||
isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
|
isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||||
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
||||||
|
|
||||||
searchHistoryDAO = NewPipeDatabase.getInstance().searchHistoryDAO();
|
historyRecordManager = new HistoryRecordManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -535,36 +527,24 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDeleteSuggestionDialog(final SuggestionItem item) {
|
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)
|
new AlertDialog.Builder(activity)
|
||||||
.setTitle(item.query)
|
.setTitle(item.query)
|
||||||
.setMessage(R.string.delete_item_search_history)
|
.setMessage(R.string.delete_item_search_history)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
.setPositiveButton(R.string.delete, (dialog, which) -> disposables.add(onDelete))
|
||||||
@Override
|
.show();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -589,83 +569,67 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||||
final Observable<String> observable = suggestionPublisher
|
final Observable<String> observable = suggestionPublisher
|
||||||
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
||||||
.startWith(searchQuery != null ? searchQuery : "")
|
.startWith(searchQuery != null ? searchQuery : "")
|
||||||
.filter(new Predicate<String>() {
|
.filter(query -> isSuggestionsEnabled);
|
||||||
@Override
|
|
||||||
public boolean test(@io.reactivex.annotations.NonNull String query) throws Exception {
|
|
||||||
return isSuggestionsEnabled;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
suggestionDisposable = observable
|
suggestionDisposable = observable
|
||||||
.switchMap(new Function<String, ObservableSource<Notification<List<SuggestionItem>>>>() {
|
.switchMap(query -> {
|
||||||
@Override
|
final Flowable<List<SearchHistoryEntry>> flowable = historyRecordManager
|
||||||
public ObservableSource<Notification<List<SuggestionItem>>> apply(@io.reactivex.annotations.NonNull final String query) throws Exception {
|
.getRelatedSearches(query, 3, 25);
|
||||||
final Flowable<List<SearchHistoryEntry>> flowable = query.length() > 0
|
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
||||||
? searchHistoryDAO.getSimilarEntries(query, 3)
|
.map(searchHistoryEntries -> {
|
||||||
: searchHistoryDAO.getUniqueEntries(25);
|
List<SuggestionItem> result = new ArrayList<>();
|
||||||
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
for (SearchHistoryEntry entry : searchHistoryEntries)
|
||||||
.map(new Function<List<SearchHistoryEntry>, List<SuggestionItem>>() {
|
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||||
@Override
|
return result;
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
|
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
|
||||||
// Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION
|
// Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION
|
||||||
return local.materialize();
|
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()
|
if (networkResult.size() > 0) result.addAll(networkResult);
|
||||||
.map(new Function<List<String>, List<SuggestionItem>>() {
|
return result;
|
||||||
@Override
|
}).materialize();
|
||||||
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();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(new Consumer<Notification<List<SuggestionItem>>>() {
|
.subscribe(listNotification -> {
|
||||||
@Override
|
if (listNotification.isOnNext()) {
|
||||||
public void accept(@io.reactivex.annotations.NonNull Notification<List<SuggestionItem>> listNotification) throws Exception {
|
handleSuggestions(listNotification.getValue());
|
||||||
if (listNotification.isOnNext()) {
|
} else if (listNotification.isOnError()) {
|
||||||
handleSuggestions(listNotification.getValue());
|
Throwable error = listNotification.getError();
|
||||||
} else if (listNotification.isOnError()) {
|
if (!ExtractorHelper.hasAssignableCauseThrowable(error,
|
||||||
Throwable error = listNotification.getError();
|
IOException.class, SocketException.class,
|
||||||
if (!ExtractorHelper.hasAssignableCauseThrowable(error,
|
InterruptedException.class, InterruptedIOException.class)) {
|
||||||
IOException.class, SocketException.class, InterruptedException.class, InterruptedIOException.class)) {
|
onSuggestionError(error);
|
||||||
onSuggestionError(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -718,11 +682,14 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor
|
||||||
hideSuggestionsPanel();
|
hideSuggestionsPanel();
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
|
|
||||||
if (activity instanceof HistoryListener) {
|
historyRecordManager.onSearched(serviceId, query)
|
||||||
((HistoryListener) activity).onSearch(serviceId, query);
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
suggestionPublisher.onNext(query);
|
.subscribe(
|
||||||
}
|
ignored -> {},
|
||||||
|
error -> showSnackBarError(error, UserAction.SEARCHED,
|
||||||
|
NewPipe.getNameOfService(serviceId), query, 0)
|
||||||
|
);
|
||||||
|
suggestionPublisher.onNext(query);
|
||||||
startLoading(false);
|
startLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,12 @@ import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
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.InfoItemDialog;
|
||||||
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
||||||
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
||||||
|
@ -49,7 +49,7 @@ public abstract class StatisticsPlaylistFragment
|
||||||
|
|
||||||
/* Used for independent events */
|
/* Used for independent events */
|
||||||
private Subscription databaseSubscription;
|
private Subscription databaseSubscription;
|
||||||
private StreamRecordManager recordManager;
|
private HistoryRecordManager recordManager;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Abstracts
|
// Abstracts
|
||||||
|
@ -68,7 +68,7 @@ public abstract class StatisticsPlaylistFragment
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
recordManager = new StreamRecordManager(NewPipeDatabase.getInstance(context));
|
recordManager = new HistoryRecordManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -205,7 +205,7 @@ public abstract class StatisticsPlaylistFragment
|
||||||
super.startLoading(forceLoad);
|
super.startLoading(forceLoad);
|
||||||
resetFragment();
|
resetFragment();
|
||||||
|
|
||||||
recordManager.getStatistics()
|
recordManager.getStreamStatistics()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(getHistoryObserver());
|
.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.FragmentPagerAdapter;
|
||||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -50,8 +51,10 @@ public class HistoryActivity extends AppCompatActivity {
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
if (getSupportActionBar() != null) {
|
||||||
getSupportActionBar().setTitle(R.string.title_activity_history);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setTitle(R.string.title_activity_history);
|
||||||
|
}
|
||||||
// Create the adapter that will return a fragment for each of the three
|
// Create the adapter that will return a fragment for each of the three
|
||||||
// primary sections of the activity.
|
// primary sections of the activity.
|
||||||
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
|
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
|
||||||
|
@ -66,17 +69,11 @@ public class HistoryActivity extends AppCompatActivity {
|
||||||
final FloatingActionButton fab = findViewById(R.id.fab);
|
final FloatingActionButton fab = findViewById(R.id.fab);
|
||||||
RxView.clicks(fab)
|
RxView.clicks(fab)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(new Consumer<Object>() {
|
.subscribe(ignored -> {
|
||||||
@Override
|
int currentItem = mViewPager.getCurrentItem();
|
||||||
public void accept(Object o) {
|
HistoryFragment fragment = (HistoryFragment) mSectionsPagerAdapter
|
||||||
int currentItem = mViewPager.getCurrentItem();
|
.instantiateItem(mViewPager, currentItem);
|
||||||
HistoryFragment fragment = (HistoryFragment) mSectionsPagerAdapter.instantiateItem(mViewPager, currentItem);
|
fragment.onHistoryCleared();
|
||||||
if(fragment != null) {
|
|
||||||
fragment.onHistoryCleared();
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Couldn't find current fragment");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,8 @@ import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.widget.RecyclerView;
|
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.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -19,7 +18,7 @@ import java.util.Date;
|
||||||
* @param <E> the type of the entries
|
* @param <E> the type of the entries
|
||||||
* @param <VH> the type of the view holder
|
* @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 ArrayList<E> mEntries;
|
||||||
private final DateFormat mDateFormat;
|
private final DateFormat mDateFormat;
|
||||||
|
@ -29,9 +28,8 @@ public abstract class HistoryEntryAdapter<E extends HistoryEntry, VH extends Rec
|
||||||
public HistoryEntryAdapter(Context context) {
|
public HistoryEntryAdapter(Context context) {
|
||||||
super();
|
super();
|
||||||
mEntries = new ArrayList<>();
|
mEntries = new ArrayList<>();
|
||||||
mDateFormat = android.text.format.DateFormat.getDateFormat(context.getApplicationContext());
|
mDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM,
|
||||||
|
Localization.getPreferredLocale(context));
|
||||||
setHasStableIds(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEntries(@NonNull Collection<E> historyEntries) {
|
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);
|
return mDateFormat.format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
return mEntries.get(position).getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return mEntries.size();
|
return mEntries.size();
|
||||||
|
@ -66,15 +59,20 @@ public abstract class HistoryEntryAdapter<E extends HistoryEntry, VH extends Rec
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(VH holder, int position) {
|
public void onBindViewHolder(VH holder, int position) {
|
||||||
final E entry = mEntries.get(position);
|
final E entry = mEntries.get(position);
|
||||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
holder.itemView.setOnClickListener(v -> {
|
||||||
@Override
|
if(onHistoryItemClickListener != null) {
|
||||||
public void onClick(View v) {
|
onHistoryItemClickListener.onHistoryItemClick(entry);
|
||||||
final OnHistoryItemClickListener<E> historyItemClickListener = onHistoryItemClickListener;
|
|
||||||
if(historyItemClickListener != null) {
|
|
||||||
historyItemClickListener.onHistoryItemClick(entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
holder.itemView.setOnLongClickListener(view -> {
|
||||||
|
if (onHistoryItemClickListener != null) {
|
||||||
|
onHistoryItemClickListener.onHistoryItemLongClick(entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
onBindViewHolder(holder, entry, position);
|
onBindViewHolder(holder, entry, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +92,8 @@ public abstract class HistoryEntryAdapter<E extends HistoryEntry, VH extends Rec
|
||||||
return mEntries.isEmpty();
|
return mEntries.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public E removeItemAt(int position) {
|
public interface OnHistoryItemClickListener<E> {
|
||||||
E entry = mEntries.remove(position);
|
void onHistoryItemClick(E item);
|
||||||
notifyItemRemoved(position);
|
void onHistoryItemLongClick(E item);
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnHistoryItemClickListener<E extends HistoryEntry> {
|
|
||||||
void onHistoryItemClick(E historyItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.history;
|
||||||
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
@ -12,34 +11,31 @@ import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.reactivestreams.Subscriber;
|
||||||
|
import org.reactivestreams.Subscription;
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
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.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.Observer;
|
import io.reactivex.Flowable;
|
||||||
|
import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.functions.Consumer;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import io.reactivex.subjects.PublishSubject;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
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> {
|
implements HistoryEntryAdapter.OnHistoryItemClickListener<E> {
|
||||||
|
|
||||||
private SharedPreferences mSharedPreferences;
|
private SharedPreferences mSharedPreferences;
|
||||||
|
@ -54,12 +50,11 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||||
Parcelable mRecyclerViewState;
|
Parcelable mRecyclerViewState;
|
||||||
private RecyclerView mRecyclerView;
|
private RecyclerView mRecyclerView;
|
||||||
private HistoryEntryAdapter<E, ? extends RecyclerView.ViewHolder> mHistoryAdapter;
|
private HistoryEntryAdapter<E, ? extends RecyclerView.ViewHolder> mHistoryAdapter;
|
||||||
private ItemTouchHelper.SimpleCallback mHistoryItemSwipeCallback;
|
|
||||||
// private int allowedSwipeToDeleteDirections = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
|
|
||||||
|
|
||||||
private HistoryDAO<E> mHistoryDataSource;
|
private Subscription historySubscription;
|
||||||
private PublishSubject<Collection<E>> mHistoryEntryDeleteSubject;
|
|
||||||
private PublishSubject<Collection<E>> mHistoryEntryInsertSubject;
|
protected HistoryRecordManager historyRecordManager;
|
||||||
|
protected CompositeDisposable disposables;
|
||||||
|
|
||||||
@StringRes
|
@StringRes
|
||||||
abstract int getEnabledConfigKey();
|
abstract int getEnabledConfigKey();
|
||||||
|
@ -77,88 +72,47 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||||
// Register history enabled listener
|
// Register history enabled listener
|
||||||
mSharedPreferences.registerOnSharedPreferenceChangeListener(mHistoryIsEnabledChangeListener);
|
mSharedPreferences.registerOnSharedPreferenceChangeListener(mHistoryIsEnabledChangeListener);
|
||||||
|
|
||||||
mHistoryDataSource = createHistoryDAO();
|
historyRecordManager = new HistoryRecordManager(getContext());
|
||||||
|
disposables = new CompositeDisposable();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
protected abstract HistoryEntryAdapter<E, ? extends RecyclerView.ViewHolder> createAdapter();
|
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
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
mHistoryDataSource.getAll()
|
|
||||||
.toObservable()
|
getAll().observeOn(AndroidSchedulers.mainThread()).subscribe(getHistorySubscriber());
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(getHistoryListConsumer());
|
final boolean newEnabled = isHistoryEnabled();
|
||||||
boolean newEnabled = isHistoryEnabled();
|
|
||||||
if (newEnabled != mHistoryIsEnabled) {
|
if (newEnabled != mHistoryIsEnabled) {
|
||||||
onHistoryIsEnabledChanged(newEnabled);
|
onHistoryIsEnabledChanged(newEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private Observer<List<E>> getHistoryListConsumer() {
|
private Subscriber<List<E>> getHistorySubscriber() {
|
||||||
return new Observer<List<E>>() {
|
return new Subscriber<List<E>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(@NonNull Disposable d) {
|
public void onSubscribe(Subscription s) {
|
||||||
|
if (historySubscription != null) historySubscription.cancel();
|
||||||
|
|
||||||
|
historySubscription = s;
|
||||||
|
historySubscription.request(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(@NonNull List<E> historyEntries) {
|
public void onNext(List<E> entries) {
|
||||||
if (!historyEntries.isEmpty()) {
|
if (!entries.isEmpty()) {
|
||||||
mHistoryAdapter.setEntries(historyEntries);
|
mHistoryAdapter.setEntries(entries);
|
||||||
animateView(mEmptyHistoryView, false, 200);
|
animateView(mEmptyHistoryView, false, 200);
|
||||||
|
|
||||||
if (mRecyclerViewState != null) {
|
if (mRecyclerViewState != null) {
|
||||||
|
@ -169,11 +123,13 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||||
mHistoryAdapter.clear();
|
mHistoryAdapter.clear();
|
||||||
showEmptyHistory();
|
showEmptyHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (historySubscription != null) historySubscription.request(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(@NonNull Throwable e) {
|
public void onError(Throwable t) {
|
||||||
// TODO: error handling like in (see e.g. subscription fragment)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -192,30 +148,33 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||||
*/
|
*/
|
||||||
@MainThread
|
@MainThread
|
||||||
public void onHistoryCleared() {
|
public void onHistoryCleared() {
|
||||||
final Parcelable stateBeforeClear = mRecyclerView.getLayoutManager().onSaveInstanceState();
|
if (getContext() == null) return;
|
||||||
final Collection<E> itemsToDelete = new ArrayList<>(mHistoryAdapter.getItems());
|
|
||||||
mHistoryEntryDeleteSubject.onNext(itemsToDelete);
|
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);
|
View view = getActivity().findViewById(R.id.main_content);
|
||||||
if (view == null) view = mRecyclerView.getRootView();
|
if (view == null) view = mRecyclerView.getRootView();
|
||||||
|
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
if (!itemsToDelete.isEmpty()) {
|
private void clearHistory() {
|
||||||
Snackbar.make(view, R.string.history_cleared, 5 * 1000)
|
final Collection<E> itemsToDelete = new ArrayList<>(mHistoryAdapter.getItems());
|
||||||
.setActionTextColor(Color.WHITE)
|
disposables.add(delete(itemsToDelete).observeOn(AndroidSchedulers.mainThread())
|
||||||
.setAction(R.string.undo, new View.OnClickListener() {
|
.subscribe());
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
mRecyclerViewState = stateBeforeClear;
|
|
||||||
mHistoryEntryInsertSubject.onNext(itemsToDelete);
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
} else {
|
|
||||||
Snackbar.make(view, R.string.history_cleared, Snackbar.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
makeSnackbar(R.string.history_cleared);
|
||||||
mHistoryAdapter.clear();
|
mHistoryAdapter.clear();
|
||||||
showEmptyHistory();
|
showEmptyHistory();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showEmptyHistory() {
|
private void showEmptyHistory() {
|
||||||
|
@ -227,18 +186,18 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||||
@Nullable
|
@Nullable
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@Override
|
@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);
|
View rootView = inflater.inflate(R.layout.fragment_history, container, false);
|
||||||
mRecyclerView = rootView.findViewById(R.id.history_view);
|
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);
|
mRecyclerView.setLayoutManager(layoutManager);
|
||||||
|
|
||||||
mHistoryAdapter = createAdapter();
|
mHistoryAdapter = createAdapter();
|
||||||
mHistoryAdapter.setOnHistoryItemClickListener(this);
|
mHistoryAdapter.setOnHistoryItemClickListener(this);
|
||||||
mRecyclerView.setAdapter(mHistoryAdapter);
|
mRecyclerView.setAdapter(mHistoryAdapter);
|
||||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mHistoryItemSwipeCallback);
|
|
||||||
itemTouchHelper.attachToRecyclerView(mRecyclerView);
|
|
||||||
mDisabledView = rootView.findViewById(R.id.history_disabled_view);
|
mDisabledView = rootView.findViewById(R.id.history_disabled_view);
|
||||||
mEmptyHistoryView = rootView.findViewById(R.id.history_empty);
|
mEmptyHistoryView = rootView.findViewById(R.id.history_empty);
|
||||||
|
|
||||||
|
@ -260,7 +219,7 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||||
mSharedPreferences = null;
|
mSharedPreferences = null;
|
||||||
mHistoryIsEnabledChangeListener = null;
|
mHistoryIsEnabledChangeListener = null;
|
||||||
mHistoryIsEnabledKey = null;
|
mHistoryIsEnabledKey = null;
|
||||||
mHistoryDataSource = null;
|
if (disposables != null) disposables.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -290,15 +249,8 @@ public abstract class HistoryFragment<E extends HistoryEntry> extends BaseFragme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private class HistoryIsEnabledChangeListener
|
||||||
* Creates a new history DAO
|
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
*
|
|
||||||
* @return the history DAO
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
protected abstract HistoryDAO<E> createHistoryDAO();
|
|
||||||
|
|
||||||
private class HistoryIsEnabledChangeListener implements SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
if (key.equals(mHistoryIsEnabledKey)) {
|
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.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
|
||||||
import org.schabi.newpipe.R;
|
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.database.history.model.SearchHistoryEntry;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
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
|
@NonNull
|
||||||
public static SearchHistoryFragment newInstance() {
|
public static SearchHistoryFragment newInstance() {
|
||||||
|
@ -30,7 +35,6 @@ public class SearchHistoryFragment extends HistoryFragment<SearchHistoryEntry> {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
historyItemSwipeCallback(allowedSwipeToDeleteDirections);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -39,21 +43,58 @@ public class SearchHistoryFragment extends HistoryFragment<SearchHistoryEntry> {
|
||||||
return new SearchHistoryAdapter(getContext());
|
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
|
@StringRes
|
||||||
@Override
|
@Override
|
||||||
int getEnabledConfigKey() {
|
int getEnabledConfigKey() {
|
||||||
return R.string.enable_search_history_key;
|
return R.string.enable_search_history_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
@Override
|
||||||
protected HistoryDAO<SearchHistoryEntry> createHistoryDAO() {
|
public void onHistoryItemClick(final SearchHistoryEntry historyItem) {
|
||||||
return NewPipeDatabase.getInstance().searchHistoryDAO();
|
NavigationHelper.openSearch(getContext(), historyItem.getServiceId(),
|
||||||
|
historyItem.getSearch());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHistoryItemClick(SearchHistoryEntry historyItem) {
|
public void onHistoryItemLongClick(final SearchHistoryEntry item) {
|
||||||
NavigationHelper.openSearch(getContext(), historyItem.getServiceId(), historyItem.getSearch());
|
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 {
|
private static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
|
@ -6,8 +6,8 @@ import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -16,18 +16,22 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.history.dao.HistoryDAO;
|
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
||||||
import org.schabi.newpipe.database.history.model.WatchHistoryEntry;
|
|
||||||
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
|
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
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
|
@NonNull
|
||||||
public static WatchedHistoryFragment newInstance() {
|
public static WatchedHistoryFragment newInstance() {
|
||||||
|
@ -37,7 +41,6 @@ public class WatchedHistoryFragment extends HistoryFragment<WatchHistoryEntry> {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
historyItemSwipeCallback(allowedSwipeToDeleteDirections);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@StringRes
|
@StringRes
|
||||||
|
@ -48,27 +51,59 @@ public class WatchedHistoryFragment extends HistoryFragment<WatchHistoryEntry> {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected WatchedHistoryAdapter createAdapter() {
|
protected StreamHistoryAdapter createAdapter() {
|
||||||
return new WatchedHistoryAdapter(getContext());
|
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
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected HistoryDAO<WatchHistoryEntry> createHistoryDAO() {
|
protected Flowable<List<StreamHistoryEntry>> getAll() {
|
||||||
return NewPipeDatabase.getInstance().watchHistoryDAO();
|
return historyRecordManager.getStreamHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHistoryItemClick(WatchHistoryEntry historyItem) {
|
public void onHistoryItemClick(StreamHistoryEntry historyItem) {
|
||||||
NavigationHelper.openVideoDetail(getContext(),
|
NavigationHelper.openVideoDetail(getContext(), historyItem.serviceId, historyItem.url,
|
||||||
historyItem.getServiceId(),
|
historyItem.title);
|
||||||
historyItem.getUrl(),
|
|
||||||
historyItem.getTitle());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,13 +122,13 @@ public class WatchedHistoryFragment extends HistoryFragment<WatchHistoryEntry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void onBindViewHolder(ViewHolder holder, WatchHistoryEntry entry, int position) {
|
void onBindViewHolder(ViewHolder holder, StreamHistoryEntry entry, int position) {
|
||||||
holder.date.setText(getFormattedDate(entry.getCreationDate()));
|
holder.date.setText(getFormattedDate(entry.accessDate));
|
||||||
holder.streamTitle.setText(entry.getTitle());
|
holder.streamTitle.setText(entry.title);
|
||||||
holder.uploader.setText(entry.getUploader());
|
holder.uploader.setText(entry.uploader);
|
||||||
holder.duration.setText(Localization.getDurationString(entry.getDuration()));
|
holder.duration.setText(Localization.getDurationString(entry.duration));
|
||||||
ImageLoader.getInstance()
|
ImageLoader.getInstance().displayImage(entry.thumbnailUrl, holder.thumbnailView,
|
||||||
.displayImage(entry.getThumbnailURL(), holder.thumbnailView, StreamInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS);
|
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.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
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.AudioReactor;
|
||||||
import org.schabi.newpipe.player.helper.CacheFactory;
|
import org.schabi.newpipe.player.helper.CacheFactory;
|
||||||
import org.schabi.newpipe.player.helper.LoadController;
|
import org.schabi.newpipe.player.helper.LoadController;
|
||||||
|
@ -150,7 +149,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
protected Disposable progressUpdateReactor;
|
protected Disposable progressUpdateReactor;
|
||||||
protected CompositeDisposable databaseUpdateReactor;
|
protected CompositeDisposable databaseUpdateReactor;
|
||||||
|
|
||||||
protected StreamRecordManager recordManager;
|
protected HistoryRecordManager recordManager;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@ -176,9 +175,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
public void initPlayer() {
|
public void initPlayer() {
|
||||||
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
||||||
|
|
||||||
if (recordManager == null) {
|
if (recordManager == null) recordManager = new HistoryRecordManager(context);
|
||||||
recordManager = new StreamRecordManager(NewPipeDatabase.getInstance(context));
|
|
||||||
}
|
|
||||||
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
|
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
|
||||||
databaseUpdateReactor = new CompositeDisposable();
|
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.
|
// 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,
|
// Therefore, the only source that causes a discrepancy would be gapless transition,
|
||||||
// which can only offset the current track by +1.
|
// 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);
|
playQueue.offsetIndex(+1);
|
||||||
}
|
}
|
||||||
playbackManager.load();
|
playbackManager.load();
|
||||||
|
|
|
@ -229,6 +229,8 @@
|
||||||
<string name="view">Play</string>
|
<string name="view">Play</string>
|
||||||
<string name="create">Create</string>
|
<string name="create">Create</string>
|
||||||
<string name="delete">Delete</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>
|
<string name="checksum">Checksum</string>
|
||||||
|
|
||||||
<!-- Fragment -->
|
<!-- Fragment -->
|
||||||
|
@ -307,6 +309,8 @@
|
||||||
<string name="history_cleared">History cleared</string>
|
<string name="history_cleared">History cleared</string>
|
||||||
<string name="item_deleted">Item deleted</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_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_watch_history">Watch History</string>
|
||||||
<string name="title_most_played">Most Played</string>
|
<string name="title_most_played">Most Played</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue