Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5b1aeb3
WIP
marcosholgado Jun 3, 2020
baa850e
Add db migrations and new icon for shortcut
marcosholgado Jun 10, 2020
fc6e835
Merge branch 'develop' of github.com:duckduckgo/Android into feature/…
marcosholgado Jun 10, 2020
21c10ea
Rename methods/constants. Add strings for the new notification. Add n…
marcosholgado Jun 10, 2020
2f5c8c2
Move logic and add new tab repo methods
marcosholgado Jun 10, 2020
6219cfd
Add deletion cta to flow
marcosholgado Jun 10, 2020
5c8e12a
Hide keyboard when showing dialog
marcosholgado Jun 11, 2020
d91c039
Add copy to shortcut success toast
marcosholgado Jun 12, 2020
6234190
Fix tests
marcosholgado Jun 12, 2020
14e2a7f
Merge branch 'develop' of github.com:duckduckgo/Android into feature/…
marcosholgado Jun 12, 2020
09ffd52
Add pixels
marcosholgado Jun 20, 2020
2241e2d
Add more pixels
marcosholgado Jun 20, 2020
713ef52
Fix ktlint
marcosholgado Jun 20, 2020
900c29e
Merge branches 'develop' and 'feature/marcos/in_app_fb_usage_flow' of…
marcosholgado Jun 24, 2020
65a980c
Add pixel
marcosholgado Jun 24, 2020
e2c503c
Create tests and move use our app domains to the cta companion object
marcosholgado Jun 24, 2020
6eee789
Add more tests
marcosholgado Jun 24, 2020
8b14b10
Add tests
marcosholgado Jun 25, 2020
c4b1a34
Add ShortcutReceiver tests
marcosholgado Jun 25, 2020
073d893
Merge branches 'develop' and 'feature/marcos/in_app_fb_usage_flow' of…
marcosholgado Jun 25, 2020
2fef4dc
Amend notification schedule
marcosholgado Jun 25, 2020
10b05c5
Fix ktlint
marcosholgado Jun 25, 2020
accdf65
Fix tests
marcosholgado Jun 25, 2020
cdd4b7d
Merge branches 'develop' and 'feature/marcos/in_app_fb_usage_flow' of…
marcosholgado Jun 25, 2020
e0def0c
Add new logic for users with hide tips enable and tests
marcosholgado Jun 25, 2020
b0ff683
Remove unneded pixel
marcosholgado Jun 25, 2020
23b28eb
Rename KeyTimestamp to UserEvent
marcosholgado Jun 26, 2020
db616c3
Create use our app detector
marcosholgado Jun 26, 2020
df8f285
Rename methods
marcosholgado Jun 26, 2020
c359ea5
Amend copy
marcosholgado Jun 26, 2020
deb919a
Amend copy!
marcosholgado Jun 27, 2020
8fa6eb2
Merge branch 'develop' of github.com:duckduckgo/Android into feature/…
marcosholgado Jun 30, 2020
3ec696b
Integrate fireproof dialog
marcosholgado Jun 30, 2020
7c78d4c
Only migrate to use our app flow 10% of old users
marcosholgado Jun 30, 2020
c1c77c8
Amend code as per PR
marcosholgado Jul 1, 2020
2c1d230
Merge branches 'develop' and 'feature/marcos/in_app_fb_usage_flow' of…
marcosholgado Jul 1, 2020
698ced8
Do not rely on actual timestamps for tests
marcosholgado Jul 1, 2020
3328b32
Check if user is established before apply migration
marcosholgado Jul 1, 2020
2f0dca3
Amend code as per PR
marcosholgado Jul 1, 2020
df28e5d
Amend test as per PR
marcosholgado Jul 1, 2020
34600df
Filter by english locale and send pixel only if cta has been seen
marcosholgado Jul 2, 2020
d455fbb
Move language check to database migration
marcosholgado Jul 2, 2020
ffe4ba6
Send uoa pixel only if cta was seen
marcosholgado Jul 2, 2020
365efb6
Wrap call in launch using IO
marcosholgado Jul 3, 2020
43a46a9
Merge branches 'develop' and 'feature/marcos/in_app_fb_usage_flow' of…
marcosholgado Jul 3, 2020
a80bda1
Change order in when statement so we always get a high-res icon for u…
marcosholgado Jul 3, 2020
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
746 changes: 746 additions & 0 deletions app/schemas/com.duckduckgo.app.global.db.AppDatabase/22.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,28 @@ import com.duckduckgo.app.browser.session.WebViewSessionStorage
import com.duckduckgo.app.cta.db.DismissedCtaDao
import com.duckduckgo.app.cta.model.CtaId
import com.duckduckgo.app.cta.model.DismissedCta
import com.duckduckgo.app.cta.ui.*
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteDao
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteRepository
import com.duckduckgo.app.cta.ui.Cta
import com.duckduckgo.app.cta.ui.CtaViewModel
import com.duckduckgo.app.cta.ui.DaxBubbleCta
import com.duckduckgo.app.cta.ui.DaxDialogCta
import com.duckduckgo.app.cta.ui.HomePanelCta
import com.duckduckgo.app.cta.ui.UseOurAppCta
import com.duckduckgo.app.global.useourapp.UseOurAppDetector.Companion.USE_OUR_APP_DOMAIN
import com.duckduckgo.app.global.useourapp.UseOurAppDetector.Companion.USE_OUR_APP_SHORTCUT_URL
import com.duckduckgo.app.global.db.AppDatabase
import com.duckduckgo.app.global.install.AppInstallStore
import com.duckduckgo.app.global.model.Site
import com.duckduckgo.app.global.model.SiteFactory
import com.duckduckgo.app.global.events.db.UserEventKey
import com.duckduckgo.app.notification.model.UseOurAppNotification
import com.duckduckgo.app.global.events.db.UserEventEntity
import com.duckduckgo.app.global.events.db.UserEventsStore
import com.duckduckgo.app.global.useourapp.UseOurAppDetector
import com.duckduckgo.app.notification.db.NotificationDao
import com.duckduckgo.app.onboarding.store.AppStage
import com.duckduckgo.app.onboarding.store.OnboardingStore
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.privacy.db.NetworkLeaderboardDao
Expand Down Expand Up @@ -188,6 +203,12 @@ class BrowserTabViewModelTest {
@Mock
private lateinit var mockNavigationAwareLoginDetector: NavigationAwareLoginDetector

@Mock
private lateinit var mockUserEventsStore: UserEventsStore

@Mock
private lateinit var mockNotificationDao: NotificationDao

private lateinit var mockAutoCompleteApi: AutoCompleteApi

private lateinit var ctaViewModel: CtaViewModel
Expand Down Expand Up @@ -227,6 +248,8 @@ class BrowserTabViewModelTest {
mockSettingsStore,
mockOnboardingStore,
mockUserStageStore,
mockUserEventsStore,
UseOurAppDetector(mockUserEventsStore),
coroutineRule.testDispatcherProvider
)

Expand Down Expand Up @@ -263,6 +286,9 @@ class BrowserTabViewModelTest {
dispatchers = coroutineRule.testDispatcherProvider,
fireproofWebsiteRepository = FireproofWebsiteRepository(fireproofWebsiteDao, coroutineRule.testDispatcherProvider),
navigationAwareLoginDetector = mockNavigationAwareLoginDetector,
userEventsStore = mockUserEventsStore,
notificationDao = mockNotificationDao,
useOurAppDetector = UseOurAppDetector(mockUserEventsStore),
variantManager = mockVariantManager
)

Expand Down Expand Up @@ -313,19 +339,31 @@ class BrowserTabViewModelTest {
}

@Test
fun whenViewIsResumedAndBrowserShowingThenKeyboardHidden() {
setBrowserShowing(true)
testee.onViewResumed()
fun whenViewBecomesVisibleAndHomeShowingAndUserIsNotInUseOurAppOnboardingStageThenKeyboardShown() = coroutineRule.runBlocking {
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.ESTABLISHED)
setBrowserShowing(false)

testee.onViewVisible()
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertTrue(commandCaptor.allValues.contains(Command.HideKeyboard))
assertTrue(commandCaptor.allValues.contains(Command.ShowKeyboard))
}

@Test
fun whenViewIsResumedAndHomeShowingThenKeyboardShown() {
fun whenViewBecomesVisibleAndHomeShowingAndUserIsInUseOurAppOnboardingStageThenKeyboardHidden() = coroutineRule.runBlocking {
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.USE_OUR_APP_ONBOARDING)
setBrowserShowing(false)
testee.onViewResumed()

testee.onViewVisible()
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertTrue(commandCaptor.allValues.contains(Command.ShowKeyboard))
assertTrue(commandCaptor.allValues.contains(Command.HideKeyboard))
}

@Test
fun whenViewBecomesVisibleAndBrowserShowingThenKeyboardHidden() {
setBrowserShowing(true)
testee.onViewVisible()
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertTrue(commandCaptor.allValues.contains(Command.HideKeyboard))
}

@Test
Expand Down Expand Up @@ -1509,7 +1547,15 @@ class BrowserTabViewModelTest {
fun whenUserPressesBackAndNotSkippingHomeThenWebViewPreviewNotGenerated() {
setupNavigation(isBrowsing = true, canGoBack = false, skipHome = false)
testee.onUserPressedBack()
verify(mockCommandObserver, never()).onChanged(commandCaptor.capture())
assertFalse(commandCaptor.allValues.contains(Command.GenerateWebViewPreviewImage))
}

@Test
fun whenUserPressesBackAndGoesToHomeThenKeyboardShown() {
setupNavigation(isBrowsing = true, canGoBack = false, skipHome = false)
testee.onUserPressedBack()
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertTrue(commandCaptor.allValues.contains(Command.ShowKeyboard))
}

@Test
Expand Down Expand Up @@ -1650,6 +1696,16 @@ class BrowserTabViewModelTest {
assertCommandIssued<Command.LaunchLegacyAddWidget>()
}

@Test
fun whenUserClickedUseOurAppCtaOkButtonThenLaunchAddHomeShortcutAndNavigateCommand() {
whenever(mockOmnibarConverter.convertQueryToUrl(USE_OUR_APP_SHORTCUT_URL, null)).thenReturn(USE_OUR_APP_SHORTCUT_URL)
val cta = UseOurAppCta()
setCta(cta)
testee.onUserClickCtaOkButton()
assertCommandIssued<Command.AddHomeShortcut>()
assertCommandIssued<Navigate>()
}

@Test
fun whenSurveyCtaDismissedAndNoOtherCtaPossibleCtaIsNull() = coroutineRule.runBlocking {
givenShownCtas(CtaId.DAX_INTRO, CtaId.DAX_END)
Expand Down Expand Up @@ -1727,6 +1783,14 @@ class BrowserTabViewModelTest {
verify(mockSurveyDao).cancelScheduledSurveys()
}

@Test
fun whenUserClickedSecondaryCtaButtonInUseOurAppCtaThenLaunchShowKeyboardCommand() {
val cta = UseOurAppCta()
setCta(cta)
testee.onUserClickCtaSecondaryButton()
assertCommandIssued<Command.ShowKeyboard>()
}

@Test
fun whenSurrogateDetectedThenSiteUpdated() {
givenOneActiveTabSelected()
Expand Down Expand Up @@ -1915,6 +1979,30 @@ class BrowserTabViewModelTest {
}
}

@Test
fun whenLoginDetectedAndUrlIsUseOurAppThenRegisterUserEvent() = coroutineRule.runBlocking {
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)).thenReturn(null)
loginEventLiveData.value = givenLoginDetected(USE_OUR_APP_SHORTCUT_URL)

verify(mockUserEventsStore).registerUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)
}

@Test
fun whenLoginDetectedAndUrlIsNotUseOurAppThenDoNotRegisterUserEvent() = coroutineRule.runBlocking {
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)).thenReturn(null)
loginEventLiveData.value = givenLoginDetected("example.com")

verify(mockUserEventsStore, never()).registerUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)
}

@Test
fun whenLoginDetectedAndDialogAlreadySeenThenDoNotRegisterUserEvent() = coroutineRule.runBlocking {
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)).thenReturn(UserEventEntity(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN))
loginEventLiveData.value = givenLoginDetected(USE_OUR_APP_SHORTCUT_URL)

verify(mockUserEventsStore, never()).registerUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)
}

@Test
fun whenUserBrowsingPressesBackThenCannotAddBookmark() {
setupNavigation(skipHome = false, isBrowsing = true, canGoBack = false)
Expand Down Expand Up @@ -2057,6 +2145,127 @@ class BrowserTabViewModelTest {
testee.onUserSubmittedQuery("about:blank")
}

@Test
fun whenViewReadyIfDomainSameAsUseOurAppAfterNotificationSeenThenPixelSent() = coroutineRule.runBlocking {
givenUseOurAppSiteSelected()
whenever(mockNotificationDao.exists(UseOurAppNotification.ID)).thenReturn(true)

testee.onViewReady()

verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_NOTIFICATION)
}

@Test
fun whenViewReadyIfDomainSameAsUseOurAppAfterShortcutAddedThenPixelSent() = coroutineRule.runBlocking {
givenUseOurAppSiteSelected()
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)).thenReturn(UserEventEntity(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED))

testee.onViewReady()

verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_SHORTCUT)
}

@Test
fun whenViewReadyIfDomainSameAsUseOurAppAfterDeleteCtaShownThenPixelSent() = coroutineRule.runBlocking {
givenUseOurAppSiteSelected()
whenever(mockDismissedCtaDao.exists(CtaId.USE_OUR_APP_DELETION)).thenReturn(true)

testee.onViewReady()

verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_DELETE_CTA)
}

@Test
fun whenViewReadyIfDomainIsNotTheSameAsUseOurAppAfterNotificationSeenThenPixelNotSent() = coroutineRule.runBlocking {
givenUseOurAppSiteIsNotSelected()
whenever(mockNotificationDao.exists(UseOurAppNotification.ID)).thenReturn(true)

testee.onViewReady()

verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_NOTIFICATION)
}

@Test
fun whenViewReadyIfDomainIsNotTheSameAsUseOurAppAfterShortcutAddedThenPixelNotSent() = coroutineRule.runBlocking {
givenUseOurAppSiteIsNotSelected()
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)).thenReturn(UserEventEntity(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED))

testee.onViewReady()

verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_SHORTCUT)
}

@Test
fun whenViewReadyIfDomainIsNotTheSameAsUseOurAppAfterDeleteCtaShownThenPixelNotSent() = coroutineRule.runBlocking {
givenUseOurAppSiteIsNotSelected()
whenever(mockDismissedCtaDao.exists(CtaId.USE_OUR_APP_DELETION)).thenReturn(true)

testee.onViewReady()

verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_DELETE_CTA)
}

@Test
fun whenPageChangedIfPreviousOneWasNotUseOurAppSiteAfterNotificationSeenThenPixelSent() = coroutineRule.runBlocking {
givenUseOurAppSiteIsNotSelected()
whenever(mockNotificationDao.exists(UseOurAppNotification.ID)).thenReturn(true)

loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)

verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_NOTIFICATION)
}

@Test
fun whenPageChangedIfPreviousOneWasNotUseOurAppSiteAfterShortcutAddedThenPixelSent() = coroutineRule.runBlocking {
givenUseOurAppSiteIsNotSelected()
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)).thenReturn(UserEventEntity(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED))

loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)

verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_SHORTCUT)
}

@Test
fun whenPageChangedIfPreviousOneWasNotUseOurAppSiteAfterDeleteCtaShownThenPixelSent() = coroutineRule.runBlocking {
givenUseOurAppSiteIsNotSelected()
whenever(mockDismissedCtaDao.exists(CtaId.USE_OUR_APP_DELETION)).thenReturn(true)

loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)

verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_DELETE_CTA)
}

@Test
fun whenPageChangedIfPreviousOneWasUseOurAppSiteAfterNotificationSeenThenPixelNotSent() = coroutineRule.runBlocking {
givenUseOurAppSiteSelected()
whenever(mockNotificationDao.exists(UseOurAppNotification.ID)).thenReturn(true)

loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)

verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_NOTIFICATION)
}

@Test
fun whenPageChangedIfPreviousOneWasUseOurAppSiteAfterShortcutAddedThenPixelNotSent() = coroutineRule.runBlocking {
givenUseOurAppSiteSelected()
val timestampEntity = UserEventEntity(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)).thenReturn(timestampEntity)

loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)

verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_SHORTCUT)
}

@Test
fun whenPageChangedIfPreviousOneWasUseOurAppSiteThenAfterDeleteCtaShownPixelNotSent() = coroutineRule.runBlocking {
givenUseOurAppSiteSelected()
whenever(mockDismissedCtaDao.exists(CtaId.USE_OUR_APP_DELETION)).thenReturn(true)

loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)

verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_DELETE_CTA)
}

private inline fun <reified T : Command> assertCommandIssued(instanceAssertions: T.() -> Unit = {}) {
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
val issuedCommand = commandCaptor.allValues.find { it is T }
Expand Down Expand Up @@ -2096,6 +2305,26 @@ class BrowserTabViewModelTest {
testee.loadData("TAB_ID", "https://example.com", false)
}

private fun givenUseOurAppSiteSelected() {
whenever(mockOmnibarConverter.convertQueryToUrl(USE_OUR_APP_DOMAIN, null)).thenReturn(USE_OUR_APP_DOMAIN)
val site: Site = mock()
whenever(site.url).thenReturn(USE_OUR_APP_DOMAIN)
val siteLiveData = MutableLiveData<Site>()
siteLiveData.value = site
whenever(mockTabsRepository.retrieveSiteData("TAB_ID")).thenReturn(siteLiveData)
testee.loadData("TAB_ID", USE_OUR_APP_DOMAIN, false)
}

private fun givenUseOurAppSiteIsNotSelected() {
whenever(mockOmnibarConverter.convertQueryToUrl("example.com", null)).thenReturn("example.com")
val site: Site = mock()
whenever(site.url).thenReturn("example.com")
val siteLiveData = MutableLiveData<Site>()
siteLiveData.value = site
whenever(mockTabsRepository.retrieveSiteData("TAB_ID")).thenReturn(siteLiveData)
testee.loadData("TAB_ID", "example.com", false)
}

private fun givenFireproofWebsiteDomain(vararg fireproofWebsitesDomain: String) {
fireproofWebsitesDomain.forEach {
fireproofWebsiteDao.insert(FireproofWebsiteEntity(domain = it))
Expand Down
Loading