From d87e13a2880c2152e6fd80ac57397ab7ca894878 Mon Sep 17 00:00:00 2001 From: u7084054 Date: Sun, 27 Oct 2024 16:36:26 +1100 Subject: [PATCH] making pull request branch --- .../10.json | 737 ++++++++++++++++++ .../newpipe/database/DatabaseMigrationTest.kt | 19 + .../local/history/HistoryRecordManagerTest.kt | 67 +- .../org/schabi/newpipe/NewPipeDatabase.java | 3 +- .../schabi/newpipe/database/AppDatabase.java | 4 +- .../schabi/newpipe/database/Migrations.java | 10 + .../history/dao/SearchHistoryDAO.java | 14 +- .../history/model/SearchHistoryEntry.kt | 4 +- .../fragments/list/search/SearchFragment.java | 59 +- .../fragments/list/search/SuggestionItem.java | 17 + .../list/search/SuggestionListAdapter.java | 43 +- .../local/history/HistoryRecordManager.java | 33 +- .../res/layout/item_search_suggestion.xml | 2 + 13 files changed, 937 insertions(+), 75 deletions(-) create mode 100644 app/schemas/org.schabi.newpipe.database.AppDatabase/10.json diff --git a/app/schemas/org.schabi.newpipe.database.AppDatabase/10.json b/app/schemas/org.schabi.newpipe.database.AppDatabase/10.json new file mode 100644 index 00000000000..4cce53fee9f --- /dev/null +++ b/app/schemas/org.schabi.newpipe.database.AppDatabase/10.json @@ -0,0 +1,737 @@ +{ + "formatVersion": 1, + "database": { + "version": 10, + "identityHash": "92dd562cb84cdebafed0586775a03495", + "entities": [ + { + "tableName": "subscriptions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "service_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "avatarUrl", + "columnName": "avatar_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subscriberCount", + "columnName": "subscriber_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "notificationMode", + "columnName": "notification_mode", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + }, + "indices": [ + { + "name": "index_subscriptions_service_id_url", + "unique": true, + "columnNames": [ + "service_id", + "url" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "search_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT, `bookmark` INTEGER NOT NULL DEFAULT 0, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "creationDate", + "columnName": "creation_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "serviceId", + "columnName": "service_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "search", + "columnName": "search", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bookmark", + "columnName": "bookmark", + "affinity": "INTEGER", + "notNull": true, + "default": 0 + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_search_history_search", + "unique": false, + "columnNames": [ + "search" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "streams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "service_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "streamType", + "columnName": "stream_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploader_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "viewCount", + "columnName": "view_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "textualUploadDate", + "columnName": "textual_upload_date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "upload_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isUploadDateApproximation", + "columnName": "is_upload_date_approximation", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + }, + "indices": [ + { + "name": "index_streams_service_id_url", + "unique": true, + "columnNames": [ + "service_id", + "url" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "stream_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "streamUid", + "columnName": "stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accessDate", + "columnName": "access_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatCount", + "columnName": "repeat_count", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "stream_id", + "access_date" + ] + }, + "indices": [ + { + "name": "index_stream_history_stream_id", + "unique": false, + "columnNames": [ + "stream_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)" + } + ], + "foreignKeys": [ + { + "table": "streams", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "stream_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "stream_state", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "streamUid", + "columnName": "stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "progressMillis", + "columnName": "progress_time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "stream_id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "streams", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "stream_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "playlists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, `thumbnail_stream_id` INTEGER NOT NULL, `display_index` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isThumbnailPermanent", + "columnName": "is_thumbnail_permanent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thumbnailStreamId", + "columnName": "thumbnail_stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "displayIndex", + "columnName": "display_index", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "playlist_stream_join", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "playlistUid", + "columnName": "playlist_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "streamUid", + "columnName": "stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "index", + "columnName": "join_index", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "playlist_id", + "join_index" + ] + }, + "indices": [ + { + "name": "index_playlist_stream_join_playlist_id_join_index", + "unique": true, + "columnNames": [ + "playlist_id", + "join_index" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)" + }, + { + "name": "index_playlist_stream_join_stream_id", + "unique": false, + "columnNames": [ + "stream_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)" + } + ], + "foreignKeys": [ + { + "table": "playlists", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "playlist_id" + ], + "referencedColumns": [ + "uid" + ] + }, + { + "table": "streams", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "stream_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "remote_playlists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `display_index` INTEGER NOT NULL, `stream_count` INTEGER)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "service_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "displayIndex", + "columnName": "display_index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "streamCount", + "columnName": "stream_count", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + }, + "indices": [ + { + "name": "index_remote_playlists_service_id_url", + "unique": true, + "columnNames": [ + "service_id", + "url" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "feed", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "streamId", + "columnName": "stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscriptionId", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "stream_id", + "subscription_id" + ] + }, + "indices": [ + { + "name": "index_feed_subscription_id", + "unique": false, + "columnNames": [ + "subscription_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)" + } + ], + "foreignKeys": [ + { + "table": "streams", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "stream_id" + ], + "referencedColumns": [ + "uid" + ] + }, + { + "table": "subscriptions", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "subscription_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "feed_group", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortOrder", + "columnName": "sort_order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + }, + "indices": [ + { + "name": "index_feed_group_sort_order", + "unique": false, + "columnNames": [ + "sort_order" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "feed_group_subscription_join", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "feedGroupId", + "columnName": "group_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscriptionId", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "group_id", + "subscription_id" + ] + }, + "indices": [ + { + "name": "index_feed_group_subscription_join_subscription_id", + "unique": false, + "columnNames": [ + "subscription_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)" + } + ], + "foreignKeys": [ + { + "table": "feed_group", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "group_id" + ], + "referencedColumns": [ + "uid" + ] + }, + { + "table": "subscriptions", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "subscription_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "feed_last_updated", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "subscriptionId", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "last_updated", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "subscription_id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "subscriptions", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "subscription_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '92dd562cb84cdebafed0586775a03495')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt index a34cfece671..398e3712554 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt @@ -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() @@ -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() @@ -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() diff --git a/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt b/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt index 24be0f868d1..fbe78eb5120 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt @@ -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 @@ -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 @@ -118,10 +118,10 @@ 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 ) } @@ -129,20 +129,25 @@ class HistoryRecordManagerTest { 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( + searches[0].search.toString(), + searches[1].search.toString(), + searches[2].search.toString() + ) + assertThat(res).containsExactly("AA", "A", "BA") } @Test @@ -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 @@ -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), ) } } diff --git a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java index 21c5354f44d..ea1dbdf0c16 100644 --- a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java +++ b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java @@ -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; @@ -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(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java b/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java index 04d93a238d5..88dcd455299 100644 --- a/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java +++ b/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java @@ -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; @@ -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"; diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java index c9f630869c9..8fabc885eb6 100644 --- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java +++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java @@ -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; @@ -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() { } } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java index 8a281bdb48c..cef51aff42f 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java @@ -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 { - 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 + ")") @@ -37,16 +39,16 @@ public interface SearchHistoryDAO extends HistoryDAO { @Override Flowable> 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> getUniqueEntries(int limit); + Flowable> getUniqueEntries(int limit); @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) @Override Flowable> 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> getSimilarEntries(String query, int limit); + Flowable> getSimilarEntries(String query, int limit); } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt b/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt index 8cb9a25ca17..04a60e2966b 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt @@ -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) @@ -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" } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 18c60400b47..491224ff6da 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -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; @@ -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; @@ -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 + "]"); @@ -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) { @@ -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 + "]"); @@ -728,7 +745,7 @@ private Observable> getLocalSuggestionsObservable( .toObservable() .map(searchHistoryEntries -> searchHistoryEntries.stream() - .map(entry -> new SuggestionItem(true, entry)) + .map(entry -> new SuggestionItem(entry)) .collect(Collectors.toList())); } @@ -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) @@ -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)) @@ -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)) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java index 83f68dbb571..f680fe0251a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java @@ -2,15 +2,32 @@ import androidx.annotation.NonNull; +import org.schabi.newpipe.database.history.model.SearchHistoryEntry; + + public class SuggestionItem { final boolean fromHistory; public final String query; + public boolean bookmark; + public int serviceId; + public long historyId; public SuggestionItem(final boolean fromHistory, final String query) { this.fromHistory = fromHistory; this.query = query; + this.bookmark = false; + } + + public SuggestionItem(final SearchHistoryEntry entry) { + this.fromHistory = true; + this.query = entry.getSearch(); + this.bookmark = entry.getBookmark(); + this.serviceId = entry.getServiceId(); + this.historyId = entry.getId(); + } + @Override public boolean equals(final Object o) { if (o instanceof SuggestionItem) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java index 856ba22f19c..abcdfb7ecf3 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java @@ -11,6 +11,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ItemSearchSuggestionBinding; + public class SuggestionListAdapter extends ListAdapter { private OnSuggestionItemSelected listener; @@ -34,7 +35,29 @@ public SuggestionItemHolder onCreateViewHolder(@NonNull final ViewGroup parent, @Override public void onBindViewHolder(final SuggestionItemHolder holder, final int position) { final SuggestionItem currentItem = getItem(position); - holder.updateFrom(currentItem); + holder.updateFrom(currentItem, position); + + holder.itemBinding.itemSuggestionIcon.setOnClickListener(v -> { + if (listener != null) { + // Only allow bookmarking if item was searched before + // otherwise default to previous + if (currentItem.fromHistory) { + listener.onBookmark(currentItem); + //listener.onSuggestionItemInserted(currentItem); + + } else { + listener.onSuggestionItemSelected(currentItem); + } + } + }); + + holder.itemBinding.itemSuggestionIcon.setOnLongClickListener(v -> { + if (listener != null) { + listener.onSuggestionItemLongClick(currentItem); + } + return true; + }); + holder.itemBinding.suggestionSearch.setOnClickListener(v -> { if (listener != null) { listener.onSuggestionItemSelected(currentItem); @@ -51,6 +74,7 @@ public void onBindViewHolder(final SuggestionItemHolder holder, final int positi listener.onSuggestionItemInserted(currentItem); } }); + } public interface OnSuggestionItemSelected { @@ -59,6 +83,8 @@ public interface OnSuggestionItemSelected { void onSuggestionItemInserted(SuggestionItem item); void onSuggestionItemLongClick(SuggestionItem item); + + void onBookmark(SuggestionItem item); } public static final class SuggestionItemHolder extends RecyclerView.ViewHolder { @@ -69,21 +95,28 @@ private SuggestionItemHolder(final ItemSearchSuggestionBinding binding) { this.itemBinding = binding; } - private void updateFrom(final SuggestionItem item) { - itemBinding.itemSuggestionIcon.setImageResource(item.fromHistory ? R.drawable.ic_history - : R.drawable.ic_search); + private void updateFrom(final SuggestionItem item, final int position) { + + itemBinding.itemSuggestionIcon.setImageResource(item.bookmark ? R.drawable.ic_bookmark + : (item.fromHistory ? R.drawable.ic_history + : R.drawable.ic_search)); itemBinding.itemSuggestionQuery.setText(item.query); } + } private static class SuggestionItemCallback extends DiffUtil.ItemCallback { @Override public boolean areItemsTheSame(@NonNull final SuggestionItem oldItem, @NonNull final SuggestionItem newItem) { + return oldItem.fromHistory == newItem.fromHistory - && oldItem.query.equals(newItem.query); + && oldItem.query.equals(newItem.query) + && oldItem.bookmark == newItem.bookmark; + } + @Override public boolean areContentsTheSame(@NonNull final SuggestionItem oldItem, @NonNull final SuggestionItem newItem) { diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index ed3cf548f96..1097e963732 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -43,6 +43,7 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.fragments.list.search.SuggestionItem; import org.schabi.newpipe.local.feed.FeedViewModel; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.util.ExtractorHelper; @@ -103,10 +104,10 @@ public Maybe markAsWatched(final StreamInfoItem info) { // Duration will not exist if the item was loaded with fast mode, so fetch it if empty if (info.getDuration() < 0) { final StreamInfo completeInfo = ExtractorHelper.getStreamInfo( - info.getServiceId(), - info.getUrl(), - false - ) + info.getServiceId(), + info.getUrl(), + false + ) .subscribeOn(Schedulers.io()) .blockingGet(); duration = completeInfo.getDuration(); @@ -195,7 +196,8 @@ public Maybe onSearched(final int serviceId, final String search) { } final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC); - final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search); + final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search, + false); return Maybe.fromCallable(() -> database.runInTransaction(() -> { final SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry(); @@ -207,6 +209,21 @@ public Maybe onSearched(final int serviceId, final String search) { } })).subscribeOn(Schedulers.io()); } + public Maybe onBookmark(final SuggestionItem entry) { + if (!isSearchHistoryEnabled()) { + return Maybe.empty(); + } + + final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC); + final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, + entry.serviceId, entry.query, !entry.bookmark); + //ID is normally set to autogenerate, but the id needs to be set to the + // id of the existing database entry + newEntry.setId(entry.historyId); + return Maybe.fromCallable(() -> database.runInTransaction(() -> { + return (long) searchHistoryTable.update(newEntry); + })).subscribeOn(Schedulers.io()); + } public Single deleteSearchHistory(final String search) { return Single.fromCallable(() -> searchHistoryTable.deleteAllWhereQuery(search)) @@ -218,9 +235,9 @@ public Single deleteCompleteSearchHistory() { .subscribeOn(Schedulers.io()); } - public Flowable> getRelatedSearches(final String query, - final int similarQueryLimit, - final int uniqueQueryLimit) { + public Flowable> getRelatedSearches(final String query, + final int similarQueryLimit, + final int uniqueQueryLimit) { return query.length() > 0 ? searchHistoryTable.getSimilarEntries(query, similarQueryLimit) : searchHistoryTable.getUniqueEntries(uniqueQueryLimit); diff --git a/app/src/main/res/layout/item_search_suggestion.xml b/app/src/main/res/layout/item_search_suggestion.xml index f7a07bbcc04..54324d3b969 100644 --- a/app/src/main/res/layout/item_search_suggestion.xml +++ b/app/src/main/res/layout/item_search_suggestion.xml @@ -28,6 +28,8 @@ android:layout_gravity="center_vertical" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" + android:clickable="true" + android:focusable="true" tools:ignore="ContentDescription,RtlHardcoded" tools:src="@drawable/ic_history" />