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

Search history - Merge feature branch #784

Merged
merged 55 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a06c02e
Add search history item
ashiagr Feb 6, 2023
f2fb605
Add dao
ashiagr Feb 6, 2023
429155e
Add dao tests
ashiagr Feb 6, 2023
8a37a8a
Add migration
ashiagr Feb 6, 2023
6f50ad9
Add limit
ashiagr Feb 6, 2023
24ea15c
Add suspend
ashiagr Feb 7, 2023
8f4ddd7
Replace assertEquals with assertTrue
ashiagr Feb 7, 2023
12d2db7
Merge pull request #758 from Automattic/task/752-add-search-history-dao
ashiagr Feb 7, 2023
724554d
Update dao to include show folders condition
ashiagr Feb 9, 2023
733c552
Add blank search history page, vm and manager
ashiagr Feb 9, 2023
edc33b6
Add search history entry sealed class with mappings
ashiagr Feb 9, 2023
e3b8c2e
Update search history manager
ashiagr Feb 9, 2023
21a1dd1
Show podcast in search history
ashiagr Feb 9, 2023
c6b7b04
Show folder in search history
ashiagr Feb 9, 2023
d67ed87
Add tests for folders in search history
ashiagr Feb 9, 2023
eef2d7e
Show search term in search history
ashiagr Feb 9, 2023
7949829
Re-display search history on orientation change and empty query
ashiagr Feb 9, 2023
f6f2367
Add else branch for unknown search history item
ashiagr Feb 10, 2023
56eb0d3
Fix folder placeholder images
ashiagr Feb 10, 2023
1a23420
Merge pull request #765 from Automattic/task/752-show-search-history
ashiagr Feb 10, 2023
06b9f23
Add header with clear all button
ashiagr Feb 10, 2023
9451d70
Add search history row click action
ashiagr Feb 10, 2023
d3f5f06
Add search history clear all confirmation dialog
ashiagr Feb 10, 2023
b7bb0bf
Merge pull request #767 from Automattic/task/752-search-history-clear…
ashiagr Feb 13, 2023
6931f6b
Adjust padding, fonts
ashiagr Feb 13, 2023
69c4ac0
Add row type with dot separator
ashiagr Feb 13, 2023
dbaee22
Fix talkback
ashiagr Feb 13, 2023
38f33b4
Show subscribed for folder and podcast
ashiagr Feb 13, 2023
260c126
Fix podcast ids for folder
ashiagr Feb 13, 2023
6e074f5
Revert "Show subscribed for folder and podcast"
ashiagr Feb 14, 2023
014af91
Update episode fields
ashiagr Feb 14, 2023
1feba9e
Center search term vertically
ashiagr Feb 14, 2023
468fdea
Remove limit and add max history count
ashiagr Feb 14, 2023
0467dba
Dismiss keyboard on scroll
ashiagr Feb 14, 2023
f4b7019
Fix tests
ashiagr Feb 14, 2023
f6e142c
Update clear all confirm button title
ashiagr Feb 16, 2023
ee7b08e
Update search field empty string
ashiagr Feb 16, 2023
90879e3
Update dispatcher
ashiagr Feb 16, 2023
ccd4ee4
Fix wrong item highlight on return from search results
ashiagr Feb 16, 2023
8e084da
Merge branch 'main' into feature/752-search-history
ashiagr Feb 16, 2023
29956f9
Update search filed empty text
ashiagr Feb 16, 2023
62de821
Track search history events
ashiagr Feb 16, 2023
f831eef
Track search shown, dismissed events for podcasts_list and discover
ashiagr Feb 16, 2023
fac6ba4
Track search shown, dismissed events for onboarding recommendations
ashiagr Feb 16, 2023
0988c2a
Update vm tests
ashiagr Feb 16, 2023
27c93c9
Add test for search history truncation
mchowning Feb 16, 2023
4227b91
Update search query immediately on search term selection from search …
ashiagr Feb 17, 2023
b8fc637
Merge branch 'task/752-search-history-fine-tune' of https://github.co…
ashiagr Feb 17, 2023
ab5871d
Merge pull request #769 from Automattic/task/752-search-history-fine-…
ashiagr Feb 17, 2023
f68950e
Merge branch 'feature/752-search-history' into task/752-search-histor…
ashiagr Feb 17, 2023
20cfee8
Merge branch 'feature/752-search-history' into task/752-search-histor…
ashiagr Feb 17, 2023
a1daa56
Merge pull request #778 from Automattic/task/752-search-history-analy…
ashiagr Feb 17, 2023
d341af2
Add change log
ashiagr Feb 17, 2023
8b37574
Merge branch 'main' into feature/752-search-history
ashiagr Feb 17, 2023
139d617
Set compose view composition strategy
ashiagr Feb 17, 2023
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
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
7.33
-----
* Bug Fixes:
* New Features:
* Added search history
([#784](https://github.com/Automattic/pocket-casts-android/pull/784)).
* Bug Fixes:
* App no longer crashes when the device browser has been disabled
([#762](https://github.com/Automattic/pocket-casts-android/issues/762)).

7.32
-----
* Bug Fixes:
* Bug Fixes:
* Ask notifications permission for newly-installed apps on Android 13
([#723](https://github.com/Automattic/pocket-casts-android/issues/723)).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ class AppDatabaseTest {
AppDatabase.MIGRATION_69_70,
AppDatabase.MIGRATION_70_71,
AppDatabase.MIGRATION_71_72,
AppDatabase.MIGRATION_72_73
AppDatabase.MIGRATION_72_73,
AppDatabase.MIGRATION_73_74
)
.build()
// close the database and release any stream resources when the test finishes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
package au.com.shiftyjelly.pocketcasts.models.db

import androidx.room.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import au.com.shiftyjelly.pocketcasts.models.db.dao.SearchHistoryDao
import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem
import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem.Folder
import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem.Podcast
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.util.UUID

private const val SEARCH_TERM_TEST1 = "test1"
private const val SEARCH_TERM_TEST2 = "test2"

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class SearchHistoryDaoTest {
lateinit var searchHistoryDao: SearchHistoryDao
lateinit var testDb: AppDatabase

@Before
fun setupDb() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
searchHistoryDao = testDb.searchHistoryDao()
}

@After
fun closeDb() {
testDb.close()
}

/* INSERT */
@Test
fun testInsertSearchTerm() = runTest {
searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1))

assertTrue(findSearchHistory().first().term == SEARCH_TERM_TEST1)
}

@Test
fun testInsertPodcastSearchHistory() {
val uuid = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid))

assertTrue(findSearchHistory().first().podcast?.uuid == uuid)
}
}

@Test
fun testInsertFolderSearchHistory() {
val uuid = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createFolderSearchHistoryItem(uuid))

assertTrue(findSearchHistory().first().folder?.uuid == uuid)
}
}

@Test
fun testInsertEpisodeSearchHistory() {
val uuid = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid))

assertTrue(findSearchHistory().first().episode?.uuid == uuid)
}
}

/* MULTIPLE INSERT OR REPLACE */
@Test
fun testMultipleInsertSameSearchTerms() {
runTest {
searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1))
val modifiedPrevious = findSearchHistory().first().modified
searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1))

val result = findSearchHistory()
assertEquals("Insert should replace, count should be 1", 1, result.size)
assertTrue(
"Replaced search term should be on top",
result.first().modified > modifiedPrevious
)
}
}

@Test
fun testMultipleInsertUniqueSearchTerms() {
runTest {
searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1))
searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST2))

val result = findSearchHistory()
assertEquals("Unique search terms should be inserted, count should be 2", 2, result.size)
assertEquals(
"Last search term inserted should be on top",
SEARCH_TERM_TEST2,
result.first().term
)
}
}

@Test
fun testMultipleInsertSamePodcastSearchHistory() {
val uuid = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid = uuid))
val modifiedPrevious = findSearchHistory().first().modified
searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid = uuid))

val result = findSearchHistory()
assertEquals("Same podcast search insert should replace, count should be 1", 1, result.size)
assertTrue(
"Replaced podcast search history item should be on top",
result.first().modified > modifiedPrevious
)
}
}

@Test
fun testMultipleInsertUniquePodcastSearchHistory() {
val uuid1 = UUID.randomUUID().toString()
val uuid2 = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid1))
searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid2))

val result = findSearchHistory()
assertEquals("Unique podcast search history should be inserted, count should be 2", 2, result.size)
assertEquals(
"Last podcast search history inserted should be on top",
uuid2,
result.first().podcast?.uuid
)
}
}

@Test
fun testMultipleInsertSameFolderSearchHistory() {
val uuid = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createFolderSearchHistoryItem(uuid))
val modifiedPrevious = findSearchHistory().first().modified
searchHistoryDao.insert(createFolderSearchHistoryItem(uuid))

val result = findSearchHistory()
assertEquals("Same folder search insert should replace, count should be 1", 1, result.size)
assertTrue(
"Replaced folder search should be on top",
result.first().modified > modifiedPrevious
)
}
}

@Test
fun testMultipleInsertUniqueFolderSearchHistory() {
val uuid1 = UUID.randomUUID().toString()
val uuid2 = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createFolderSearchHistoryItem(uuid1))
searchHistoryDao.insert(createFolderSearchHistoryItem(uuid2))

val result = findSearchHistory()
assertEquals("Unique folder search history should be inserted, count should be 2", 2, result.size)
assertEquals(
"Last folder search history inserted should be on top",
uuid2,
result.first().folder?.uuid
)
}
}

@Test
fun testMultipleInsertSameEpisodeSearchHistory() {
val uuid = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid))
val modifiedPrevious = findSearchHistory().first().modified
searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid))

val result = findSearchHistory()
assertEquals("Same episode insert should replace, count should be 1", 1, result.size)
assertTrue(
"Replaced episode search should be on top",
result.first().modified > modifiedPrevious
)
}
}

@Test
fun testMultipleInsertUniqueEpisodeSearchHistory() {
val uuid1 = UUID.randomUUID().toString()
val uuid2 = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid1))
searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid2))

val result = findSearchHistory()
assertEquals("Unique episode search history should be inserted, count should be 2", 2, result.size)
assertEquals(
"Last episode search history inserted should be on top",
uuid2,
result.first().episode?.uuid
)
}
}

@Test
fun testInsertTooManyItemsKeepsMostRecent() = runTest {
val oldSearch = createTermSearchHistoryItem("old_search", modified = 0)
val recentSearch = createTermSearchHistoryItem("recent_search", modified = 1)
assertTrue(
"second search occurred after first search",
recentSearch.modified > oldSearch.modified
)

searchHistoryDao.deleteAll()
searchHistoryDao.insert(oldSearch)
searchHistoryDao.insert(recentSearch)
assertEquals(
"results contain both searches before truncation",
2,
findSearchHistory().size
)

val limit = 1
searchHistoryDao.truncateHistory(limit)
val results = findSearchHistory()
assertEquals(
"truncation reduces number of items to limit",
limit,
results.size
)
assertTrue(
"truncated results should contain most recent search item (${recentSearch.term}), but instead has (${results.map { it.term }})",
results.first().term == recentSearch.term
)
}

/* DELETE */
@Test
fun testDeleteSearchHistoryItem() {
runTest {
searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1))

searchHistoryDao.delete(findSearchHistory().first())

assertTrue(findSearchHistory().isEmpty())
}
}

@Test
fun testDeleteAllSearchHistory() {
runTest {
val uuid = UUID.randomUUID().toString()
searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1))
searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid))
searchHistoryDao.insert(createFolderSearchHistoryItem(uuid))
searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid))

searchHistoryDao.deleteAll()

assertTrue(findSearchHistory().isEmpty())
}
}

/* SHOW FOLDERS FILTER */
@Test
fun testFoldersShownInSearchHistory() {
val uuid = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createFolderSearchHistoryItem(uuid))

assertTrue(findSearchHistory(showFolders = true).size == 1)
}
}

@Test
fun testFoldersHiddenInSearchHistory() {
val uuid = UUID.randomUUID().toString()
runTest {
searchHistoryDao.insert(createFolderSearchHistoryItem(uuid))

assertTrue(findSearchHistory(showFolders = false).isEmpty())
}
}

/* HELPER FUNCTIONS */
private fun createTermSearchHistoryItem(
term: String,
modified: Long = System.currentTimeMillis()
) = SearchHistoryItem(term = term, modified = modified)

private fun createPodcastSearchHistoryItem(uuid: String) =
SearchHistoryItem(
podcast = Podcast(
uuid = uuid,
title = "",
author = "",
)
)

private fun createFolderSearchHistoryItem(uuid: String) =
SearchHistoryItem(folder = Folder(uuid = uuid, title = "", color = 0, podcastIds = ""))

private fun createEpisodeSearchHistoryItem(uuid: String) =
SearchHistoryItem(
episode = SearchHistoryItem.Episode(
uuid = uuid,
title = "",
duration = 0.0,
podcastUuid = "",
podcastTitle = "",
artworkUrl = ""
)
)

private suspend fun findSearchHistory(
showFolders: Boolean = true,
) = searchHistoryDao.findAll(showFolders)
}
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,9 @@ class MainActivity :
viewModel.waitingForSignInToShowStories = false
} else if (!settings.getEndOfYearModalHasBeenShown()) {
viewModel.updateStoriesModalShowState(true)
setupEndOfYearLaunchBottomSheet()
launch(Dispatchers.Main) {
if (viewModel.isEndOfYearStoriesEligible()) setupEndOfYearLaunchBottomSheet()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fun OnboardingRecommendationsSearchPage(
}

BackHandler {
viewModel.onBackPressed()
onBackPressed()
}

Expand All @@ -77,7 +78,10 @@ fun OnboardingRecommendationsSearchPage(
) {
ThemedTopAppBar(
title = stringResource(LR.string.onboarding_find_podcasts),
onNavigationClick = onBackPressed
onNavigationClick = {
viewModel.onBackPressed()
onBackPressed()
}
)

SearchBar(
Expand Down
Loading