From 1ae8ae8e27a7e48b5f5e7b6ae88d16adb1872634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Sat, 9 Nov 2024 01:49:43 +0100 Subject: [PATCH] Refresh toast updates (#3552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/72649045549333/1207889813347128/f **Description**: We’re introducing a behavioral toast on macOS, which required moving logic from the iOS client into BSK. Some of this logic has beenupdated—specifically, we now only support an event for 3 consecutive refreshes, whereas previously, we had multiple events. --- Core/PixelEvent.swift | 14 +- Core/UserDefaultsPropertyWrapper.swift | 4 +- DuckDuckGo.xcodeproj/project.pbxproj | 66 ++++----- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppDependencyProvider.swift | 6 +- DuckDuckGo/AppPageRefreshMonitor.swift | 30 ++++ DuckDuckGo/BrokenSitePromptLimiter.swift | 106 -------------- DuckDuckGo/BrokenSitePromptLimiterStore.swift | 32 +++++ DuckDuckGo/BrokenSitePromptView.swift | 2 +- DuckDuckGo/MainViewController.swift | 29 ++-- DuckDuckGo/PageRefreshStore.swift | 29 ++++ DuckDuckGo/TabViewController.swift | 8 +- DuckDuckGo/UserBehaviorMonitor.swift | 132 ------------------ DuckDuckGo/UserText.swift | 2 +- DuckDuckGo/bg.lproj/Localizable.strings | 6 +- DuckDuckGo/cs.lproj/Localizable.strings | 6 +- DuckDuckGo/da.lproj/Localizable.strings | 6 +- DuckDuckGo/de.lproj/Localizable.strings | 6 +- DuckDuckGo/el.lproj/Localizable.strings | 6 +- DuckDuckGo/en.lproj/Localizable.strings | 6 +- DuckDuckGo/es.lproj/Localizable.strings | 6 +- DuckDuckGo/et.lproj/Localizable.strings | 6 +- DuckDuckGo/fi.lproj/Localizable.strings | 6 +- DuckDuckGo/fr.lproj/Localizable.strings | 6 +- DuckDuckGo/hr.lproj/Localizable.strings | 6 +- DuckDuckGo/hu.lproj/Localizable.strings | 6 +- DuckDuckGo/it.lproj/Localizable.strings | 6 +- DuckDuckGo/lt.lproj/Localizable.strings | 6 +- DuckDuckGo/lv.lproj/Localizable.strings | 6 +- DuckDuckGo/nb.lproj/Localizable.strings | 6 +- DuckDuckGo/nl.lproj/Localizable.strings | 6 +- DuckDuckGo/pl.lproj/Localizable.strings | 6 +- DuckDuckGo/pt.lproj/Localizable.strings | 6 +- DuckDuckGo/ro.lproj/Localizable.strings | 6 +- DuckDuckGo/ru.lproj/Localizable.strings | 6 +- DuckDuckGo/sk.lproj/Localizable.strings | 6 +- DuckDuckGo/sl.lproj/Localizable.strings | 6 +- DuckDuckGo/sv.lproj/Localizable.strings | 6 +- DuckDuckGo/tr.lproj/Localizable.strings | 6 +- .../BrokenSitePromptLimiterTests.swift | 82 ----------- .../UserBehaviorMonitorTests.swift | 124 ---------------- 41 files changed, 235 insertions(+), 585 deletions(-) create mode 100644 DuckDuckGo/AppPageRefreshMonitor.swift delete mode 100644 DuckDuckGo/BrokenSitePromptLimiter.swift create mode 100644 DuckDuckGo/BrokenSitePromptLimiterStore.swift create mode 100644 DuckDuckGo/PageRefreshStore.swift delete mode 100644 DuckDuckGo/UserBehaviorMonitor.swift delete mode 100644 DuckDuckGoTests/BrokenSitePromptLimiterTests.swift delete mode 100644 DuckDuckGoTests/UserBehaviorMonitorTests.swift diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index a19da94024..35429325fc 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -671,8 +671,10 @@ extension Pixel { case toggleReportDoNotSend case toggleReportDismiss - case userBehaviorReloadTwiceWithin12Seconds - case userBehaviorReloadThreeTimesWithin20Seconds + case pageRefreshThreeTimesWithin20Seconds + + case siteNotWorkingShown + case siteNotWorkingWebsiteIsBroken // MARK: History case historyStoreLoadFailed @@ -1494,9 +1496,11 @@ extension Pixel.Event { // MARK: - Apple Ad Attribution case .appleAdAttribution: return "m_apple-ad-attribution" - // MARK: - User behavior - case .userBehaviorReloadTwiceWithin12Seconds: return "m_reload-twice-within-12-seconds" - case .userBehaviorReloadThreeTimesWithin20Seconds: return "m_reload-three-times-within-20-seconds" + // MARK: - Page refresh toasts + case .pageRefreshThreeTimesWithin20Seconds: return "m_reload-three-times-within-20-seconds" + + case .siteNotWorkingShown: return "m_site-not-working_shown" + case .siteNotWorkingWebsiteIsBroken: return "m_site-not-working_website-is-broken" // MARK: - History debug case .historyStoreLoadFailed: return "m_debug_history-store-load-failed" diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 1231840c9b..eae3ee9842 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -143,9 +143,7 @@ public struct UserDefaultsWrapper { case appleAdAttributionReportCompleted = "com.duckduckgo.ios.appleAdAttributionReport.completed" - case didRefreshTimestamp = "com.duckduckgo.ios.userBehavior.didRefreshTimestamp" - case didDoubleRefreshTimestamp = "com.duckduckgo.ios.userBehavior.didDoubleRefreshTimestamp" - case didRefreshCounter = "com.duckduckgo.ios.userBehavior.didRefreshCounter" + case refreshTimestamps = "com.duckduckgo.ios.pageRefreshMonitor.refreshTimestamps" case lastBrokenSiteToastShownDate = "com.duckduckgo.ios.userBehavior.lastBrokenSiteToastShownDate" case toastDismissStreakCounter = "com.duckduckgo.ios.userBehavior.toastDismissStreakCounter" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6f52dde323..ea3eb46242 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -17,8 +17,7 @@ 026DABA428242BC80089E0B5 /* MockUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026DABA328242BC80089E0B5 /* MockUserAgent.swift */; }; 0283A1FC2C6E3D8100508FBD /* BrokenSitePromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283A1FA2C6E3D8100508FBD /* BrokenSitePromptView.swift */; }; 0283A1FE2C6E3E1B00508FBD /* BrokenSitePromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283A1FB2C6E3D8100508FBD /* BrokenSitePromptViewModel.swift */; }; - 0283A2012C6E46E300508FBD /* BrokenSitePromptLimiter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283A2002C6E46E300508FBD /* BrokenSitePromptLimiter.swift */; }; - 0283A2042C6E572F00508FBD /* BrokenSitePromptLimiterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283A2032C6E572F00508FBD /* BrokenSitePromptLimiterTests.swift */; }; + 0283A2012C6E46E300508FBD /* BrokenSitePromptLimiterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283A2002C6E46E300508FBD /* BrokenSitePromptLimiterStore.swift */; }; 02BA15B126A89ECA00472DD7 /* ios-config.json in Resources */ = {isa = PBXBuildFile; fileRef = 02BA15B026A89ECA00472DD7 /* ios-config.json */; }; 02CA904924F6BFE700D41DDF /* navigatorsharepatch.js in Resources */ = {isa = PBXBuildFile; fileRef = 02CA904824F6BFE700D41DDF /* navigatorsharepatch.js */; }; 02CA904B24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904A24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift */; }; @@ -940,12 +939,12 @@ CB2A7EEF283D185100885F67 /* RulesCompilationMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EEE283D185100885F67 /* RulesCompilationMonitor.swift */; }; CB2A7EF128410DF700885F67 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EF028410DF700885F67 /* PixelEvent.swift */; }; CB2A7EF4285383B300885F67 /* AppLastCompiledRulesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */; }; - CB48D3332B90CE9F00631D8B /* UserBehaviorMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */; }; - CB48D3372B90DF2000631D8B /* UserBehaviorMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */; }; + CB48D3332B90CE9F00631D8B /* PageRefreshStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */; }; CB4FA44E2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */; }; CB5516D0286500290079B175 /* TrackerRadarIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */; }; CB5516D1286500290079B175 /* ContentBlockingRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */; }; CB5516D2286500290079B175 /* AtbServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F21DBD21121147002631A6 /* AtbServerTests.swift */; }; + CB6CC7E42CD2529000320907 /* BrokenSitePrompt in Frameworks */ = {isa = PBXBuildFile; productRef = CB6CC7E32CD2529000320907 /* BrokenSitePrompt */; }; CB6D8E982C80A9B100D0E772 /* SpecialErrorPages in Frameworks */ = {isa = PBXBuildFile; productRef = CB6D8E972C80A9B100D0E772 /* SpecialErrorPages */; }; CB825C922C071B1400BCC586 /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB825C912C071B1400BCC586 /* AlertView.swift */; }; CB825C962C071C9300BCC586 /* AlertViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB825C952C071C9300BCC586 /* AlertViewPresenter.swift */; }; @@ -967,6 +966,8 @@ CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1AEFB02799AA940031AE3D /* SwiftUICollectionViewCell.swift */; }; CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DDE29A6736A00832877 /* APIHeadersTests.swift */; }; CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; + CBECDB6F2CD3DFBE005B8B87 /* PageRefreshMonitor in Frameworks */ = {isa = PBXBuildFile; productRef = CBECDB6E2CD3DFBE005B8B87 /* PageRefreshMonitor */; }; + CBECDB7A2CD981CE005B8B87 /* AppPageRefreshMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBECDB792CD981C6005B8B87 /* AppPageRefreshMonitor.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; CBFCB30E2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */; }; D60170BD2BA34CE8001911B5 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60170BB2BA32DD6001911B5 /* Subscription.swift */; }; @@ -1335,8 +1336,7 @@ 026DABA328242BC80089E0B5 /* MockUserAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserAgent.swift; sourceTree = ""; }; 0283A1FA2C6E3D8100508FBD /* BrokenSitePromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptView.swift; sourceTree = ""; }; 0283A1FB2C6E3D8100508FBD /* BrokenSitePromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptViewModel.swift; sourceTree = ""; }; - 0283A2002C6E46E300508FBD /* BrokenSitePromptLimiter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptLimiter.swift; sourceTree = ""; }; - 0283A2032C6E572F00508FBD /* BrokenSitePromptLimiterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptLimiterTests.swift; sourceTree = ""; }; + 0283A2002C6E46E300508FBD /* BrokenSitePromptLimiterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptLimiterStore.swift; sourceTree = ""; }; 02BA15B026A89ECA00472DD7 /* ios-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "ios-config.json"; sourceTree = ""; }; 02C4BC3127C3F9B600C40026 /* AppPrivacyConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPrivacyConfigurationTests.swift; sourceTree = ""; }; 02CA904824F6BFE700D41DDF /* navigatorsharepatch.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = navigatorsharepatch.js; sourceTree = ""; }; @@ -2743,8 +2743,7 @@ CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLastCompiledRulesStore.swift; sourceTree = ""; }; CB2C47822AF6D55800AEDCD9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; CB4448752AF6D51D001F93F7 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/InfoPlist.strings; sourceTree = ""; }; - CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserBehaviorMonitor.swift; sourceTree = ""; }; - CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBehaviorMonitorTests.swift; sourceTree = ""; }; + CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageRefreshStore.swift; sourceTree = ""; }; CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageUserScript.swift; sourceTree = ""; }; CB5038622AF6D563007FD69F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; CB6ABD002AF6D52B004A8224 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -2775,6 +2774,7 @@ CBDD5DE029A6741300832877 /* MockBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockBundle.swift; sourceTree = ""; }; CBE099292AF6D54D000EFC47 /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/InfoPlist.strings; sourceTree = ""; }; CBECB27B2AF6D58D006960FA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; + CBECDB792CD981C6005B8B87 /* AppPageRefreshMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPageRefreshMonitor.swift; sourceTree = ""; }; CBEF49902AF6D50600BFBD7D /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = ""; }; CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CriticalAlerts.swift; sourceTree = ""; }; CBF0FA762AF6D4D800FB1C5B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -3148,6 +3148,7 @@ 98A50962294B48A400D10880 /* Bookmarks in Frameworks */, 1E60989B290009C700A508F9 /* Common in Frameworks */, 1E60989D290011E600A508F9 /* ContentBlocking in Frameworks */, + CB6CC7E42CD2529000320907 /* BrokenSitePrompt in Frameworks */, F486D33425069BBB002D07D7 /* Kingfisher in Frameworks */, EE8E568A2A56BCE400F11DCA /* NetworkProtection in Frameworks */, D6BC8ACB2C5AA3860025375B /* DuckPlayer in Frameworks */, @@ -3160,6 +3161,7 @@ 851481882A600EFC00ABC65F /* RemoteMessaging in Frameworks */, 37DF000C29F9CA80002B7D3E /* SyncDataProviders in Frameworks */, 1E6098A1290011E600A508F9 /* UserScript in Frameworks */, + CBECDB6F2CD3DFBE005B8B87 /* PageRefreshMonitor in Frameworks */, D61CDA162B7CF77300A0FBB9 /* Subscription in Frameworks */, 858D009D2B9799FC004E5B4C /* History in Frameworks */, C14882ED27F211A000D59F0C /* SwiftSoup in Frameworks */, @@ -3198,15 +3200,7 @@ children = ( 0283A1FA2C6E3D8100508FBD /* BrokenSitePromptView.swift */, 0283A1FB2C6E3D8100508FBD /* BrokenSitePromptViewModel.swift */, - 0283A2002C6E46E300508FBD /* BrokenSitePromptLimiter.swift */, - ); - name = BrokenSitePrompt; - sourceTree = ""; - }; - 0283A2022C6E56F400508FBD /* BrokenSitePrompt */ = { - isa = PBXGroup; - children = ( - 0283A2032C6E572F00508FBD /* BrokenSitePromptLimiterTests.swift */, + 0283A2002C6E46E300508FBD /* BrokenSitePromptLimiterStore.swift */, ); name = BrokenSitePrompt; sourceTree = ""; @@ -4256,7 +4250,7 @@ 98F3A1D6217B36EE0011A0D4 /* Themes */, 7BF78E002CA2CC100026A1FC /* TipKit */, F11CEF581EBB66C80088E4D7 /* Tutorials */, - CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */, + CB48D32F2B90CE8500631D8B /* PageRefreshMonitor */, F1D796ED1E7AE4090019D451 /* UserInterface */, 84E341E31E2FC0E400BDBA6F /* UserInterfaceResources */, 3151F0E827357F8F00226F58 /* VoiceSearch */, @@ -5289,20 +5283,13 @@ path = Configuration; sourceTree = ""; }; - CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */ = { + CB48D32F2B90CE8500631D8B /* PageRefreshMonitor */ = { isa = PBXGroup; children = ( - CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */, + CBECDB792CD981C6005B8B87 /* AppPageRefreshMonitor.swift */, + CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */, ); - name = UserBehaviorMonitor; - sourceTree = ""; - }; - CB48D3342B90CEBD00631D8B /* UserBehaviorMonitor */ = { - isa = PBXGroup; - children = ( - CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */, - ); - name = UserBehaviorMonitor; + name = PageRefreshMonitor; sourceTree = ""; }; CBAA195627BFDD9800A4BD49 /* SmarterEncryption */ = { @@ -5765,7 +5752,6 @@ 981FED7222045FFA008488D7 /* AutoClear */, 1E1D8B5B2994FF7800C96994 /* Autoconsent */, F40F843228C92B1C0081AE75 /* Autofill */, - 0283A2022C6E56F400508FBD /* BrokenSitePrompt */, 98559FD0267099F400A83094 /* ContentBlocker */, 31C138A127A334F600FFD4B2 /* Downloads */, D62EC3B72C24695800FC9D04 /* DuckPlayer */, @@ -5781,7 +5767,6 @@ F13B4BF71F18C9E800814661 /* Tabs */, 98EA2C3A218B9A880023E1DC /* Themes */, F12790DD1EBBDDF3001D3AEC /* Tutorials */, - CB48D3342B90CEBD00631D8B /* UserBehaviorMonitor */, F194FAF91F14E605009B4DF8 /* UserInterface */, 317045BE2858C69A0016ED1F /* Utils */, 4B6484F927FFCF520050A7A1 /* Waitlist */, @@ -6792,6 +6777,8 @@ 851F74252B9A1BFD00747C42 /* Suggestions */, D6BC8ACA2C5AA3860025375B /* DuckPlayer */, CB6D8E972C80A9B100D0E772 /* SpecialErrorPages */, + CB6CC7E32CD2529000320907 /* BrokenSitePrompt */, + CBECDB6E2CD3DFBE005B8B87 /* PageRefreshMonitor */, ); productName = Core; productReference = F143C2E41E4A4CD400CFDE3A /* Core.framework */; @@ -7626,6 +7613,7 @@ CB4FA44E2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift in Sources */, EE0153ED2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift in Sources */, EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */, + CBECDB7A2CD981CE005B8B87 /* AppPageRefreshMonitor.swift in Sources */, B60DFF072872B64B0061E7C2 /* JSAlertController.swift in Sources */, 981FED6E22025151008488D7 /* BlankSnapshotViewController.swift in Sources */, D66F683D2BB333C100AE93E2 /* SubscriptionContainerView.swift in Sources */, @@ -7775,7 +7763,7 @@ 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */, 310C4B47281B60E300BA79A9 /* AutofillLoginDetailsViewModel.swift in Sources */, 85EE7F572246685B000FE757 /* WebContainerViewController.swift in Sources */, - CB48D3332B90CE9F00631D8B /* UserBehaviorMonitor.swift in Sources */, + CB48D3332B90CE9F00631D8B /* PageRefreshStore.swift in Sources */, 1EC458462948932500CB2B13 /* UIHostingControllerExtension.swift in Sources */, 1E4DCF4E27B6A69600961E25 /* DownloadsListHostingController.swift in Sources */, 850F93DB2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift in Sources */, @@ -7825,7 +7813,7 @@ C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */, 851624C72B96389D002D5CD7 /* HistoryDebugViewController.swift in Sources */, 8540BBA22440857A00017FE4 /* PreserveLoginsWorker.swift in Sources */, - 0283A2012C6E46E300508FBD /* BrokenSitePromptLimiter.swift in Sources */, + 0283A2012C6E46E300508FBD /* BrokenSitePromptLimiterStore.swift in Sources */, 85DFEDF924CF3D0E00973FE7 /* TabsBarCell.swift in Sources */, 851672D32BED23FE00592F24 /* AutocompleteViewModel.swift in Sources */, 8562CE152B9B645C00E1D399 /* CachedBookmarkSuggestions.swift in Sources */, @@ -8045,7 +8033,6 @@ 6F934F862C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift in Sources */, 987130C5294AAB9F00AB05E0 /* BookmarkEditorViewModelTests.swift in Sources */, BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */, - 0283A2042C6E572F00508FBD /* BrokenSitePromptLimiterTests.swift in Sources */, 9F8E0F332CCA642D001EA7C5 /* VideoPlayerViewModelTests.swift in Sources */, D62EC3BA2C246A7000FC9D04 /* YoutublePlayerNavigationHandlerTests.swift in Sources */, 1EAABE712C99FC75003F5137 /* SubscriptionFeatureAvailabilityMock.swift in Sources */, @@ -8065,7 +8052,6 @@ 9F5E5AB22C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift in Sources */, 9F4CC5172C48B8D4006A96EB /* TabViewControllerDaxDialogTests.swift in Sources */, F13B4BFB1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift in Sources */, - CB48D3372B90DF2000631D8B /* UserBehaviorMonitorTests.swift in Sources */, 8528AE7E212EF5FF00D0BD74 /* AppRatingPromptTests.swift in Sources */, 981FED692201FE69008488D7 /* AutoClearSettingsScreenTests.swift in Sources */, 9F9EE4CE2C377D4900D4118E /* OnboardingFirePixelMock.swift in Sources */, @@ -10986,7 +10972,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 207.1.0; + version = 208.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { @@ -11211,6 +11197,10 @@ package = C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; + CB6CC7E32CD2529000320907 /* BrokenSitePrompt */ = { + isa = XCSwiftPackageProductDependency; + productName = BrokenSitePrompt; + }; CB6D8E972C80A9B100D0E772 /* SpecialErrorPages */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -11226,6 +11216,10 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Configuration; }; + CBECDB6E2CD3DFBE005B8B87 /* PageRefreshMonitor */ = { + isa = XCSwiftPackageProductDependency; + productName = PageRefreshMonitor; + }; D61CDA152B7CF77300A0FBB9 /* Subscription */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 71287414b2..49d5d017ea 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "26cc3c597990db8a0f8aa4be743b25ce65076c95", - "version" : "207.1.0" + "revision" : "17154907fe86c75942331ed6d037694c666ddd95", + "version" : "208.0.0" } }, { diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 4997f73e56..35d7bf45d0 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -26,6 +26,7 @@ import Subscription import Common import NetworkProtection import RemoteMessaging +import PageRefreshMonitor protocol DependencyProvider { @@ -39,7 +40,7 @@ protocol DependencyProvider { var autofillNeverPromptWebsitesManager: AutofillNeverPromptWebsitesManager { get } var configurationManager: ConfigurationManager { get } var configurationStore: ConfigurationStore { get } - var userBehaviorMonitor: UserBehaviorMonitor { get } + var pageRefreshMonitor: PageRefreshMonitor { get } var subscriptionManager: SubscriptionManager { get } var accountManager: AccountManager { get } var vpnFeatureVisibility: DefaultNetworkProtectionVisibility { get } @@ -72,7 +73,8 @@ final class AppDependencyProvider: DependencyProvider { let configurationManager: ConfigurationManager let configurationStore = ConfigurationStore() - let userBehaviorMonitor = UserBehaviorMonitor() + let pageRefreshMonitor = PageRefreshMonitor(onDidDetectRefreshPattern: PageRefreshMonitor.onDidDetectRefreshPattern, + store: PageRefreshStore()) // Subscription let subscriptionManager: SubscriptionManager diff --git a/DuckDuckGo/AppPageRefreshMonitor.swift b/DuckDuckGo/AppPageRefreshMonitor.swift new file mode 100644 index 0000000000..7152b57f75 --- /dev/null +++ b/DuckDuckGo/AppPageRefreshMonitor.swift @@ -0,0 +1,30 @@ +// +// AppPageRefreshMonitor.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Core +import Common +import PageRefreshMonitor + +extension PageRefreshMonitor { + + static let onDidDetectRefreshPattern: () -> Void = { + Pixel.fire(pixel: .pageRefreshThreeTimesWithin20Seconds) + } + +} diff --git a/DuckDuckGo/BrokenSitePromptLimiter.swift b/DuckDuckGo/BrokenSitePromptLimiter.swift deleted file mode 100644 index 69b962ab13..0000000000 --- a/DuckDuckGo/BrokenSitePromptLimiter.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// BrokenSitePromptLimiter.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Core -import BrowserServicesKit - -protocol BrokenSitePromptLimiterStoring { - var lastToastShownDate: Date { get set } - var toastDismissStreakCounter: Int { get set } -} - -final class BrokenSitePromptLimiterStore: BrokenSitePromptLimiterStoring { - @UserDefaultsWrapper(key: .lastBrokenSiteToastShownDate, defaultValue: .distantPast) - var lastToastShownDate: Date - - @UserDefaultsWrapper(key: .toastDismissStreakCounter, defaultValue: 0) - var toastDismissStreakCounter: Int -} - -final class BrokenSitePromptLimiter { - - struct BrokenSitePromptLimiterSettings: Codable { - let maxDismissStreak: Int - let dismissStreakResetDays: Int - let coolDownDays: Int - } - - private var lastToastShownDate: Date { - get { store.lastToastShownDate } - set { store.lastToastShownDate = newValue } - } - - private var toastDismissStreakCounter: Int { - get { store.toastDismissStreakCounter } - set { store.toastDismissStreakCounter = newValue } - } - - private var privacyConfigManager: PrivacyConfigurationManaging - private var store: BrokenSitePromptLimiterStoring - - init(privacyConfigManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, - store: BrokenSitePromptLimiterStoring = BrokenSitePromptLimiterStore()) { - self.privacyConfigManager = privacyConfigManager - self.store = store - } - - private func getSettingsFromConfig() -> BrokenSitePromptLimiterSettings { - let settings = privacyConfigManager.privacyConfig.settings(for: .brokenSitePrompt) - - // Get settings from config or fallback to standard defaults - return BrokenSitePromptLimiterSettings( - maxDismissStreak: settings["maxDismissStreak"] as? Int ?? 3, - dismissStreakResetDays: settings["dismissStreakResetDays"] as? Int ?? 30, - coolDownDays: settings["coolDownDays"] as? Int ?? 7 - ) - } - - /// If it has been `dismissStreakResetDays` or more since the last time we showed the prompt, reset the dismiss counter to 0 - private func resetDismissStreakIfNeeded(dismissStreakResetDays: Int) { - if !lastToastShownDate.isLessThan(daysAgo: dismissStreakResetDays) { - toastDismissStreakCounter = 0 - } - } - - public func shouldShowToast() -> Bool { - guard privacyConfigManager.privacyConfig.isEnabled(featureKey: .brokenSitePrompt) else { return false } - - let settings = getSettingsFromConfig() - - resetDismissStreakIfNeeded(dismissStreakResetDays: settings.dismissStreakResetDays) - guard toastDismissStreakCounter < settings.maxDismissStreak else { return false } // Don't show the toast if the user dismissed it more than `maxDismissStreak` times in a row - guard !lastToastShownDate.isLessThan(daysAgo: settings.coolDownDays) else { return false } // Only show the toast once per `coolDownDays` days - - return true - } - - public func didShowToast() { - lastToastShownDate = Date() - } - - public func didDismissToast() { - toastDismissStreakCounter += 1 - } - - public func didOpenReport() { - toastDismissStreakCounter = 0 - } - -} diff --git a/DuckDuckGo/BrokenSitePromptLimiterStore.swift b/DuckDuckGo/BrokenSitePromptLimiterStore.swift new file mode 100644 index 0000000000..9236ee3f0e --- /dev/null +++ b/DuckDuckGo/BrokenSitePromptLimiterStore.swift @@ -0,0 +1,32 @@ +// +// BrokenSitePromptLimiterStore.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import BrokenSitePrompt +import Core + +final class BrokenSitePromptLimiterStore: BrokenSitePromptLimiterStoring { + + @UserDefaultsWrapper(key: .lastBrokenSiteToastShownDate, defaultValue: .distantPast) + var lastToastShownDate: Date + + @UserDefaultsWrapper(key: .toastDismissStreakCounter, defaultValue: 0) + var toastDismissStreakCounter: Int + +} diff --git a/DuckDuckGo/BrokenSitePromptView.swift b/DuckDuckGo/BrokenSitePromptView.swift index b04b06a778..2f89e67dce 100644 --- a/DuckDuckGo/BrokenSitePromptView.swift +++ b/DuckDuckGo/BrokenSitePromptView.swift @@ -39,7 +39,7 @@ struct BrokenSitePromptView: View { Button(UserText.siteNotWorkingDismiss, action: viewModel.onDidDismiss) .buttonStyle(GhostButtonStyle()) .fixedSize() - Button(UserText.siteNotWorkingWebsiteIsBroken, action: viewModel.onDidSubmit) + Button(UserText.siteNotWorkingReportBrokenSite, action: viewModel.onDidSubmit) .buttonStyle(PrimaryButtonStyle(compact: true)) .fixedSize() } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 9726c00630..de806d9737 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -36,6 +36,8 @@ import SwiftUI import NetworkProtection import Onboarding import os.log +import PageRefreshMonitor +import BrokenSitePrompt class MainViewController: UIViewController { @@ -301,7 +303,7 @@ class MainViewController: UIViewController { findInPageView.delegate = self findInPageBottomLayoutConstraint.constant = 0 registerForKeyboardNotifications() - registerForUserBehaviorEvents() + registerForPageRefreshPatterns() registerForSyncFeatureFlagsUpdates() decorate() @@ -497,11 +499,11 @@ class MainViewController: UIViewController { keyboardShowing = false } - private func registerForUserBehaviorEvents() { + private func registerForPageRefreshPatterns() { NotificationCenter.default.addObserver( self, selector: #selector(attemptToShowBrokenSitePrompt(_:)), - name: .userBehaviorDidMatchBrokenSiteCriteria, + name: .pageRefreshMonitorDidDetectRefreshPattern, object: nil) } @@ -1095,8 +1097,7 @@ class MainViewController: UIViewController { } private func hideNotificationBarIfBrokenSitePromptShown(afterRefresh: Bool = false) { - guard brokenSitePromptViewHostingController != nil, - let event = brokenSitePromptEvent?.rawValue else { return } + guard brokenSitePromptViewHostingController != nil else { return } brokenSitePromptViewHostingController = nil hideNotification() } @@ -1341,12 +1342,11 @@ class MainViewController: UIViewController { } private var brokenSitePromptViewHostingController: UIHostingController? - private var brokenSitePromptEvent: UserBehaviorEvent? - lazy private var brokenSitePromptLimiter = BrokenSitePromptLimiter() + lazy private var brokenSitePromptLimiter = BrokenSitePromptLimiter(privacyConfigManager: ContentBlocking.shared.privacyConfigurationManager, + store: BrokenSitePromptLimiterStore()) @objc func attemptToShowBrokenSitePrompt(_ notification: Notification) { guard brokenSitePromptLimiter.shouldShowToast(), - let event = notification.userInfo?[UserBehaviorEvent.Key.event] as? UserBehaviorEvent, let url = currentTab?.url, !url.isDuckDuckGo, notificationView == nil, !isPad, @@ -1356,18 +1356,18 @@ class MainViewController: UIViewController { // We're using async to ensure the view dismissal happens on the first runloop after a refresh. This prevents the scenario where the view briefly appears and then immediately disappears after a refresh. brokenSitePromptLimiter.didShowToast() DispatchQueue.main.async { - self.showBrokenSitePrompt(after: event) + self.showBrokenSitePrompt() } } - private func showBrokenSitePrompt(after event: UserBehaviorEvent) { - let host = makeBrokenSitePromptViewHostingController(event: event) + private func showBrokenSitePrompt() { + let host = makeBrokenSitePromptViewHostingController() brokenSitePromptViewHostingController = host - brokenSitePromptEvent = event + Pixel.fire(pixel: .siteNotWorkingShown) showNotification(with: host.view) } - private func makeBrokenSitePromptViewHostingController(event: UserBehaviorEvent) -> UIHostingController { + private func makeBrokenSitePromptViewHostingController() -> UIHostingController { let viewModel = BrokenSitePromptViewModel(onDidDismiss: { [weak self] in Task { @MainActor in self?.hideNotification() @@ -1376,10 +1376,11 @@ class MainViewController: UIViewController { } }, onDidSubmit: { [weak self] in Task { @MainActor in - self?.segueToReportBrokenSite(entryPoint: .prompt(event.rawValue)) + self?.segueToReportBrokenSite(entryPoint: .prompt) self?.hideNotification() self?.brokenSitePromptLimiter.didOpenReport() self?.brokenSitePromptViewHostingController = nil + Pixel.fire(pixel: .siteNotWorkingWebsiteIsBroken) } }) return UIHostingController(rootView: BrokenSitePromptView(viewModel: viewModel), ignoreSafeArea: true) diff --git a/DuckDuckGo/PageRefreshStore.swift b/DuckDuckGo/PageRefreshStore.swift new file mode 100644 index 0000000000..2f0a0baad9 --- /dev/null +++ b/DuckDuckGo/PageRefreshStore.swift @@ -0,0 +1,29 @@ +// +// PageRefreshStore.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Core +import PageRefreshMonitor + +final class PageRefreshStore: PageRefreshStoring { + + @UserDefaultsWrapper(key: .refreshTimestamps, defaultValue: []) + var refreshTimestamps: [Date] + +} diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 94a89dec38..6be661152c 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -621,7 +621,9 @@ class TabViewController: UIViewController { reload() delegate?.tabDidRequestRefresh(tab: self) Pixel.fire(pixel: .pullToRefresh) - AppDependencyProvider.shared.userBehaviorMonitor.handleRefreshAction() + if let url = webView.url { + AppDependencyProvider.shared.pageRefreshMonitor.register(for: url) + } }, for: .valueChanged) refreshControl.backgroundColor = .systemBackground @@ -2473,7 +2475,9 @@ extension TabViewController: UIGestureRecognizerDelegate { } refreshCountSinceLoad += 1 - AppDependencyProvider.shared.userBehaviorMonitor.handleRefreshAction() + if let url { + AppDependencyProvider.shared.pageRefreshMonitor.register(for: url) + } } } diff --git a/DuckDuckGo/UserBehaviorMonitor.swift b/DuckDuckGo/UserBehaviorMonitor.swift deleted file mode 100644 index 4d905cbdd5..0000000000 --- a/DuckDuckGo/UserBehaviorMonitor.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// UserBehaviorMonitor.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Common -import Core - -public extension Notification.Name { - - static let userBehaviorDidMatchBrokenSiteCriteria = Notification.Name("com.duckduckgo.app.userBehaviorDidMatchBrokenSiteCriteria") - -} - -public enum UserBehaviorEvent: String { - - public enum Key { - - static let event = "com.duckduckgo.com.userBehaviorEvent.key" - - } - - case reloadTwiceWithin12Seconds = "reload-twice-within-12-seconds" - case reloadThreeTimesWithin20Seconds = "reload-three-times-within-20-seconds" - -} - -protocol UserBehaviorStoring { - - var didRefreshTimestamp: Date? { get set } - var didDoubleRefreshTimestamp: Date? { get set } - var didRefreshCounter: Int { get set } - -} - -final class UserBehaviorStore: UserBehaviorStoring { - - @UserDefaultsWrapper(key: .didRefreshTimestamp, defaultValue: .distantPast) - var didRefreshTimestamp: Date? - - @UserDefaultsWrapper(key: .didDoubleRefreshTimestamp, defaultValue: .distantPast) - var didDoubleRefreshTimestamp: Date? - - @UserDefaultsWrapper(key: .didRefreshCounter, defaultValue: 0) - var didRefreshCounter: Int - -} - -final class UserBehaviorMonitor { - - enum Action: Equatable { - - case refresh - - } - - private let eventMapping: EventMapping - private var store: UserBehaviorStoring - - init(eventMapping: EventMapping = AppUserBehaviorMonitor.eventMapping, - store: UserBehaviorStoring = UserBehaviorStore()) { - self.eventMapping = eventMapping - self.store = store - } - - var didRefreshTimestamp: Date? { - get { store.didRefreshTimestamp } - set { store.didRefreshTimestamp = newValue } - } - - var didDoubleRefreshTimestamp: Date? { - get { store.didDoubleRefreshTimestamp } - set { store.didDoubleRefreshTimestamp = newValue } - } - - var didRefreshCounter: Int { - get { store.didRefreshCounter } - set { store.didRefreshCounter = newValue } - } - - func handleRefreshAction(date: Date = Date()) { - fireEventIfActionOccurredRecently(within: 12.0, since: didRefreshTimestamp, eventToFire: .reloadTwiceWithin12Seconds) - didRefreshTimestamp = date - - if didRefreshCounter == 0 { - didDoubleRefreshTimestamp = date - } - didRefreshCounter += 1 - if didRefreshCounter > 2 { - fireEventIfActionOccurredRecently(within: 20.0, since: didDoubleRefreshTimestamp, eventToFire: .reloadThreeTimesWithin20Seconds) - didRefreshCounter = 0 - } - - func fireEventIfActionOccurredRecently(within interval: Double = 30.0, since timestamp: Date?, eventToFire: UserBehaviorEvent) { - if let timestamp = timestamp, date.timeIntervalSince(timestamp) < interval { - eventMapping.fire(eventToFire) - NotificationCenter.default.post(name: .userBehaviorDidMatchBrokenSiteCriteria, - object: self, - userInfo: [UserBehaviorEvent.Key.event: eventToFire]) - } - } - } - -} - -final class AppUserBehaviorMonitor { - - static let eventMapping = EventMapping { event, _, _, _ in - let domainEvent: Pixel.Event - switch event { - case .reloadTwiceWithin12Seconds: domainEvent = .userBehaviorReloadTwiceWithin12Seconds - case .reloadThreeTimesWithin20Seconds: domainEvent = .userBehaviorReloadThreeTimesWithin20Seconds - } - Pixel.fire(pixel: domainEvent) - } - -} diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index b32b0c035f..c526e40a7d 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1258,7 +1258,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let siteNotWorkingTitle = NSLocalizedString("site.not.working.title", value: "Site not working? Let DuckDuckGo know.", comment: "Prompt asking user to send report to us if we suspect site may be broken") public static let siteNotWorkingSubtitle = NSLocalizedString("site.not.working.subtitle", value: "This helps us improve the browser.", comment: "Prompt asking user to send report to us if we suspect site may be broken") public static let siteNotWorkingDismiss = NSLocalizedString("site.not.working.dismiss", value: "Dismiss", comment: "Dismiss button") - public static let siteNotWorkingWebsiteIsBroken = NSLocalizedString("site.not.working.website.is.broken", value: "Website Is Broken", comment: "Button that triggers flow to report broken site") + public static let siteNotWorkingReportBrokenSite = NSLocalizedString("site.not.working.report.broken.site", value: "Report Broken Site", comment: "Button that triggers flow to report broken site") public static let siteNotWorkingDescription = NSLocalizedString("site.not.working.description", value: "Select the option that best describes the problem you experienced.", comment: "Description on a report broken site page.") // Broken site report experiment diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index fe5c65f295..df72ca960f 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -2255,15 +2255,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Отказване"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Подаване на сигнал за повреден сайт"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Това ни помага да подобрим браузъра."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Сайтът не работи ли? Уведомете DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Уебсайтът е повреден"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Изпращане на доклад"; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 45df7910f4..86a9a53eb4 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Odmítnout"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Nahlásit nefunkční webové stránky"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Pomáhá nám to vylepšovat prohlížeč."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Stránka nefunguje? Dej DuckDuckGo vědět."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Web nefunguje"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Odešlete zprávu"; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 382ee89485..3e89b7a158 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Afvis"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Rapporter ødelagt websted"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Det hjælper os med at forbedre browseren."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Virker webstedet ikke? Fortæl DuckDuckGo det."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Hvilket websted er ødelagt?"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Indsend rapport"; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 5344dede4c..5f893a3166 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Verwerfen"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Fehlerhafte Website melden"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Das hilft uns, den Browser zu verbessern."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Webseite funktioniert nicht? Sag DuckDuckGo Bescheid."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Website ist fehlerhaft"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Bericht senden"; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 70c05970bd..5df4692edf 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Απόρριψη"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Αναφορά ιστότοπου που δεν λειτουργεί"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Αυτό μας βοηθά να βελτιώνουμε το πρόγραμμα περιήγησης."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Ο ιστότοπος δεν λειτουργεί; Ενημερώστε σχετικά το DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Ο ιστότοπος δεν λειτουργεί"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Υποβολή αναφοράς"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 98f79b49e5..4ab56d6e1f 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2409,15 +2409,15 @@ But if you *do* want a peek under the hood, you can find more information about /* Dismiss button */ "site.not.working.dismiss" = "Dismiss"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Report Broken Site"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "This helps us improve the browser."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Site not working? Let DuckDuckGo know."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Website Is Broken"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Submit Report"; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index 0b7bd2e9e0..b7dff0c463 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Descartar"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Informar de sitio web dañado"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Esto nos ayuda a mejorar el navegador."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "¿El sitio no funciona? Házselo saber a DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "El sitio web no funciona"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Enviar informe"; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index 38a5acaea0..b1376a0ab7 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Loobu"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Teata mittetoimivast saidist"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "See aitab meil brauserit täiustada."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Kas sait ei tööta? Anna DuckDuckGole teada."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Veebisait ei toimi"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Saada aruanne"; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index d0c31a4164..c745173f5a 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Hylkää"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Ilmoita viallisesta sivustosta"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Tämä auttaa meitä parantamaan selainta."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Eikö sivusto toimi? Kerro siitä DuckDuckGolle."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Verkkosivusto on viallinen"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Lähetä raportti"; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index f900fa4815..970ec2d762 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Ignorer"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Signaler un problème de site"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Cela nous aide à améliorer le navigateur."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Le site ne fonctionne pas ? Faites-le savoir à DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Le site Web pose problème"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Envoyer le rapport"; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index 269e54b844..1e91cfa4d0 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Odbaci"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Prijavi neispravno web-mjesto"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Ovo nam pomaže da poboljšamo preglednik."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Web-mjesto ne funkcionira? Javi DuckDuckGou."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Web-mjesto nije ispravno"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Pošalji izvješće"; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index 52618f6a75..6e011c1d33 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Elutasítás"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Hibás weboldal jelentése"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Ez segít minket a böngésző tökéletesítésében."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Nem működik a webhely? Jelezd a DuckDuckGo felé."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Nem működik a webhely"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Jelentés beküldése"; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index e01a9d279f..aa975129cd 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Ignora"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Segnala sito danneggiato"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Questo ci aiuta a migliorare il browser."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Il sito non funziona? Comunicalo a DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Il sito web è danneggiato"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Invia segnalazione"; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index c116362c61..aefc629f07 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Atmesti"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Pranešti apie sugadintą svetainę"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Tai padeda mums tobulinti naršyklę."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Svetainė neveikia? Praneškite „DuckDuckGo“."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Svetainė neveikia"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Pateikti ataskaitą"; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index 8f9c980cc1..53a83ff1b3 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Nerādīt"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Ziņot par bojātu vietni"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Tas mums palīdz uzlabot pārlūkprogrammu."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Vai vietne nedarbojas? Informē DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Vietne ir bojāta"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Iesniegt ziņojumu"; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index cd0ded480c..5c75a162e9 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Avvis"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Rapporter nettstedfeil"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Dette hjelper oss med å forbedre nettleseren."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Fungerer ikke nettstedet? Gi beskjed til DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Nettstedet fungerer ikke"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Send inn rapport"; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 0ae46ad07e..49b2ffc582 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Negeren"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Defecte website melden"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Hiermee kunnen we de browser verbeteren."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Werkt de site niet? Laat het DuckDuckGo weten."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "De website werkt niet"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Rapport versturen"; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 9888d7bfe0..a82a482b04 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Odrzuć"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Zgłoś uszkodzoną witrynę"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "To pomaga nam ulepszyć przeglądarkę."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Witryna nie działa? Poinformuj DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Witryna nie działa poprawnie"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Prześlij raport"; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 7fd8ef149b..fbef6e16e2 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Ignorar"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Denunciar site danificado"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Isto ajuda-nos a melhorar o navegador."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "O site não funciona? Informa o DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "O site não funciona"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Submeter relatório"; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index ee562a26d3..a0d15fec4e 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Renunță"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Raportați site-ul defect"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Acest lucru ne ajută să îmbunătățim browserul."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Site-ul nu funcționează? Anunță DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Site-ul este nefuncțional"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Trimite raportul"; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index 5d041fb58f..35e1e8e6da 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Отклонить"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Сообщить о неработающем сайте"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Ваш ответ поможет нам улучшить работу браузера."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Сайт не работает? Сообщите в DuckDuckGo!"; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Проблема на сайте"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Отправить жалобу"; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 43c3005757..e3ef7a8a83 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Odmietnuť"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Nahlásiť nefunkčnú stránku"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Toto nám pomáha vylepšiť prehliadač."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Stránka nefunguje? Oznámte to DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Webová stránka je nefunkčná"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Odoslať správu"; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 088ce79181..08fd2dde74 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Opusti"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Prijavi nedelujočo stran"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "To nam pomaga izboljšati brskalnik."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Stran ne deluje? Sporočite DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Spletna stran je nedelujoča"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Pošlji poročilo"; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index de228cd093..8b073384b5 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Avvisa"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Rapportera skadad webbplats"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Det hjälper oss att förbättra webbläsaren."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Fungerar inte webbplatsen? Informera DuckDuckGo."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Webbplatsen fungerar inte"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Skicka in rapport"; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 5b967dc65a..abaf73d410 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -2253,15 +2253,15 @@ /* Dismiss button */ "site.not.working.dismiss" = "Reddet"; +/* Button that triggers flow to report broken site */ +"site.not.working.report.broken.site" = "Hatalı Siteyi Bildir"; + /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.subtitle" = "Bu gibi bildirimler tarayıcıyı geliştirmemize yardımcı olur."; /* Prompt asking user to send report to us if we suspect site may be broken */ "site.not.working.title" = "Site çalışmıyor mu? DuckDuckGo'ya bildirin."; -/* Button that triggers flow to report broken site */ -"site.not.working.website.is.broken" = "Web Sitesi Bozuk"; - /* Report a Broken Site screen confirmation button */ "siteFeedback.buttonText" = "Rapor Gönder"; diff --git a/DuckDuckGoTests/BrokenSitePromptLimiterTests.swift b/DuckDuckGoTests/BrokenSitePromptLimiterTests.swift deleted file mode 100644 index c83836915c..0000000000 --- a/DuckDuckGoTests/BrokenSitePromptLimiterTests.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// BrokenSitePromptLimiterTests.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import XCTest -import BrowserServicesKit -@testable import DuckDuckGo -@testable import Core - -final class MockBrokenSitePromptLimiterStore: BrokenSitePromptLimiterStoring { - var lastToastShownDate: Date = .distantPast - var toastDismissStreakCounter: Int = 0 -} - -final class BrokenSitePromptLimiterTests: XCTestCase { - - let configManager = PrivacyConfigurationManagerMock() - var brokenSiteLimiter: BrokenSitePromptLimiter! - var mockStore: MockBrokenSitePromptLimiterStore! - - override func setUp() { - super.setUp() - - (configManager.privacyConfig as? PrivacyConfigurationMock)?.enabledFeaturesForVersions[.brokenSitePrompt] = [AppVersionProvider().appVersion() ?? ""] - - mockStore = MockBrokenSitePromptLimiterStore() - brokenSiteLimiter = BrokenSitePromptLimiter(privacyConfigManager: configManager, store: mockStore) - } - - func testShouldNotShowPromptIfConfigDisabled() throws { - (configManager.privacyConfig as? PrivacyConfigurationMock)?.enabledFeaturesForVersions[.brokenSitePrompt] = [] - XCTAssertFalse(brokenSiteLimiter.shouldShowToast(), "Toast should not show if disabled via config") - } - - func testShouldShowPromptOnFirstActivationThenLimit() throws { - XCTAssertTrue(brokenSiteLimiter.shouldShowToast(), "Toast should show on first activation") - brokenSiteLimiter.didShowToast() - XCTAssertFalse(brokenSiteLimiter.shouldShowToast(), "Subsequent call should not show toast due to limiting logic") - } - - func testShouldShowPromptAgainAfter7days() throws { - XCTAssertTrue(brokenSiteLimiter.shouldShowToast(), "Toast should show on first activation") - brokenSiteLimiter.didShowToast() - XCTAssertFalse(brokenSiteLimiter.shouldShowToast(), "Subsequent call should not show toast due to limiting logic") - mockStore.lastToastShownDate = Date().addingTimeInterval(-7 * 24 * 60 * 60 - 1) - XCTAssertTrue(brokenSiteLimiter.shouldShowToast(), "Toast should show again after 7 days") - } - - func testShouldNotShowPromptAfter3Dismissals() throws { - brokenSiteLimiter.didDismissToast() - brokenSiteLimiter.didDismissToast() - brokenSiteLimiter.didDismissToast() - // Set last date 7 days back so the toast shows but doesn't reset dismiss counter - mockStore.lastToastShownDate = Date().addingTimeInterval(-7 * 24 * 60 * 60 - 1) - XCTAssertFalse(brokenSiteLimiter.shouldShowToast(), "Toast should not show again after 3 dismissals") - } - - func testShouldResetDismissCounterAfter30Days() throws { - brokenSiteLimiter.didDismissToast() - brokenSiteLimiter.didDismissToast() - brokenSiteLimiter.didDismissToast() - XCTAssert(mockStore.toastDismissStreakCounter == 3, "Dismiss count should be equal to 3 after 3 dismiss calls") - XCTAssertTrue(brokenSiteLimiter.shouldShowToast(), "Toast should show after resetting counter") - XCTAssert(mockStore.toastDismissStreakCounter == 0, "Dismiss count should be reset to 0 after 30 days") - } - -} diff --git a/DuckDuckGoTests/UserBehaviorMonitorTests.swift b/DuckDuckGoTests/UserBehaviorMonitorTests.swift deleted file mode 100644 index 265a05484d..0000000000 --- a/DuckDuckGoTests/UserBehaviorMonitorTests.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// UserBehaviorMonitorTests.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import XCTest -import Common -@testable import DuckDuckGo - -final class MockUserBehaviorEventsMapping: EventMapping { - - init(captureEvent: @escaping (UserBehaviorEvent) -> Void) { - super.init { event, _, _, _ in - captureEvent(event) - } - } - - override init(mapping: @escaping EventMapping.Mapping) { - fatalError("Use init()") - } -} - -final class MockUserBehaviorStore: UserBehaviorStoring { - - var didRefreshTimestamp: Date? - var didDoubleRefreshTimestamp: Date? - var didRefreshCounter: Int = 0 - -} - -final class UserBehaviorMonitorTests: XCTestCase { - - var eventMapping: MockUserBehaviorEventsMapping! - var monitor: UserBehaviorMonitor! - var events: [UserBehaviorEvent] = [] - - override func setUp() { - super.setUp() - events.removeAll() - eventMapping = MockUserBehaviorEventsMapping(captureEvent: { event in - self.events.append(event) - }) - monitor = UserBehaviorMonitor(eventMapping: eventMapping, - store: MockUserBehaviorStore()) - } - - // - MARK: Behavior testing - // Expecting events - - func testWhenUserRefreshesTwiceItSendsReloadTwiceEvent() { - monitor.handleRefreshAction() - monitor.handleRefreshAction() - XCTAssertEqual(events.count, 1) - XCTAssertEqual(events[0], .reloadTwiceWithin12Seconds) - } - - func testWhenUserRefreshesThreeTimesItSendsTwoReloadTwiceEvents() { - monitor.handleRefreshAction() - monitor.handleRefreshAction() - monitor.handleRefreshAction() - XCTAssertEqual(events.count, 3) - XCTAssertEqual(events[0], .reloadTwiceWithin12Seconds) - XCTAssertEqual(events[1], .reloadTwiceWithin12Seconds) - } - - func testWhenUserRefreshesThreeTimesItSendsReloadThreeTimesEvent() { - monitor.handleRefreshAction() - monitor.handleRefreshAction() - monitor.handleRefreshAction() - XCTAssertEqual(events.count, 3) - XCTAssertEqual(events[2], .reloadThreeTimesWithin20Seconds) - } - - // Timed pixels - - func testReloadTwiceEventShouldNotSendEventIfSecondRefreshOccuredAfter12Seconds() { - let date = Date() - monitor.handleRefreshAction(date: date) - monitor.handleRefreshAction(date: date + 13) // 13 seconds after the first event - XCTAssertTrue(events.isEmpty) - } - - func testReloadTwiceEventShouldSendEventIfSecondRefreshOccurredBelow12Seconds() { - let date = Date() - monitor.handleRefreshAction(date: date) - monitor.handleRefreshAction(date: date + 11) // 20 seconds after the first event - XCTAssertEqual(events.count, 1) - XCTAssertEqual(events[0], .reloadTwiceWithin12Seconds) - } - - func testReloadThreeTimesEventShouldNotSendEventIfThreeRefreshesOccurredAfter20Seconds() { - let date = Date() - monitor.handleRefreshAction(date: date) - monitor.handleRefreshAction(date: date) - monitor.handleRefreshAction(date: date + 21) // 21 seconds after the first event - events.removeAll { $0 == .reloadTwiceWithin12Seconds } // remove events that are not being tested - XCTAssertTrue(events.isEmpty) - } - - func testReloadThreeTimesEventShouldSendEventIfThreeRefreshesOccurredBelow20Seconds() { - let date = Date() - monitor.handleRefreshAction(date: date) - monitor.handleRefreshAction(date: date) - monitor.handleRefreshAction(date: date + 19) // 10 seconds after the first event - events.removeAll { $0 == .reloadTwiceWithin12Seconds } // remove events that are not being tested - XCTAssertEqual(events.count, 1) - XCTAssertEqual(events[0], .reloadThreeTimesWithin20Seconds) - } - -}