Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Search Shortcuts Feature #7686 #11649

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
737 changes: 737 additions & 0 deletions app/schemas/org.schabi.newpipe.database.AppDatabase/10.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ class DatabaseMigrationTest {
Migrations.MIGRATION_8_9
)

testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_10,
true,
Migrations.MIGRATION_9_10
)

val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()

Expand Down Expand Up @@ -216,6 +223,11 @@ class DatabaseMigrationTest {
true, Migrations.MIGRATION_8_9
)

testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_10,
true, Migrations.MIGRATION_9_10
)

val migratedDatabaseV8 = getMigratedDatabase()
val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()

Expand Down Expand Up @@ -282,6 +294,13 @@ class DatabaseMigrationTest {
Migrations.MIGRATION_8_9
)

testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_10,
true,
Migrations.MIGRATION_9_10
)

val migratedDatabaseV9 = getMigratedDatabase()
var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ class HistoryRecordManagerTest {
@Test
fun deleteSearchHistory() {
val entries = listOf(
SearchHistoryEntry(time.minusSeconds(1), 0, "A"),
SearchHistoryEntry(time.minusSeconds(2), 2, "A"),
SearchHistoryEntry(time.minusSeconds(3), 1, "B"),
SearchHistoryEntry(time.minusSeconds(4), 0, "B"),
SearchHistoryEntry(time.minusSeconds(1), 0, "A", false),
SearchHistoryEntry(time.minusSeconds(2), 2, "A", false),
SearchHistoryEntry(time.minusSeconds(3), 1, "B", false),
SearchHistoryEntry(time.minusSeconds(4), 0, "B", false),
)

// make sure all 4 were inserted
Expand Down Expand Up @@ -83,9 +83,9 @@ class HistoryRecordManagerTest {
@Test
fun deleteCompleteSearchHistory() {
val entries = listOf(
SearchHistoryEntry(time.minusSeconds(1), 1, "A"),
SearchHistoryEntry(time.minusSeconds(2), 2, "B"),
SearchHistoryEntry(time.minusSeconds(3), 0, "C"),
SearchHistoryEntry(time.minusSeconds(1), 1, "A", false),
SearchHistoryEntry(time.minusSeconds(2), 2, "B", false),
SearchHistoryEntry(time.minusSeconds(3), 0, "C", false),
)

// make sure all 3 were inserted
Expand Down Expand Up @@ -118,31 +118,36 @@ class HistoryRecordManagerTest {
// make sure correct number of searches is returned and in correct order
val searches = manager.getRelatedSearches("", 6, 4).blockingFirst()
assertThat(searches).containsExactly(
RELATED_SEARCHES_ENTRIES[6].search, // A (even if in two places)
RELATED_SEARCHES_ENTRIES[4].search, // B
RELATED_SEARCHES_ENTRIES[5].search, // AA
RELATED_SEARCHES_ENTRIES[2].search, // BA
RELATED_SEARCHES_ENTRIES[6], // A (even if in two places)
RELATED_SEARCHES_ENTRIES[4], // B
RELATED_SEARCHES_ENTRIES[5], // AA
RELATED_SEARCHES_ENTRIES[2], // BA
)
}

@Test
fun getRelatedSearches_emptyQuery_manyDuplicates() {
insertShuffledRelatedSearches(
listOf(
SearchHistoryEntry(time.minusSeconds(9), 3, "A"),
SearchHistoryEntry(time.minusSeconds(8), 3, "AB"),
SearchHistoryEntry(time.minusSeconds(7), 3, "A"),
SearchHistoryEntry(time.minusSeconds(6), 3, "A"),
SearchHistoryEntry(time.minusSeconds(5), 3, "BA"),
SearchHistoryEntry(time.minusSeconds(4), 3, "A"),
SearchHistoryEntry(time.minusSeconds(3), 3, "A"),
SearchHistoryEntry(time.minusSeconds(2), 0, "A"),
SearchHistoryEntry(time.minusSeconds(1), 2, "AA"),
SearchHistoryEntry(time.minusSeconds(9), 3, "A", false),
SearchHistoryEntry(time.minusSeconds(8), 3, "AB", false),
SearchHistoryEntry(time.minusSeconds(7), 3, "A", false),
SearchHistoryEntry(time.minusSeconds(6), 3, "A", false),
SearchHistoryEntry(time.minusSeconds(5), 3, "BA", false),
SearchHistoryEntry(time.minusSeconds(4), 3, "A", false),
SearchHistoryEntry(time.minusSeconds(3), 3, "A", false),
SearchHistoryEntry(time.minusSeconds(2), 0, "A", false),
SearchHistoryEntry(time.minusSeconds(1), 2, "AA", false),
)
)

val searches = manager.getRelatedSearches("", 9, 3).blockingFirst()
assertThat(searches).containsExactly("AA", "A", "BA")
var res = listOf<String>(
searches[0].search.toString(),
searches[1].search.toString(),
searches[2].search.toString()
)
assertThat(res).containsExactly("AA", "A", "BA")
}

@Test
Expand All @@ -152,9 +157,9 @@ class HistoryRecordManagerTest {
// make sure correct number of searches is returned and in correct order
val searches = manager.getRelatedSearches("A", 3, 5).blockingFirst()
assertThat(searches).containsExactly(
RELATED_SEARCHES_ENTRIES[6].search, // A (even if in two places)
RELATED_SEARCHES_ENTRIES[5].search, // AA
RELATED_SEARCHES_ENTRIES[1].search, // BA
RELATED_SEARCHES_ENTRIES[6], // A (even if in two places)
RELATED_SEARCHES_ENTRIES[5], // AA
RELATED_SEARCHES_ENTRIES[1], // BA
)

// also make sure that the string comparison is case insensitive
Expand All @@ -166,13 +171,13 @@ class HistoryRecordManagerTest {
private val time = OffsetDateTime.of(LocalDateTime.of(2000, 1, 1, 1, 1), ZoneOffset.UTC)

private val RELATED_SEARCHES_ENTRIES = listOf(
SearchHistoryEntry(time.minusSeconds(7), 2, "AC"),
SearchHistoryEntry(time.minusSeconds(6), 0, "ABC"),
SearchHistoryEntry(time.minusSeconds(5), 1, "BA"),
SearchHistoryEntry(time.minusSeconds(4), 3, "A"),
SearchHistoryEntry(time.minusSeconds(2), 0, "B"),
SearchHistoryEntry(time.minusSeconds(3), 2, "AA"),
SearchHistoryEntry(time.minusSeconds(1), 1, "A"),
SearchHistoryEntry(time.minusSeconds(7), 2, "AC", false),
SearchHistoryEntry(time.minusSeconds(6), 0, "ABC", false),
SearchHistoryEntry(time.minusSeconds(5), 1, "BA", false),
SearchHistoryEntry(time.minusSeconds(4), 3, "A", false),
SearchHistoryEntry(time.minusSeconds(2), 0, "B", false),
SearchHistoryEntry(time.minusSeconds(3), 2, "AA", false),
SearchHistoryEntry(time.minusSeconds(1), 1, "A", false),
)
}
}
3 changes: 2 additions & 1 deletion app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static org.schabi.newpipe.database.Migrations.MIGRATION_6_7;
import static org.schabi.newpipe.database.Migrations.MIGRATION_7_8;
import static org.schabi.newpipe.database.Migrations.MIGRATION_8_9;
import static org.schabi.newpipe.database.Migrations.MIGRATION_9_10;

import android.content.Context;
import android.database.Cursor;
Expand All @@ -29,7 +30,7 @@ private static AppDatabase getDatabase(final Context context) {
return Room
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9)
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.schabi.newpipe.database;

import static org.schabi.newpipe.database.Migrations.DB_VER_9;
import static org.schabi.newpipe.database.Migrations.DB_VER_10;

import androidx.room.Database;
import androidx.room.RoomDatabase;
Expand Down Expand Up @@ -38,7 +38,7 @@
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
FeedLastUpdatedEntity.class
},
version = DB_VER_9
version = DB_VER_10
)
public abstract class AppDatabase extends RoomDatabase {
public static final String DATABASE_NAME = "newpipe.db";
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/org/schabi/newpipe/database/Migrations.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public final class Migrations {
public static final int DB_VER_8 = 8;
public static final int DB_VER_9 = 9;

public static final int DB_VER_10 = 10;

private static final String TAG = Migrations.class.getName();
public static final boolean DEBUG = MainActivity.DEBUG;

Expand Down Expand Up @@ -301,7 +303,15 @@ public void migrate(@NonNull final SupportSQLiteDatabase database) {
}
}
};
public static final Migration MIGRATION_9_10 = new Migration(DB_VER_9, DB_VER_10) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
// Adding in new column for search shortcut
database.execSQL("ALTER TABLE `search_history` ADD COLUMN `bookmark` "
+ "INTEGER NOT NULL DEFAULT 0");

}
};
private Migrations() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SERVICE_ID;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE_NAME;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.BOOKMARK;

@Dao
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
String ORDER_BY_MAX_CREATION_DATE = " ORDER BY MAX(" + CREATION_DATE + ") DESC";
String ORDER_BY_CREATION_DATE = " ORDER BY " + BOOKMARK + " DESC," + CREATION_DATE + " DESC";
String ORDER_BY_MAX_CREATION_DATE = " ORDER BY " + BOOKMARK + " DESC, "
+ "MAX(" + CREATION_DATE + ") DESC";

@Query("SELECT * FROM " + TABLE_NAME
+ " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
Expand All @@ -37,16 +39,16 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
@Override
Flowable<List<SearchHistoryEntry>> getAll();

@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " GROUP BY " + SEARCH
@Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH
+ ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
Flowable<List<String>> getUniqueEntries(int limit);
Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit);

@Query("SELECT * FROM " + TABLE_NAME
+ " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
@Override
Flowable<List<SearchHistoryEntry>> listByService(int serviceId);

@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
+ " GROUP BY " + SEARCH + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
Flowable<List<String>> getSimilarEntries(String query, int limit);
Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ data class SearchHistoryEntry(
@field:ColumnInfo(
name = SERVICE_ID
) var serviceId: Int,
@field:ColumnInfo(name = SEARCH) var search: String?
@field:ColumnInfo(name = SEARCH) var search: String?,
@field:ColumnInfo(name = BOOKMARK) var bookmark: Boolean
) {
@ColumnInfo(name = ID)
@PrimaryKey(autoGenerate = true)
Expand All @@ -36,5 +37,6 @@ data class SearchHistoryEntry(
const val SERVICE_ID = "service_id"
const val CREATION_DATE = "creation_date"
const val SEARCH = "search"
const val BOOKMARK = "bookmark"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;

import com.evernote.android.state.State;

import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentSearchBinding;
import org.schabi.newpipe.error.ErrorInfo;
Expand Down Expand Up @@ -79,6 +77,7 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.evernote.android.state.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
Expand Down Expand Up @@ -551,7 +550,7 @@ private void initSearchListeners() {
}
});

searchEditText.setOnFocusChangeListener((final View v, final boolean hasFocus) -> {
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
if (DEBUG) {
Log.d(TAG, "onFocusChange() called with: "
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
Expand Down Expand Up @@ -581,6 +580,24 @@ public void onSuggestionItemLongClick(final SuggestionItem item) {
showDeleteSuggestionDialog(item);
}
}

@Override
public void onBookmark(final SuggestionItem item) {

if (item.historyId > 0) {
disposables.add(historyRecordManager.onBookmark(item)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {
},
throwable -> showSnackBarError(new ErrorInfo(throwable,
UserAction.SEARCHED, item.query,
item.serviceId))
));
}
// Trigger refresh of list to show updated book using existing text watcher
searchEditText.setText(searchEditText.getText());
}
});

if (textWatcher != null) {
Expand Down Expand Up @@ -612,7 +629,7 @@ public void afterTextChanged(final Editable s) {
};
searchEditText.addTextChangedListener(textWatcher);
searchEditText.setOnEditorActionListener(
(final TextView v, final int actionId, final KeyEvent event) -> {
(TextView v, int actionId, KeyEvent event) -> {
if (DEBUG) {
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], "
+ "actionId = [" + actionId + "], event = [" + event + "]");
Expand Down Expand Up @@ -728,7 +745,7 @@ private Observable<List<SuggestionItem>> getLocalSuggestionsObservable(
.toObservable()
.map(searchHistoryEntries ->
searchHistoryEntries.stream()
.map(entry -> new SuggestionItem(true, entry))
.map(entry -> new SuggestionItem(entry))
.collect(Collectors.toList()));
}

Expand Down Expand Up @@ -764,14 +781,14 @@ private void initSuggestionObserver() {

if (showLocalSuggestions && shallShowRemoteSuggestionsNow) {
return Observable.zip(
getLocalSuggestionsObservable(query, 3),
getRemoteSuggestionsObservable(query),
(local, remote) -> {
remote.removeIf(remoteItem -> local.stream().anyMatch(
localItem -> localItem.equals(remoteItem)));
local.addAll(remote);
return local;
})
getLocalSuggestionsObservable(query, 3),
getRemoteSuggestionsObservable(query),
(local, remote) -> {
remote.removeIf(remoteItem -> local.stream().anyMatch(
localItem -> localItem.equals(remoteItem)));
local.addAll(remote);
return local;
})
.materialize();
} else if (showLocalSuggestions) {
return getLocalSuggestionsObservable(query, 25)
Expand Down Expand Up @@ -876,9 +893,9 @@ public void startLoading(final boolean forceLoad) {
searchDisposable.dispose();
}
searchDisposable = ExtractorHelper.searchFor(serviceId,
searchString,
Arrays.asList(contentFilter),
sortFilter)
searchString,
Arrays.asList(contentFilter),
sortFilter)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
Expand All @@ -897,11 +914,11 @@ protected void loadMoreItems() {
searchDisposable.dispose();
}
searchDisposable = ExtractorHelper.getMoreSearchItems(
serviceId,
searchString,
asList(contentFilter),
sortFilter,
nextPage)
serviceId,
searchString,
asList(contentFilter),
sortFilter,
nextPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
Expand Down
Loading
Loading