Skip to content

Commit 408259c

Browse files
authored
Issue #753 - Downloads that open in a new tab leave user on an empty tab (#883)
* Adds sourceTabId property to TabEntity in order to track source tab to navigate back to. Bumps DB version and adds migration sql Adds viewModel method to close and return to source tab. Adds download callbacks to return to source tab if empty tab is opened. Specifically in this event when a download link opens a new blank tab and needs to close after download has been triggered. * Removes need to pass entire TabEntity in favor of just passing sourceTabId * Code clean up, scope fix, and mirrors TabDataRepo delete closer in deleteAndSelectSource * Updates tests to expected addWithSource rather than just add for new tabs * Addresses PR feedback primarily letting fragment lifecycle handle cleanup and referencing the liveSelectedTab from inside the repo rather than duplicating the value and passing as a param * Addresses PR feedback - info related to the source tab will be pulled directly from the tabsDao rather than the LiveData object. * Addresses PR feedback - Create and use transaction rather than separate db calls to ensure data integrity. * Adds tests for the 2 new TabDataRepo methods (AddWithSource and DeleteCurrentTabAndSelectSource) * updates db migrations and tests after merge
1 parent 57d5064 commit 408259c

File tree

16 files changed

+910
-11
lines changed

16 files changed

+910
-11
lines changed

app/schemas/com.duckduckgo.app.global.db.AppDatabase/24.json

Lines changed: 752 additions & 0 deletions
Large diffs are not rendered by default.

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserViewModelTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,18 @@ class BrowserViewModelTest {
121121

122122
@Test
123123
fun whenNewTabRequestedThenTabAddedToRepository() = runBlocking<Unit> {
124+
whenever(mockTabRepository.liveSelectedTab).doReturn(MutableLiveData())
124125
testee.onNewTabRequested()
125-
verify(mockTabRepository).add()
126+
verify(mockTabRepository).addWithSource()
126127
}
127128

128129
@Test
129130
fun whenOpenInNewTabRequestedThenTabAddedToRepository() = runBlocking<Unit> {
130131
val url = "http://example.com"
131132
whenever(mockOmnibarEntryConverter.convertQueryToUrl(url)).thenReturn(url)
133+
whenever(mockTabRepository.liveSelectedTab).doReturn(MutableLiveData())
132134
testee.onOpenInNewTabRequested(url)
133-
verify(mockTabRepository).add(url)
135+
verify(mockTabRepository).addWithSource(url)
134136
}
135137

136138
@Test

app/src/androidTest/java/com/duckduckgo/app/global/db/AppDatabaseTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ class AppDatabaseTest {
281281
return stage
282282
}
283283

284+
@Test
285+
fun whenMigratingFromVersion23To24ThenValidationSucceeds() {
286+
createDatabaseAndMigrate(23, 24, migrationsProvider.MIGRATION_23_TO_24)
287+
}
288+
284289
private fun createDatabase(version: Int) {
285290
testHelper.createDatabase(TEST_DB_NAME, version).close()
286291
}

app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,39 @@ class TabDataRepositoryTest {
297297
db.close()
298298
}
299299

300+
@Test
301+
fun whenAddWithSourceEnsureTabEntryContainsExpectedSourceId() = runBlocking<Unit> {
302+
val db = createDatabase()
303+
val dao = db.tabsDao()
304+
val sourceTab = TabEntity(tabId = "sourceId", url = "http://www.example.com", position = 0)
305+
dao.addAndSelectTab(sourceTab)
306+
307+
testee = TabDataRepository(dao, SiteFactory(mockPrivacyPractices, mockEntityLookup), mockWebViewPreviewPersister, useOurAppDetector)
308+
309+
val addedTabId = testee.addWithSource("http://www.example.com", skipHome = false, isDefaultTab = false)
310+
val addedTab = testee.liveSelectedTab.blockingObserve()
311+
assertEquals(addedTabId, addedTab?.tabId)
312+
assertEquals(addedTab?.sourceTabId, sourceTab.tabId)
313+
}
314+
315+
@Test
316+
fun whenDeleteCurrentTabAndSelectSourceLiveSelectedTabReturnsToSourceTab() = runBlocking<Unit> {
317+
val db = createDatabase()
318+
val dao = db.tabsDao()
319+
val sourceTab = TabEntity(tabId = "sourceId", url = "http://www.example.com", position = 0)
320+
val tabToDelete = TabEntity(tabId = "tabToDeleteId", url = "http://www.example.com", position = 1, sourceTabId = "sourceId")
321+
dao.addAndSelectTab(sourceTab)
322+
dao.addAndSelectTab(tabToDelete)
323+
324+
testee = TabDataRepository(dao, SiteFactory(mockPrivacyPractices, mockEntityLookup), mockWebViewPreviewPersister, useOurAppDetector)
325+
326+
var currentSelectedTabId = testee.liveSelectedTab.blockingObserve()?.tabId
327+
assertEquals(currentSelectedTabId, tabToDelete.tabId)
328+
testee.deleteCurrentTabAndSelectSource()
329+
currentSelectedTabId = testee.liveSelectedTab.blockingObserve()?.tabId
330+
assertEquals(currentSelectedTabId, sourceTab.tabId)
331+
}
332+
300333
private fun createDatabase(): AppDatabase {
301334
return Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getInstrumentation().targetContext, AppDatabase::class.java)
302335
.allowMainThreadQueries()

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,10 +1107,19 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
11071107
}
11081108
}
11091109

1110+
private fun closeAndReturnToSourceIfBlankTab() {
1111+
if (viewModel.url == null) {
1112+
launch {
1113+
viewModel.closeAndSelectSourceTab()
1114+
}
1115+
}
1116+
}
1117+
11101118
private fun createDownloadListener(): FileDownloadListener {
11111119
return object : FileDownloadListener {
11121120
override fun downloadStarted() {
11131121
fileDownloadNotificationManager.showDownloadInProgressNotification()
1122+
closeAndReturnToSourceIfBlankTab()
11141123
}
11151124

11161125
override fun downloadFinished(file: File, mimeType: String?) {
@@ -1134,6 +1143,14 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
11341143
snackbar.show()
11351144
}
11361145

1146+
override fun downloadCancelled() {
1147+
closeAndReturnToSourceIfBlankTab()
1148+
}
1149+
1150+
override fun downloadOpened() {
1151+
closeAndReturnToSourceIfBlankTab()
1152+
}
1153+
11371154
private fun showDownloadManagerAppSettings() {
11381155
try {
11391156
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)

app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,14 @@ class BrowserTabViewModel(
505505
viewModelScope.launch { removeCurrentTabFromRepository() }
506506
}
507507

508+
override fun closeAndSelectSourceTab() {
509+
viewModelScope.launch { removeAndSelectTabFromRepository() }
510+
}
511+
512+
private suspend fun removeAndSelectTabFromRepository() {
513+
tabRepository.deleteCurrentTabAndSelectSource()
514+
}
515+
508516
fun onUserPressedForward() {
509517
navigationAwareLoginDetector.onEvent(NavigationEvent.UserAction.NavigateForward)
510518
if (!currentBrowserViewState().browserShowing) {

app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,15 @@ class BrowserViewModel(
127127
}
128128

129129
suspend fun onNewTabRequested(isDefaultTab: Boolean = false): String {
130-
return tabRepository.add(isDefaultTab = isDefaultTab)
130+
return tabRepository.addWithSource(isDefaultTab = isDefaultTab)
131131
}
132132

133133
suspend fun onOpenInNewTabRequested(query: String, skipHome: Boolean = false): String {
134-
return tabRepository.add(queryUrlConverter.convertQueryToUrl(query), skipHome, isDefaultTab = false)
134+
return tabRepository.addWithSource(
135+
queryUrlConverter.convertQueryToUrl(query),
136+
skipHome,
137+
isDefaultTab = false
138+
)
135139
}
136140

137141
suspend fun onTabsUpdated(tabs: List<TabEntity>?) {

app/src/main/java/com/duckduckgo/app/browser/DownloadConfirmationFragment.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class DownloadConfirmationFragment : BottomSheetDialogFragment() {
4949
lateinit var downloadListener: FileDownloadListener
5050

5151
private val pendingDownload: PendingFileDownload by lazy {
52-
arguments!![PENDING_DOWNLOAD_BUNDLE_KEY] as PendingFileDownload
52+
requireArguments()[PENDING_DOWNLOAD_BUNDLE_KEY] as PendingFileDownload
5353
}
5454

5555
private var file: File? = null
@@ -87,6 +87,7 @@ class DownloadConfirmationFragment : BottomSheetDialogFragment() {
8787
}
8888
view.cancel.setOnClickListener {
8989
Timber.i("Cancelled download for url ${pendingDownload.url}")
90+
downloadListener.downloadCancelled()
9091
dismiss()
9192
}
9293

@@ -126,6 +127,7 @@ class DownloadConfirmationFragment : BottomSheetDialogFragment() {
126127
Timber.e("No suitable activity found")
127128
Toast.makeText(activity, R.string.downloadConfirmationUnableToOpenFileText, Toast.LENGTH_SHORT).show()
128129
}
130+
downloadListener.downloadOpened()
129131
}
130132
}
131133

app/src/main/java/com/duckduckgo/app/browser/WebViewClientListener.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ interface WebViewClientListener {
4747
fun recoverFromRenderProcessGone()
4848
fun requiresAuthentication(request: BasicAuthenticationRequest)
4949
fun closeCurrentTab()
50+
fun closeAndSelectSourceTab()
5051
fun upgradedToHttps()
5152
fun surrogateDetected(surrogate: SurrogateResponse)
5253

app/src/main/java/com/duckduckgo/app/browser/downloader/FileDownloader.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class FileDownloader @Inject constructor(
5252
fun downloadStarted()
5353
fun downloadFinished(file: File, mimeType: String?)
5454
fun downloadFailed(message: String, downloadFailReason: DownloadFailReason)
55+
fun downloadCancelled()
56+
fun downloadOpened()
5557
}
5658
}
5759

0 commit comments

Comments
 (0)