diff --git a/.github/workflows/adhoc.yml b/.github/workflows/adhoc.yml index 157bf4371f..5fededcc1a 100644 --- a/.github/workflows/adhoc.yml +++ b/.github/workflows/adhoc.yml @@ -21,7 +21,7 @@ on: jobs: make-adhoc: - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge name: Make ad-hoc build steps: diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index 80156b5f3a..763f15db1e 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -31,7 +31,7 @@ on: jobs: make-alpha: - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge name: Make TestFlight Alpha Build timeout-minutes: 30 diff --git a/.github/workflows/end-to-end.yml b/.github/workflows/end-to-end.yml index a623d45091..014d40028c 100644 --- a/.github/workflows/end-to-end.yml +++ b/.github/workflows/end-to-end.yml @@ -8,7 +8,7 @@ on: jobs: end-to-end-tests: name: End to end Tests - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge steps: - name: Check out the code diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 51cbf9ccf9..c68371f73e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -7,7 +7,7 @@ on: jobs: atb-ui-tests: name: ATB UI Tests - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge timeout-minutes: 30 steps: @@ -68,7 +68,7 @@ jobs: fingerprinting-ui-tests: name: Fingerprinting UI Tests - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge timeout-minutes: 30 steps: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 0b7ef3b78a..87a40966e1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -39,7 +39,7 @@ jobs: name: Unit Tests - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge timeout-minutes: 15 outputs: @@ -132,7 +132,7 @@ jobs: # Dependabot doesn't have access to all secrets, so we skip this job if: github.actor != 'dependabot[bot]' - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge timeout-minutes: 30 steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9eefb19966..24ebf5946c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ on: jobs: make-release: if: github.event.action == 0 || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'Merge triggers release')) # empty string returns 0; for case when workflow is triggered manually - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge name: Make App Store Connect Release steps: diff --git a/.github/workflows/sync-end-to-end.yml b/.github/workflows/sync-end-to-end.yml index 43c6dab4de..9807bceb90 100644 --- a/.github/workflows/sync-end-to-end.yml +++ b/.github/workflows/sync-end-to-end.yml @@ -8,7 +8,7 @@ on: jobs: build-for-sync-end-to-end-tests: name: Build for Sync End To End Tests - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge timeout-minutes: 30 steps: @@ -68,7 +68,7 @@ jobs: sync-end-to-end-tests: name: Sync End To End Tests needs: build-for-sync-end-to-end-tests - runs-on: macos-13-xlarge + runs-on: macos-14-xlarge timeout-minutes: 90 strategy: matrix: diff --git a/.xcode-version b/.xcode-version index dafb659a69..232a7fc1a6 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -15.2 +15.4 diff --git a/Core/DefaultVariantManager.swift b/Core/DefaultVariantManager.swift index c2f73b4dc8..d7f4b63855 100644 --- a/Core/DefaultVariantManager.swift +++ b/Core/DefaultVariantManager.swift @@ -40,13 +40,6 @@ public struct VariantIOS: Variant { .contains(where: { Locale.current.regionCode == $0 }) } static let inEnglish = { return Locale.current.languageCode == "en" } - - static let iOS15 = { () -> Bool in - if #available(iOS 15, *) { - return true - } - return false - } } /// This variant is used for returning users to separate them from really new users. diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 5478cdfe88..2d896faaa5 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -201,8 +201,6 @@ extension Pixel { case downloadsSharingPredownloadedLocalFile - case downloadAttemptToOpenBLOBviaJS - case jsAlertShown case featureFlaggingInternalUserAuthenticated @@ -449,11 +447,8 @@ extension Pixel { case dbSaveExcludedHTTPSDomainsError case dbSaveBloomFilterError case dbRemoteMessagingSaveConfigError - case dbRemoteMessagingInvalidateConfigError - case dbRemoteMessagingSaveMessageError case dbRemoteMessagingUpdateMessageShownError case dbRemoteMessagingUpdateMessageStatusError - case dbRemoteMessagingDeleteScheduledMessageError case dbLocalAuthenticationError case configurationFetchInfo @@ -687,12 +682,6 @@ extension Pixel { case settingsAccessibilityOpen case settingsAccessiblityTextSize - // Other settings - case settingsKeyboardOnNewTabOn - case settingsKeyboardOnNewTabOff - case settingsKeyboardOnAppLaunchOn - case settingsKeyboardOnAppLaunchOff - // Web pixels case privacyProOfferMonthlyPriceClick case privacyProOfferYearlyPriceClick @@ -735,16 +724,10 @@ extension Pixel { case duckPlayerOverlayYoutubeImpressions case duckPlayerOverlayYoutubeWatchHere case duckPlayerSettingAlwaysDuckPlayer - case duckPlayerSettingAlwaysOverlaySERP - case duckPlayerSettingAlwaysOverlayYoutube case duckPlayerSettingAlwaysSettings - case duckPlayerSettingNeverOverlaySERP - case duckPlayerSettingNeverOverlayYoutube case duckPlayerSettingNeverSettings case duckPlayerSettingBackToDefault case duckPlayerWatchOnYoutube - case watchInDuckPlayerInitial - } } @@ -794,11 +777,6 @@ extension Pixel.Event { case .settingsAutoconsentOn: return "m_settings_autoconsent_on" case .settingsAutoconsentOff: return "m_settings_autoconsent_off" - case .settingsKeyboardOnNewTabOn: return "m_settings_keyboard_on-new-tab_on" - case .settingsKeyboardOnNewTabOff: return "m_settings_keyboard_on-new-tab_off" - case .settingsKeyboardOnAppLaunchOn: return "m_settings_keyboard_on-app-launch_on" - case .settingsKeyboardOnAppLaunchOff: return "m_settings_keyboard_on-app-launch_off" - case .browsingMenuOpened: return "mb" case .browsingMenuNewTab: return "mb_tb" case .browsingMenuAddToBookmarks: return "mb_abk" @@ -934,8 +912,6 @@ extension Pixel.Event { case .downloadsSharingPredownloadedLocalFile: return "m_downloads_sharing_predownloaded_local_file" - case .downloadAttemptToOpenBLOBviaJS: return "m_download_attempt_to_open_blob_js" - case .jsAlertShown: return "m_js_alert_shown" case .featureFlaggingInternalUserAuthenticated: return "m_internal-user_authenticated" @@ -1169,11 +1145,8 @@ extension Pixel.Event { case .dbSaveExcludedHTTPSDomainsError: return "m_d_dbsw" case .dbSaveBloomFilterError: return "m_d_dbsb" case .dbRemoteMessagingSaveConfigError: return "m_d_db_rm_save_config" - case .dbRemoteMessagingInvalidateConfigError: return "m_d_db_rm_invalidate_config" - case .dbRemoteMessagingSaveMessageError: return "m_d_db_rm_save_message" case .dbRemoteMessagingUpdateMessageShownError: return "m_d_db_rm_update_message_shown" case .dbRemoteMessagingUpdateMessageStatusError: return "m_d_db_rm_update_message_status" - case .dbRemoteMessagingDeleteScheduledMessageError: return "m_d_db_rm_delete_scheduled_message" case .dbLocalAuthenticationError: return "m_d_local_auth_error" case .debugBookmarksMigratedMoreThanOnce: return "m_debug_bookmarks_migrated-more-than-once" @@ -1465,17 +1438,12 @@ extension Pixel.Event { case .duckPlayerViewFromSERP: return "m_duck-player_view-from_serp" case .duckPlayerViewFromOther: return "m_duck-player_view-from_other" case .duckPlayerSettingAlwaysSettings: return "m_duck-player_setting_always_settings" + case .duckPlayerSettingAlwaysDuckPlayer: return "m_duck-player_setting_always_duck-player" case .duckPlayerOverlayYoutubeImpressions: return "m_duck-player_overlay_youtube_impressions" case .duckPlayerOverlayYoutubeWatchHere: return "m_duck-player_overlay_youtube_watch_here" - case .duckPlayerSettingAlwaysDuckPlayer: return "m_duck-player_setting_always_duck-player" - case .duckPlayerSettingAlwaysOverlaySERP: return "m_duck-player_setting_always_overlay_serp" - case .duckPlayerSettingAlwaysOverlayYoutube: return "m_duck-player_setting_always_overlay_youtube" - case .duckPlayerSettingNeverOverlaySERP: return "m_duck-player_setting_never_overlay_serp" - case .duckPlayerSettingNeverOverlayYoutube: return "m_duck-player_setting_never_overlay_youtube" case .duckPlayerSettingNeverSettings: return "m_duck-player_setting_never_settings" case .duckPlayerSettingBackToDefault: return "m_duck-player_setting_back-to-default" case .duckPlayerWatchOnYoutube: return "m_duck-player_watch_on_youtube" - case .watchInDuckPlayerInitial: return "m_watch-in-duckplayer_initial_u" } } } diff --git a/Core/UserDefaults+NetworkProtection.swift b/Core/UserDefaults+NetworkProtection.swift index 4ec17ba101..ac5db87a93 100644 --- a/Core/UserDefaults+NetworkProtection.swift +++ b/Core/UserDefaults+NetworkProtection.swift @@ -17,8 +17,6 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import Foundation public extension UserDefaults { @@ -36,5 +34,3 @@ public enum NetworkProtectionUserDefaultKeys { public static let lastSelectedServerCity = "com.duckduckgo.network-protection.last-selected-server-city" } - -#endif diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c3e12f62c5..d365b15de8 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -228,7 +228,6 @@ 4BCBE45E2BA7E81F00FC75A1 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 4BCBE45D2BA7E81F00FC75A1 /* PrivacyInfo.xcprivacy */; }; 4BCBE4602BA7E87100FC75A1 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 4BCBE45F2BA7E87100FC75A1 /* PrivacyInfo.xcprivacy */; }; 4BCD14672B05B682000B1E4C /* NetworkProtectionTermsAndConditionsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD14662B05B682000B1E4C /* NetworkProtectionTermsAndConditionsStore.swift */; }; - 4BCD146D2B05DB09000B1E4C /* NetworkProtectionAccessControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD146C2B05DB09000B1E4C /* NetworkProtectionAccessControllerTests.swift */; }; 4BE2756827304F57006B20B0 /* URLRequestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE27566272F878F006B20B0 /* URLRequestExtension.swift */; }; 4BE67B012B96B741007335F7 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4BE67B002B96B741007335F7 /* Common */; }; 4BE67B032B96B864007335F7 /* ContentBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = 4BE67B022B96B864007335F7 /* ContentBlocking */; }; @@ -267,6 +266,7 @@ 6F64AA5D2C4920D200CF4489 /* ShortcutAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F64AA5C2C4920D200CF4489 /* ShortcutAccessoryView.swift */; }; 6F64AA5F2C49463C00CF4489 /* ShortcutsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F64AA5E2C49463C00CF4489 /* ShortcutsModel.swift */; }; 6F655BE22BAB289E00AC3597 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */; }; + 6F691CCA2C4979EC002E9553 /* FavoritesTooltip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F691CC92C4979EC002E9553 /* FavoritesTooltip.swift */; }; 6F8496412BC3D8EE00ADA54E /* OnboardingButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8496402BC3D8EE00ADA54E /* OnboardingButtonsView.swift */; }; 6F96FF102C2B128500162692 /* NewTabPageCustomizeButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F96FF0F2C2B128500162692 /* NewTabPageCustomizeButtonView.swift */; }; 6FA3438F2C3D3BC300470677 /* Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA3438E2C3D3BC300470677 /* Favorite.swift */; }; @@ -951,7 +951,6 @@ F15531962BF215F60029ED04 /* SubscriptionTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F15531952BF215F60029ED04 /* SubscriptionTestingUtilities */; }; F1564F032B7B915F00D454A6 /* AppDelegate+SKAD4.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */; }; F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */; }; - F15E9F3E2BEE128200DEFDDE /* SubscriptionManageriOS14.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15E9F3D2BEE128200DEFDDE /* SubscriptionManageriOS14.swift */; }; F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */; }; F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C141E57336D00DEDCAF /* TabManager.swift */; }; F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */; }; @@ -1384,7 +1383,6 @@ 4BCBE45D2BA7E81F00FC75A1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 4BCBE45F2BA7E87100FC75A1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 4BCD14662B05B682000B1E4C /* NetworkProtectionTermsAndConditionsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionTermsAndConditionsStore.swift; sourceTree = ""; }; - 4BCD146C2B05DB09000B1E4C /* NetworkProtectionAccessControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAccessControllerTests.swift; sourceTree = ""; }; 4BE27566272F878F006B20B0 /* URLRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLRequestExtension.swift; path = ../DuckDuckGo/URLRequestExtension.swift; sourceTree = ""; }; 4BF3E4AE2C06A85200ED5D57 /* VPNRedditSessionWorkaround.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNRedditSessionWorkaround.swift; sourceTree = ""; }; 560E990E2BEE2CB800507CE0 /* SyncErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorMessage.swift; sourceTree = ""; }; @@ -1419,6 +1417,7 @@ 6F64AA5C2C4920D200CF4489 /* ShortcutAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutAccessoryView.swift; sourceTree = ""; }; 6F64AA5E2C49463C00CF4489 /* ShortcutsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsModel.swift; sourceTree = ""; }; 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = ""; }; + 6F691CC92C4979EC002E9553 /* FavoritesTooltip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesTooltip.swift; sourceTree = ""; }; 6F8496402BC3D8EE00ADA54E /* OnboardingButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingButtonsView.swift; sourceTree = ""; }; 6F96FF0F2C2B128500162692 /* NewTabPageCustomizeButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageCustomizeButtonView.swift; sourceTree = ""; }; 6FA3438E2C3D3BC300470677 /* Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Favorite.swift; sourceTree = ""; }; @@ -2667,7 +2666,6 @@ F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate+SKAD4.swift"; sourceTree = ""; }; F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = ""; }; F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteViewController.swift; sourceTree = ""; }; - F15E9F3D2BEE128200DEFDDE /* SubscriptionManageriOS14.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionManageriOS14.swift; sourceTree = ""; }; F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherViewController.swift; sourceTree = ""; }; F1617C141E57336D00DEDCAF /* TabManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabManager.swift; sourceTree = ""; }; F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherDelegate.swift; sourceTree = ""; }; @@ -3492,6 +3490,14 @@ name = NewTabPage; sourceTree = ""; }; + 6F691CC82C4979DD002E9553 /* Tooltip */ = { + isa = PBXGroup; + children = ( + 6F691CC92C4979EC002E9553 /* FavoritesTooltip.swift */, + ); + name = Tooltip; + sourceTree = ""; + }; 6FA3438D2C3D3BB800470677 /* Model */ = { isa = PBXGroup; children = ( @@ -3570,6 +3576,7 @@ 6FE127412C204DE900EB5724 /* Favorites */ = { isa = PBXGroup; children = ( + 6F691CC82C4979DD002E9553 /* Tooltip */, 6FA343902C3D3C2500470677 /* Item */, 6FA3438D2C3D3BB800470677 /* Model */, 6FB2A6782C2C5B9E004D20C8 /* EmptyState */, @@ -4749,7 +4756,6 @@ children = ( F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */, D60170BB2BA32DD6001911B5 /* Subscription.swift */, - F15E9F3D2BEE128200DEFDDE /* SubscriptionManageriOS14.swift */, D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */, D664C7952B289AA000CBFA76 /* Subscription.storekit */, D664C7932B289AA000CBFA76 /* ViewModel */, @@ -4947,7 +4953,6 @@ isa = PBXGroup; children = ( EEFE9C722A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift */, - 4BCD146C2B05DB09000B1E4C /* NetworkProtectionAccessControllerTests.swift */, EEC02C152B065BE00045CE11 /* NetworkProtectionVPNLocationViewModelTests.swift */, BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */, ); @@ -6699,7 +6704,6 @@ 3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */, 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, 8540BD5423D8D5080057FDD2 /* PreserveLoginsAlert.swift in Sources */, - F15E9F3E2BEE128200DEFDDE /* SubscriptionManageriOS14.swift in Sources */, 1E87615928A1517200C7C5CE /* PrivacyDashboardViewController.swift in Sources */, 6F03CAFE2C32DD08004179A8 /* HomePageMessagesConfiguration.swift in Sources */, EE9D68D12AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift in Sources */, @@ -6806,6 +6810,7 @@ 9FE08BD62C2A60CD001D5EBC /* MetricBuilder.swift in Sources */, 1E1626072968413B0004127F /* ViewExtension.swift in Sources */, 31A42566285A0A6300049386 /* FaviconViewModel.swift in Sources */, + 6F691CCA2C4979EC002E9553 /* FavoritesTooltip.swift in Sources */, D65625952C22D382006EF297 /* TabViewController.swift in Sources */, 8C4838B5221C8F7F008A6739 /* GestureToolbarButton.swift in Sources */, EE276BEA2A77F823009167B6 /* NetworkProtectionRootViewController.swift in Sources */, @@ -7313,7 +7318,6 @@ 987130C9294AAB9F00AB05E0 /* BookmarkUtilsTests.swift in Sources */, 56A061442BEE086700F24B36 /* CapturingAdapterErrorHandler.swift in Sources */, C1BF0BA929B63E2200482B73 /* AutofillLoginPromptViewModelTests.swift in Sources */, - 4BCD146D2B05DB09000B1E4C /* NetworkProtectionAccessControllerTests.swift in Sources */, EE3B226B29DE0F110082298A /* MockInternalUserStoring.swift in Sources */, 987130C8294AAB9F00AB05E0 /* BookmarksTestHelpers.swift in Sources */, C185ED672BD43DA100BAE9DC /* ImportPasswordsStatusHandlerTests.swift in Sources */, @@ -8647,7 +8651,7 @@ ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ld_classic"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -8703,7 +8707,7 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = "-ld_classic"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = NETWORK_PROTECTION; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -8732,7 +8736,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development - App"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; }; name = Debug; @@ -8755,7 +8759,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.duckduckgo.mobile.ios"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = NETWORK_PROTECTION; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_VERSION = 5.0; }; name = Release; @@ -9206,7 +9210,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG ALPHA"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -9237,7 +9241,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development App - Alpha"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG ALPHA"; SWIFT_VERSION = 5.0; }; name = "Alpha Debug"; @@ -9598,7 +9602,7 @@ ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ld_classic"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "NETWORK_PROTECTION ALPHA"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALPHA; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -9625,7 +9629,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha; PRODUCT_NAME = "$(TARGET_NAME)-Alpha"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.duckduckgo.mobile.ios.alpha"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "NETWORK_PROTECTION ALPHA"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALPHA; SWIFT_VERSION = 5.0; }; name = Alpha; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 47bf7bf9e6..4c558c3a3f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "dc26bfc6e33ad9c79a719b7f21d5ca0564db1859", - "version" : "6.3.0" + "revision" : "097e545c737db78cb1d253a87a9acd6dd8ad8497", + "version" : "6.4.0" } }, { diff --git a/DuckDuckGo/ActionMessageView.swift b/DuckDuckGo/ActionMessageView.swift index 57ff705e6e..f0a3ef3f63 100644 --- a/DuckDuckGo/ActionMessageView.swift +++ b/DuckDuckGo/ActionMessageView.swift @@ -124,7 +124,7 @@ class ActionMessageView: UIView { presentationLocation: PresentationLocation = .withBottomBar(andAddressBarBottom: false), onAction: @escaping () -> Void = {}, onDidDismiss: @escaping () -> Void = {}) { - guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first else { return } + guard let window = UIApplication.shared.firstKeyWindow else { return } dismissAllMessages() diff --git a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift index a9d3d31a80..b052a0c127 100644 --- a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift +++ b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift @@ -46,10 +46,6 @@ struct DefaultAdAttributionFetcher: AdAttributionFetcher { } func fetch() async -> AdServicesAttributionResponse? { - guard #available(iOS 14.3, *) else { - return nil - } - var lastToken: String? for _ in 0.. String { - if #available(iOS 14.3, *) { - return try AAAttribution.attributionToken() - } else { - throw AdAttributionFetcherError.attributionUnsupported - } + return try AAAttribution.attributionToken() } } diff --git a/DuckDuckGo/AnimatableTypingText.swift b/DuckDuckGo/AnimatableTypingText.swift index 7e323f99af..b391d447f0 100644 --- a/DuckDuckGo/AnimatableTypingText.swift +++ b/DuckDuckGo/AnimatableTypingText.swift @@ -47,13 +47,8 @@ struct AnimatableTypingText: View { .frame(maxWidth: .infinity, alignment: .leading) .visibility(.invisible) - if #available(iOS 15, *) { - Text(AttributedString(model.typedAttributedText)) - .frame(maxWidth: .infinity, alignment: .leading) - } else { - Text(model.typedAttributedText.string) - .frame(maxWidth: .infinity, alignment: .leading) - } + Text(AttributedString(model.typedAttributedText)) + .frame(maxWidth: .infinity, alignment: .leading) } .onChange(of: startAnimating.wrappedValue, perform: { shouldAnimate in if shouldAnimate { @@ -128,10 +123,6 @@ final class AnimatableTypingTextModel: ObservableObject { private func showCharacter() { func attributedTypedString(forTypedChars typedChars: [String.Element]) -> NSAttributedString { - guard #available(iOS 15, *) else { - return NSAttributedString(string: String(typedChars)) - } - let chars = Array(text) let untypedChars = chars[typedChars.count ..< chars.count] let combined = NSMutableAttributedString(string: String(typedChars)) diff --git a/DuckDuckGo/AppDelegate+AppDeepLinks.swift b/DuckDuckGo/AppDelegate+AppDeepLinks.swift index 5cb1e9204f..280b7c32bb 100644 --- a/DuckDuckGo/AppDelegate+AppDeepLinks.swift +++ b/DuckDuckGo/AppDelegate+AppDeepLinks.swift @@ -51,9 +51,7 @@ extension AppDelegate { mainViewController.newEmailAddress() case .openVPN: -#if NETWORK_PROTECTION presentNetworkProtectionStatusSettingsModal() -#endif case .openPasswords: var source: AutofillSettingsSource = .homeScreenWidget diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index e9d6bb424c..1d84119be4 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -35,11 +35,8 @@ import DDGSync import RemoteMessaging import SyncDataProviders import Subscription - -#if NETWORK_PROTECTION import NetworkProtection import WebKit -#endif @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -47,10 +44,7 @@ import WebKit private struct ShortcutKey { static let clipboard = "com.duckduckgo.mobile.ios.clipboard" static let passwords = "com.duckduckgo.mobile.ios.passwords" - -#if NETWORK_PROTECTION static let openVPNSettings = "com.duckduckgo.mobile.ios.vpn.open-settings" -#endif } private var testing = false @@ -61,7 +55,6 @@ import WebKit private lazy var privacyStore = PrivacyUserDefaults() private var bookmarksDatabase: CoreDataDatabase = BookmarksDatabase.make() -#if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults @@ -71,7 +64,6 @@ import WebKit tunnelController: AppDependencyProvider.shared.networkProtectionTunnelController ) }() -#endif private var autoClear: AutoClear? private var showKeyboardIfSettingOn = true @@ -358,9 +350,7 @@ import WebKit AppDependencyProvider.shared.appSettings.setAutofillIsNewInstallForOnByDefault() } -#if NETWORK_PROTECTION widgetRefreshModel.beginObservingVPNStatus() -#endif AppDependencyProvider.shared.subscriptionManager.loadInitialData() @@ -429,7 +419,6 @@ import WebKit window?.rootViewController?.present(alertController, animated: true, completion: nil) } -#if NETWORK_PROTECTION private func presentExpiredEntitlementAlert() { let alertController = CriticalAlerts.makeExpiredEntitlementAlert { [weak self] in self?.mainViewController?.segueToPrivacyPro() @@ -447,7 +436,6 @@ import WebKit ) presenter.showEntitlementNotification() } -#endif private func cleanUpMacPromoExperiment2() { UserDefaults.standard.removeObject(forKey: "com.duckduckgo.ios.macPromoMay23.exp2.cohort") @@ -523,7 +511,6 @@ import WebKit fireFailedCompilationsPixelIfNeeded() -#if NETWORK_PROTECTION widgetRefreshModel.refreshVPNWidget() if tunnelDefaults.showEntitlementAlert { @@ -537,7 +524,6 @@ import WebKit await refreshShortcuts() await vpnWorkaround.installRedditSessionWorkaround() } -#endif AppDependencyProvider.shared.subscriptionManager.refreshCachedSubscriptionAndEntitlements { isSubscriptionActive in if isSubscriptionActive { @@ -637,7 +623,8 @@ import WebKit UILabel.appearance(whenContainedInInstancesOf: [UIAlertController.self]).numberOfLines = 0 } - private func refreshRemoteMessages() { + /// It's public in order to allow refreshing on demand via Debug menu. Otherwise it shouldn't be called from outside. + func refreshRemoteMessages() { Task { try? await remoteMessagingClient.fetchAndProcess(using: remoteMessagingClient.store) } @@ -801,7 +788,7 @@ import WebKit } private func tryToObtainOverlayWindow() { - for window in UIApplication.shared.windows where window.rootViewController is BlankSnapshotViewController { + for window in UIApplication.shared.foregroundSceneWindows where window.rootViewController is BlankSnapshotViewController { overlayWindow = window return } @@ -846,11 +833,9 @@ import WebKit return } -#if NETWORK_PROTECTION if shortcutItem.type == ShortcutKey.openVPNSettings { presentNetworkProtectionStatusSettingsModal() } -#endif } } @@ -914,7 +899,6 @@ import WebKit @MainActor func refreshShortcuts() async { -#if NETWORK_PROTECTION guard AppDependencyProvider.shared.vpnFeatureVisibility.shouldShowVPNShortcut() else { UIApplication.shared.shortcutItems = nil return @@ -933,7 +917,6 @@ import WebKit } else { UIApplication.shared.shortcutItems = nil } -#endif } } @@ -991,31 +974,25 @@ extension AppDelegate: UNUserNotificationCenterDelegate { if response.actionIdentifier == UNNotificationDefaultActionIdentifier { let identifier = response.notification.request.identifier -#if NETWORK_PROTECTION if NetworkProtectionNotificationIdentifier(rawValue: identifier) != nil { presentNetworkProtectionStatusSettingsModal() } -#endif } completionHandler() } -#if NETWORK_PROTECTION func presentNetworkProtectionStatusSettingsModal() { Task { if case .success(let hasEntitlements) = await accountManager.hasEntitlement(forProductName: .networkProtection), hasEntitlements { - if #available(iOS 15, *) { - let networkProtectionRoot = NetworkProtectionRootViewController() - presentSettings(with: networkProtectionRoot) - } + let networkProtectionRoot = NetworkProtectionRootViewController() + presentSettings(with: networkProtectionRoot) } else { (window?.rootViewController as? MainViewController)?.segueToPrivacyPro() } } } -#endif private func presentSettings(with viewController: UIViewController) { guard let window = window, let rootViewController = window.rootViewController as? MainViewController else { return } diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index fea06bf74e..9fb8e62f7c 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -111,16 +111,12 @@ class AppDependencyProvider: DependencyProvider { entitlementsCache: entitlementsCache, subscriptionEndpointService: subscriptionService, authEndpointService: authService) - if #available(iOS 15.0, *) { - subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: DefaultStorePurchaseManager(), - accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - subscriptionEnvironment: subscriptionEnvironment) - } else { - // This is used just for iOS <15, it's a sort of mocked environment that will not be used. - subscriptionManager = SubscriptionManageriOS14(accountManager: accountManager) - } + + subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: DefaultStorePurchaseManager(), + accountManager: accountManager, + subscriptionEndpointService: subscriptionService, + authEndpointService: authService, + subscriptionEnvironment: subscriptionEnvironment) let subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability( privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, diff --git a/DuckDuckGo/AutofillLoginDetailsView.swift b/DuckDuckGo/AutofillLoginDetailsView.swift index d645e9b346..934f380071 100644 --- a/DuckDuckGo/AutofillLoginDetailsView.swift +++ b/DuckDuckGo/AutofillLoginDetailsView.swift @@ -74,7 +74,7 @@ struct AutofillLoginDetailsView: View { viewModel.selectedCell = nil })) .listStyle(.insetGrouped) - .animation(.easeInOut) + .animation(.easeInOut, value: viewModel.viewMode) } private var editingContentView: some View { @@ -191,27 +191,19 @@ struct AutofillLoginDetailsView: View { usernameCell() } footer: { if !viewModel.isSignedIn { - if #available(iOS 15, *) { - var attributedString: AttributedString { - let text = String(format: UserText.autofillSignInToManageEmail, UserText.autofillEnableEmailProtection) - var attributedString = AttributedString(text) - if let range = attributedString.range(of: UserText.autofillEnableEmailProtection) { - attributedString[range].foregroundColor = Color(ThemeManager.shared.currentTheme.buttonTintColor) - } - return attributedString + var attributedString: AttributedString { + let text = String(format: UserText.autofillSignInToManageEmail, UserText.autofillEnableEmailProtection) + var attributedString = AttributedString(text) + if let range = attributedString.range(of: UserText.autofillEnableEmailProtection) { + attributedString[range].foregroundColor = Color(ThemeManager.shared.currentTheme.buttonTintColor) } - Text(attributedString) - .font(.footnote) - .lineLimit(nil) - .multilineTextAlignment(.leading) - .fixedSize(horizontal: false, vertical: true) - } else { - Text(String(format: UserText.autofillSignInToManageEmail, UserText.autofillEnableEmailProtection)) - .font(.footnote) - .lineLimit(nil) - .multilineTextAlignment(.leading) - .fixedSize(horizontal: false, vertical: true) + return attributedString } + Text(attributedString) + .font(.footnote) + .lineLimit(nil) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) } } .onTapGesture { diff --git a/DuckDuckGo/AutofillLoginDetailsViewController.swift b/DuckDuckGo/AutofillLoginDetailsViewController.swift index 2fde29ee32..1a08045aa1 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewController.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewController.swift @@ -343,13 +343,11 @@ extension AutofillLoginDetailsViewController { navigationController?.navigationBar.barTintColor = theme.barBackgroundColor navigationController?.navigationBar.tintColor = theme.navigationBarTintColor - if #available(iOS 15.0, *) { - let appearance = UINavigationBarAppearance() - appearance.shadowColor = .clear - appearance.backgroundColor = theme.backgroundColor + let appearance = UINavigationBarAppearance() + appearance.shadowColor = .clear + appearance.backgroundColor = theme.backgroundColor - navigationController?.navigationBar.standardAppearance = appearance - navigationController?.navigationBar.scrollEdgeAppearance = appearance - } + navigationController?.navigationBar.standardAppearance = appearance + navigationController?.navigationBar.scrollEdgeAppearance = appearance } } diff --git a/DuckDuckGo/AutofillLoginPromptViewController.swift b/DuckDuckGo/AutofillLoginPromptViewController.swift index 701ef51c0e..5841f4c36c 100644 --- a/DuckDuckGo/AutofillLoginPromptViewController.swift +++ b/DuckDuckGo/AutofillLoginPromptViewController.swift @@ -82,8 +82,7 @@ class AutofillLoginPromptViewController: UIViewController { if #available(iOS 16.0, *) { return true } - if #available(iOS 15.0, *), - let presentationController = presentationController as? UISheetPresentationController { + if let presentationController = presentationController as? UISheetPresentationController { if presentationController.selectedDetentIdentifier == nil && presentationController.detents.contains(.medium()) { return false @@ -178,10 +177,8 @@ extension AutofillLoginPromptViewController: AutofillLoginPromptViewModelDelegat } func autofillLoginPromptViewModelDidRequestExpansion(_ viewModel: AutofillLoginPromptViewModel) { - if #available(iOS 15.0, *) { - dismiss(animated: true) { - self.completion?(nil, true) - } + dismiss(animated: true) { + self.completion?(nil, true) } } diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index fba8246fca..921330d649 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -466,15 +466,13 @@ final class AutofillLoginSettingsListViewController: UIViewController { } ) - if #available(iOS 15.0, *) { - if let presentationController = authConfirmationPromptViewController.presentationController as? UISheetPresentationController { - if #available(iOS 16.0, *) { - presentationController.detents = [.custom(resolver: { _ in - AutofillViews.deleteAllPromptMinHeight - })] - } else { - presentationController.detents = [.medium()] - } + if let presentationController = authConfirmationPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.deleteAllPromptMinHeight + })] + } else { + presentationController.detents = [.medium()] } } @@ -961,14 +959,12 @@ extension AutofillLoginSettingsListViewController { navigationController?.navigationBar.barTintColor = theme.barBackgroundColor navigationController?.navigationBar.tintColor = theme.navigationBarTintColor - if #available(iOS 15.0, *) { - let appearance = UINavigationBarAppearance() - appearance.shadowColor = .clear - appearance.backgroundColor = theme.backgroundColor + let appearance = UINavigationBarAppearance() + appearance.shadowColor = .clear + appearance.backgroundColor = theme.backgroundColor - navigationController?.navigationBar.standardAppearance = appearance - navigationController?.navigationBar.scrollEdgeAppearance = appearance - } + navigationController?.navigationBar.standardAppearance = appearance + navigationController?.navigationBar.scrollEdgeAppearance = appearance tableView.reloadData() } diff --git a/DuckDuckGo/CompleteDownloadRow.swift b/DuckDuckGo/CompleteDownloadRow.swift index dbd7f3e9a4..4cb5f1694f 100644 --- a/DuckDuckGo/CompleteDownloadRow.swift +++ b/DuckDuckGo/CompleteDownloadRow.swift @@ -71,7 +71,6 @@ struct CompleteDownloadRow: View { } .accessibilityLabel(UserText.actionShare) .buttonStyle(.plain) - .animation(nil) .background( GeometryReader { geometryProxy in Color.clear diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 9d6e3255c3..07212f8267 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -1,9 +1,9 @@ - + - + @@ -264,7 +264,7 @@ - + @@ -300,7 +300,7 @@ - + @@ -923,17 +923,17 @@ - + - + - + - + diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index 0921c89ea1..29f988a2b3 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -17,8 +17,6 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import Foundation import BrowserServicesKit import Waitlist @@ -66,5 +64,3 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { } } } - -#endif diff --git a/DuckDuckGo/DownloadsList.swift b/DuckDuckGo/DownloadsList.swift index afebd5f884..d2bd6fb844 100644 --- a/DuckDuckGo/DownloadsList.swift +++ b/DuckDuckGo/DownloadsList.swift @@ -40,17 +40,7 @@ struct DownloadsList: View { private var doneButton: some View { Button(action: { - if #available(iOS 15.0, *) { - presentationMode.wrappedValue.dismiss() - } else { - // Because: presentationMode.wrappedValue.dismiss() for view wrapped in NavigationView() does not work in iOS 14 and lower - if var topController = UIApplication.shared.windows.first!.rootViewController { - while let presentedViewController = topController.presentedViewController { - topController = presentedViewController - } - topController.dismiss(animated: true) - } - } + presentationMode.wrappedValue.dismiss() }, label: { Text(UserText.navigationTitleDone).foregroundColor(.barButton).bold() }) .opacity(editMode == .inactive ? 1.0 : 0.0) @@ -81,15 +71,9 @@ struct DownloadsList: View { @ViewBuilder private var listWithBottomToolbar: some View { - if #available(iOS 15.0, *) { - listWithBackground.toolbar { - ToolbarItemGroup(placement: .bottomBar) { - toolbarButtons - } - } - } else { - listWithBackground.toolbar { - toolbarContent + listWithBackground.toolbar { + ToolbarItemGroup(placement: .bottomBar) { + toolbarButtons } } } diff --git a/DuckDuckGo/DownloadsListHostingController.swift b/DuckDuckGo/DownloadsListHostingController.swift index 84738042a6..ac700bb75b 100644 --- a/DuckDuckGo/DownloadsListHostingController.swift +++ b/DuckDuckGo/DownloadsListHostingController.swift @@ -51,7 +51,7 @@ class DownloadsListHostingController: UIHostingController { let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) if UIDevice.current.userInterfaceIdiom == .pad { - activityViewController.popoverPresentationController?.sourceView = UIApplication.shared.windows.first + activityViewController.popoverPresentationController?.sourceView = UIApplication.shared.firstKeyWindow activityViewController.popoverPresentationController?.permittedArrowDirections = .right activityViewController.popoverPresentationController?.sourceRect = rect } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index 4192367a0d..a258d80a02 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -124,6 +124,15 @@ final class DuckPlayer: DuckPlayerProtocol { } settings.setMode(userValues.duckPlayerMode) settings.setOverlayHidden(userValues.askModeOverlayHidden) + + // Fire Pixels + switch userValues.duckPlayerMode { + case .enabled: + Pixel.fire(pixel: Pixel.Event.duckPlayerSettingAlwaysDuckPlayer, debounce: 2) + default: + break + } + return userValues } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 7f50c6ad11..29f0ebab75 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -22,12 +22,14 @@ import ContentScopeScripts import WebKit import Core import Common +import BrowserServicesKit final class DuckPlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol var referrer: DuckPlayerReferrer = .other var lastHandledVideoID: String? + var featureFlagger: FeatureFlagger private struct Constants { static let SERPURL = "https://duckduckgo.com/" @@ -45,8 +47,10 @@ final class DuckPlayerNavigationHandler { static let urlInternalReferrer = "embeds_referring_euri" } - init(duckPlayer: DuckPlayerProtocol = DuckPlayer()) { + init(duckPlayer: DuckPlayerProtocol = DuckPlayer(), + featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger) { self.duckPlayer = duckPlayer + self.featureFlagger = featureFlagger } static var htmlTemplatePath: String { @@ -83,10 +87,7 @@ final class DuckPlayerNavigationHandler { } private func performNavigation(_ request: URLRequest, responseHTML: String, webView: WKWebView) { - // iOS 14 will be soon dropped out (and it does not support simulatedRequests) - if #available(iOS 15.0, *) { - webView.loadSimulatedRequest(request, responseHTML: responseHTML) - } + webView.loadSimulatedRequest(request, responseHTML: responseHTML) } private func performRequest(request: URLRequest, webView: WKWebView) { @@ -102,6 +103,10 @@ final class DuckPlayerNavigationHandler { guard let url else { return } + guard featureFlagger.isFeatureOn(.duckPlayer) else { + return + } + if let (videoID, _) = url.youtubeVideoParams, videoID == lastHandledVideoID { os_log("DP: URL (%s) already handled, skipping", log: .duckPlayerLog, type: .debug, url.absoluteString) @@ -139,6 +144,10 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { guard let url = navigationAction.request.url else { return } + guard featureFlagger.isFeatureOn(.duckPlayer) else { + return + } + // Handle Youtube internal links like "Age restricted" and "Copyright restricted" videos // These should not be handled by DuckPlayer if url.isYoutubeVideo, @@ -157,6 +166,7 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { if let videoParameterItem = queryItems.first(where: { $0.name == Constants.watchInYoutubeVideoParameter }), let id = videoParameterItem.value, let newURL = URL.youtube(id, timestamp: nil).addingWatchInYoutubeQueryParameter() { + Pixel.fire(pixel: Pixel.Event.duckPlayerWatchOnYoutube) webView.load(URLRequest(url: newURL)) return } @@ -173,7 +183,7 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Pixel for Views From Youtube if referrer == .youtube, duckPlayer.settings.mode == .enabled { - Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromYoutubeAutomatic, debounce: 2) + Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromYoutubeAutomatic) } // If DuckPlayer is Enabled or in ask mode, render the video @@ -181,14 +191,13 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk, !url.hasWatchInYoutubeQueryParameter { let newRequest = Self.makeDuckPlayerRequest(from: URLRequest(url: url)) - if #available(iOS 15.0, *) { - os_log("DP: Loading Simulated Request for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - self.performRequest(request: newRequest, webView: webView) - } - return + + os_log("DP: Loading Simulated Request for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.performRequest(request: newRequest, webView: webView) } + return } } @@ -205,6 +214,11 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { return } + guard featureFlagger.isFeatureOn(.duckPlayer) else { + completion(.allow) + return + } + if let (videoID, _) = url.youtubeVideoParams, videoID == lastHandledVideoID, !url.hasWatchInYoutubeQueryParameter { @@ -243,6 +257,11 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { @MainActor func handleJSNavigation(url: URL?, webView: WKWebView) { + + guard featureFlagger.isFeatureOn(.duckPlayer) else { + return + } + handleURLChange(url: url, webView: webView) } @@ -251,6 +270,11 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { os_log("DP: Handling Back Navigation", log: .duckPlayerLog, type: .debug) + guard featureFlagger.isFeatureOn(.duckPlayer) else { + webView.goBack() + return + } + lastHandledVideoID = nil webView.stopLoading() @@ -283,6 +307,11 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { @MainActor func handleReload(webView: WKWebView) { + guard featureFlagger.isFeatureOn(.duckPlayer) else { + webView.reload() + return + } + lastHandledVideoID = nil webView.stopLoading() if let url = webView.url, url.isDuckPlayer, @@ -299,6 +328,10 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { @MainActor func handleAttach(webView: WKWebView) { + guard featureFlagger.isFeatureOn(.duckPlayer) else { + return + } + if let url = webView.url, url.isDuckPlayer, !url.isDuckURLScheme, duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { diff --git a/DuckDuckGo/DuckPlayer/Modal/DuckPlayerModalPresenter.swift b/DuckDuckGo/DuckPlayer/Modal/DuckPlayerModalPresenter.swift index 503e6af4e5..f5ecb51636 100644 --- a/DuckDuckGo/DuckPlayer/Modal/DuckPlayerModalPresenter.swift +++ b/DuckDuckGo/DuckPlayer/Modal/DuckPlayerModalPresenter.swift @@ -42,16 +42,14 @@ struct DuckPlayerModalPresenter { } private func configurePresentationStyle(for hostingController: UIHostingController, on viewController: UIViewController) { - if #available(iOS 15.0, *) { - if let sheet = hostingController.presentationController as? UISheetPresentationController { + if let sheet = hostingController.presentationController as? UISheetPresentationController { - if #available(iOS 16.0, *) { - let targetSize = getTargetSizeForPresentationView(on: viewController) - sheet.detents = [.custom { _ in targetSize.height }] - } else { - sheet.detents = [.large()] + if #available(iOS 16.0, *) { + let targetSize = getTargetSizeForPresentationView(on: viewController) + sheet.detents = [.custom { _ in targetSize.height }] + } else { + sheet.detents = [.large()] - } } } } diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index 261e1d912b..1ec899ca54 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -76,6 +76,7 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { static let openDuckPlayer = "openDuckPlayer" static let sendDuckPlayerPixel = "sendDuckPlayerPixel" static let initialSetup = "initialSetup" + static let openInfo = "openInfo" } weak var broker: UserScriptMessageBroker? @@ -108,6 +109,8 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { return handleSendJSPixel case Handlers.initialSetup: return duckPlayer.initialSetupOverlay + case Handlers.openInfo: + return duckPlayer.openDuckPlayerInfo default: assertionFailure("YoutubeOverlayUserScript: Failed to parse User Script message: \(methodName)") // TODO: Send pixel here @@ -158,11 +161,6 @@ extension YoutubeOverlayUserScript { switch pixelName { case "play.use": Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromYoutubeViaMainOverlay) - - if let installDate = statisticsStore.installDate, - installDate > Date.yearAgo { - UniquePixel.fire(pixel: Pixel.Event.watchInDuckPlayerInitial) - } case "play.do_not_use": Pixel.fire(pixel: Pixel.Event.duckPlayerOverlayYoutubeWatchHere) diff --git a/DuckDuckGo/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/EventMapping+NetworkProtectionError.swift index 11ad29014e..684ccafcf7 100644 --- a/DuckDuckGo/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/EventMapping+NetworkProtectionError.swift @@ -17,8 +17,6 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import Foundation import NetworkProtection import Common @@ -102,5 +100,3 @@ extension EventMapping where Event == NetworkProtectionError { DailyPixel.fireDailyAndCount(pixel: pixelEvent, error: pixelError, withAdditionalParameters: params) } } - -#endif diff --git a/DuckDuckGo/Favicons.swift b/DuckDuckGo/Favicons.swift index e38acc128c..567a617e5b 100644 --- a/DuckDuckGo/Favicons.swift +++ b/DuckDuckGo/Favicons.swift @@ -395,12 +395,8 @@ public class Favicons { } } - if #available(iOS 15.0, *) { - let request = URLRequest.userInitiated(url) - metadataFetcher.startFetchingMetadata(for: request, completionHandler: completion) - } else { - metadataFetcher.startFetchingMetadata(for: url, completionHandler: completion) - } + let request = URLRequest.userInitiated(url) + metadataFetcher.startFetchingMetadata(for: request, completionHandler: completion) } private func retrieveBestImage(from urls: [URL], completion: @escaping (UIImage?) -> Void) { diff --git a/DuckDuckGo/FavoritesEmptyStateView.swift b/DuckDuckGo/FavoritesEmptyStateView.swift index 5e36f1b6cf..02c2beae1b 100644 --- a/DuckDuckGo/FavoritesEmptyStateView.swift +++ b/DuckDuckGo/FavoritesEmptyStateView.swift @@ -23,11 +23,14 @@ struct FavoritesEmptyStateView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.isLandscapeOrientation) var isLandscape - @State var headerPadding: CGFloat = 10 + @State private var headerPadding: CGFloat = 10 + + @Binding var isShowingTooltip: Bool var body: some View { + ZStack(alignment: .topTrailing) { VStack(spacing: 16) { - FavoritesSectionHeader() + FavoritesSectionHeader(isShowingTooltip: $isShowingTooltip) .padding(.horizontal, headerPadding) NewTabPageGridView { placeholdersCount in @@ -49,11 +52,19 @@ struct FavoritesEmptyStateView: View { self.headerPadding = spacingSize / 2 }) } + + if isShowingTooltip { + FavoritesTooltip() + .offset(x: -headerPadding + 18, y: 24) + .frame(maxWidth: .infinity, alignment: .bottomTrailing) + } + } } } #Preview { - FavoritesEmptyStateView() + @State var isShowingTooltip = false + return FavoritesEmptyStateView(isShowingTooltip: $isShowingTooltip) } private struct WidthKey: PreferenceKey { diff --git a/DuckDuckGo/FavoritesSectionHeader.swift b/DuckDuckGo/FavoritesSectionHeader.swift index e190400aec..ce13182c1c 100644 --- a/DuckDuckGo/FavoritesSectionHeader.swift +++ b/DuckDuckGo/FavoritesSectionHeader.swift @@ -21,22 +21,29 @@ import SwiftUI import DesignResourcesKit struct FavoritesSectionHeader: View { + + @Binding var isShowingTooltip: Bool + var body: some View { HStack(spacing: 16, content: { Text("Favorites") .font(.system(size: 15, weight: .semibold)) .foregroundColor(Color(designSystemColor: .textPrimary)) .frame(alignment: .leading) - + Spacer() - Button(action: {}, label: { + Button(action: { + isShowingTooltip.toggle() + }, label: { Image(.info12) - }).tintIfAvailable(Color(designSystemColor: .textPrimary)) + .foregroundStyle(Color(designSystemColor: .textPrimary)) + }) }) } } #Preview { - FavoritesSectionHeader() + @State var isShowingTooltip = true + return FavoritesSectionHeader(isShowingTooltip: $isShowingTooltip) } diff --git a/DuckDuckGo/FavoritesTooltip.swift b/DuckDuckGo/FavoritesTooltip.swift new file mode 100644 index 0000000000..3b408f34e2 --- /dev/null +++ b/DuckDuckGo/FavoritesTooltip.swift @@ -0,0 +1,48 @@ +// +// FavoritesTooltip.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 SwiftUI +import DuckUI + +struct FavoritesTooltip: View { + var body: some View { + Group { + Text(.init("\(UserText.newTabPageTooltipBody)")) + .daxBodyRegular() + .foregroundStyle(Color(designSystemColor: .textPrimary)) + .padding(16) + .frame(maxWidth: 300) + } + .background(Color(designSystemColor: .surface)) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .background(alignment: .topTrailing) { + Triangle() + .fill(Color(designSystemColor: .surface)) + .frame(width: 16, height: 8) + .offset(x: -8, y: -8) + } + .shadow(color: .shade(0.1), radius: 2, y: 2) + .shadow(color: .shade(0.08), radius: 1.5, y: 0) + .padding(.horizontal, 8) + } +} + +#Preview { + FavoritesTooltip() +} diff --git a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift index b74eca8b17..b7562586b9 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift @@ -17,12 +17,9 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import SwiftUI import NetworkProtection -@available(iOS 15.0, *) struct VPNFeedbackFormCategoryView: View { @Environment(\.dismiss) private var dismiss let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver) @@ -78,7 +75,6 @@ struct VPNFeedbackFormCategoryView: View { } } -@available(iOS 15.0, *) struct VPNFeedbackFormView: View { @StateObject var viewModel: VPNFeedbackFormViewModel @Environment(\.dismiss) private var dismiss @@ -230,5 +226,3 @@ private struct VPNFeedbackFormButtonStyle: ButtonStyle { } } - -#endif diff --git a/DuckDuckGo/FireButtonAnimator.swift b/DuckDuckGo/FireButtonAnimator.swift index b5aca8dc41..84b9f82a39 100644 --- a/DuckDuckGo/FireButtonAnimator.swift +++ b/DuckDuckGo/FireButtonAnimator.swift @@ -112,7 +112,7 @@ class FireButtonAnimator { func animate(onAnimationStart: @escaping () async -> Void, onTransitionCompleted: @escaping () async -> Void, completion: @escaping () async -> Void) { - guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first, + guard let window = UIApplication.shared.firstKeyWindow, let snapshot = window.snapshotView(afterScreenUpdates: false) else { Task { @MainActor in await onAnimationStart() diff --git a/DuckDuckGo/HomeMessageView.swift b/DuckDuckGo/HomeMessageView.swift index 66f2a0895b..a4067808fa 100644 --- a/DuckDuckGo/HomeMessageView.swift +++ b/DuckDuckGo/HomeMessageView.swift @@ -49,8 +49,6 @@ struct HomeMessageView: View { ZStack(alignment: .topTrailing) { VStack(spacing: 8) { Group { - topText - if case .promoSingleAction = viewModel.modelType { title .daxTitle3() @@ -114,17 +112,6 @@ struct HomeMessageView: View { .contentShape(Rectangle()) } - private var topText: some View { - Group { - if let topText = viewModel.topText { - Text(topText) - .font(Font(uiFont: Const.Font.topText)) - } else { - EmptyView() - } - } - } - private var image: some View { Group { if let image = viewModel.image { @@ -144,7 +131,7 @@ struct HomeMessageView: View { @ViewBuilder private var subtitle: some View { - if #available(iOS 15, *), let attributed = try? AttributedString(markdown: viewModel.subtitle) { + if let attributed = try? AttributedString(markdown: viewModel.subtitle) { Text(attributed) .daxBodyRegular() } else { diff --git a/DuckDuckGo/HomeMessageViewModel.swift b/DuckDuckGo/HomeMessageViewModel.swift index 4acfe4ead3..e728fa1fc6 100644 --- a/DuckDuckGo/HomeMessageViewModel.swift +++ b/DuckDuckGo/HomeMessageViewModel.swift @@ -49,11 +49,6 @@ struct HomeMessageViewModel { } } - var topText: String? { - // actually unused! - return nil - } - var title: String { switch modelType { case .small(let titleText, _): diff --git a/DuckDuckGo/HomeMessageViewSectionRenderer.swift b/DuckDuckGo/HomeMessageViewSectionRenderer.swift index a0d8df650a..3048d865ae 100644 --- a/DuckDuckGo/HomeMessageViewSectionRenderer.swift +++ b/DuckDuckGo/HomeMessageViewSectionRenderer.swift @@ -119,7 +119,16 @@ class HomeMessageViewSectionRenderer: NSObject, HomeViewSectionRenderer { params } case .remoteMessage(let remoteMessage): + let onDidAppear = { [weak self] in + self?.homePageConfiguration.didAppear(message) + } + + // call didAppear here to support marking messages as shown when they appear on the new tab page + // as a result of refreshing a config while the user was on a new tab page already. + onDidAppear() + return HomeMessageViewModelBuilder.build(for: remoteMessage, with: privacyProDataReporter) { [weak self] action in + guard let action, let self else { return } @@ -160,8 +169,8 @@ class HomeMessageViewSectionRenderer: NSObject, HomeViewSectionRenderer { } } - } onDidAppear: { [weak self] in - self?.homePageConfiguration.didAppear(message) + } onDidAppear: { + onDidAppear() } } } diff --git a/DuckDuckGo/ImportPasswordsView.swift b/DuckDuckGo/ImportPasswordsView.swift index fcb69b232a..c4964edb57 100644 --- a/DuckDuckGo/ImportPasswordsView.swift +++ b/DuckDuckGo/ImportPasswordsView.swift @@ -110,7 +110,7 @@ struct ImportPasswordsView: View { ForEach(ImportPasswordsViewModel.InstructionStep.allCases, id: \.self) { step in - if #available(iOS 15.0, *), step == .step2 || step == .step3 { + if step == .step2 || step == .step3 { Instruction(step: step.rawValue, instructionText: attributedText(viewModel.attributedInstructionsForStep(step))) } else { Instruction(step: step.rawValue, instructionText: Text(viewModel.instructionsForStep(step))) @@ -120,7 +120,6 @@ struct ImportPasswordsView: View { } - @available(iOS 15.0, *) func attributedText(_ string: AttributedString) -> Text { return Text(string) } diff --git a/DuckDuckGo/ImportPasswordsViewModel.swift b/DuckDuckGo/ImportPasswordsViewModel.swift index e70f5baab1..480eb27ef4 100644 --- a/DuckDuckGo/ImportPasswordsViewModel.swift +++ b/DuckDuckGo/ImportPasswordsViewModel.swift @@ -102,7 +102,6 @@ final class ImportPasswordsViewModel { return step.instructions() } - @available(iOS 15.0, *) func attributedInstructionsForStep(_ step: InstructionStep) -> AttributedString { let semiboldFont = Font.system(.body).weight(.semibold) var attributedString = AttributedString(step.instructions()) @@ -141,7 +140,6 @@ private extension String { } -@available(iOS 15, *) private extension AttributedString { mutating func applyFontStyle(forSubstring substring: String, withFont font: Font) { diff --git a/DuckDuckGo/InlineWKDownloadDelegate.swift b/DuckDuckGo/InlineWKDownloadDelegate.swift index b0e803133a..2eb4f5f880 100644 --- a/DuckDuckGo/InlineWKDownloadDelegate.swift +++ b/DuckDuckGo/InlineWKDownloadDelegate.swift @@ -20,7 +20,6 @@ import Foundation import WebKit -@available(iOS 14.5, *) final class InlineWKDownloadDelegate: NSObject, WKDownloadDelegate { var decideDestinationCallback: ((WKDownload, URLResponse, String, @escaping (URL?) -> Void) -> Void)? diff --git a/DuckDuckGo/KeyboardSettingsViewController.swift b/DuckDuckGo/KeyboardSettingsViewController.swift index a54f395e11..8cca55d4af 100644 --- a/DuckDuckGo/KeyboardSettingsViewController.swift +++ b/DuckDuckGo/KeyboardSettingsViewController.swift @@ -45,20 +45,10 @@ class KeyboardSettingsViewController: UITableViewController { @IBAction func onNewTabValueChanged(_ sender: Any) { settings.onNewTab = newTabToggle.isOn - if settings.onNewTab { - Pixel.fire(pixel: .settingsKeyboardOnNewTabOn) - } else { - Pixel.fire(pixel: .settingsKeyboardOnNewTabOff) - } } @IBAction func onAppLaunchValueChanged(_ sender: Any) { settings.onAppLaunch = appLaunchToggle.isOn - if settings.onAppLaunch { - Pixel.fire(pixel: .settingsKeyboardOnAppLaunchOn) - } else { - Pixel.fire(pixel: .settingsKeyboardOnAppLaunchOff) - } } } diff --git a/DuckDuckGo/MainViewController+Email.swift b/DuckDuckGo/MainViewController+Email.swift index 4209cf50dc..8b4bb3421b 100644 --- a/DuckDuckGo/MainViewController+Email.swift +++ b/DuckDuckGo/MainViewController+Email.swift @@ -85,15 +85,13 @@ extension MainViewController: EmailManagerAliasPermissionDelegate { didRequestPermissionToProvideAliasWithCompletion(addressType, autosave) } - if #available(iOS 15.0, *) { - if let presentationController = emailAddressPromptViewController.presentationController as? UISheetPresentationController { - if #available(iOS 16.0, *) { - presentationController.detents = [.custom(resolver: { _ in - AutofillViews.emailSignupPromptMinHeight - })] - } else { - presentationController.detents = [.medium()] - } + if let presentationController = emailAddressPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.emailSignupPromptMinHeight + })] + } else { + presentationController.detents = [.medium()] } } self.present(emailAddressPromptViewController, animated: true) @@ -115,15 +113,13 @@ extension MainViewController: EmailManagerAliasPermissionDelegate { } } - if #available(iOS 15.0, *) { - if let presentationController = emailSignupPromptViewController.presentationController as? UISheetPresentationController { - if #available(iOS 16.0, *) { - presentationController.detents = [.custom(resolver: { _ in - AutofillViews.emailSignupPromptMinHeight - })] - } else { - presentationController.detents = [.medium()] - } + if let presentationController = emailSignupPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.emailSignupPromptMinHeight + })] + } else { + presentationController.detents = [.medium()] } } diff --git a/DuckDuckGo/MainViewController+KeyCommands.swift b/DuckDuckGo/MainViewController+KeyCommands.swift index 695be22846..45f499a9b6 100644 --- a/DuckDuckGo/MainViewController+KeyCommands.swift +++ b/DuckDuckGo/MainViewController+KeyCommands.swift @@ -111,10 +111,8 @@ extension MainViewController { ] let commands = [alwaysAvailable, browsingCommands, findInPageCommands, arrowKeys, other].flatMap { $0 } - if #available(iOS 15, *) { - commands.forEach { - $0.wantsPriorityOverSystemBehavior = true - } + commands.forEach { + $0.wantsPriorityOverSystemBehavior = true } return commands } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index fe17899cfb..e21863d9a7 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -33,10 +33,7 @@ import Networking import Suggestions import Subscription import SwiftUI - -#if NETWORK_PROTECTION import NetworkProtection -#endif class MainViewController: UIViewController { @@ -114,11 +111,8 @@ class MainViewController: UIViewController { private var emailCancellables = Set() private var urlInterceptorCancellables = Set() private var settingsDeepLinkcancellables = Set() - -#if NETWORK_PROTECTION private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults private var vpnCancellables = Set() -#endif let privacyProDataReporter: PrivacyProDataReporting @@ -151,7 +145,7 @@ class MainViewController: UIViewController { } var searchBarRect: CGRect { - let view = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first?.rootViewController?.view + let view = UIApplication.shared.firstKeyWindow?.rootViewController?.view return viewCoordinator.omniBar.searchContainer.convert(viewCoordinator.omniBar.searchContainer.bounds, to: view) } @@ -268,10 +262,7 @@ class MainViewController: UIViewController { subscribeToEmailProtectionStatusNotifications() subscribeToURLInterceptorNotifications() subscribeToSettingsDeeplinkNotifications() - -#if NETWORK_PROTECTION subscribeToNetworkProtectionEvents() -#endif findInPageView.delegate = self findInPageBottomLayoutConstraint.constant = 0 @@ -1376,7 +1367,6 @@ class MainViewController: UIViewController { .store(in: &settingsDeepLinkcancellables) } -#if NETWORK_PROTECTION private func subscribeToNetworkProtectionEvents() { NotificationCenter.default.publisher(for: .accountDidSignIn) .receive(on: DispatchQueue.main) @@ -1477,7 +1467,6 @@ class MainViewController: UIViewController { await networkProtectionTunnelController.removeVPN(reason: .signedOut) } } -#endif @objc private func onDuckDuckGoEmailSignIn(_ notification: Notification) { @@ -2674,17 +2663,10 @@ extension MainViewController { private func historyMenuButton(with menuHistoryItemList: [BackForwardMenuHistoryItem]) -> [UIAction] { let menuItems: [UIAction] = menuHistoryItemList.compactMap { historyItem in - if #available(iOS 15.0, *) { - return UIAction(title: historyItem.title, - subtitle: historyItem.sanitizedURLForDisplay, - discoverabilityTitle: historyItem.sanitizedURLForDisplay) { [weak self] _ in - self?.loadBackForwardItem(historyItem.backForwardItem) - } - } else { - return UIAction(title: historyItem.title, - discoverabilityTitle: historyItem.sanitizedURLForDisplay) { [weak self] _ in - self?.loadBackForwardItem(historyItem.backForwardItem) - } + return UIAction(title: historyItem.title, + subtitle: historyItem.sanitizedURLForDisplay, + discoverabilityTitle: historyItem.sanitizedURLForDisplay) { [weak self] _ in + self?.loadBackForwardItem(historyItem.backForwardItem) } } diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index f6a65bd9ae..d44f9dbc10 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -17,8 +17,6 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import NetworkProtection import UIKit import Common @@ -104,5 +102,3 @@ extension NetworkProtectionVPNLocationViewModel { ) } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionDNSSettingsView.swift b/DuckDuckGo/NetworkProtectionDNSSettingsView.swift index 415226eeba..33698a2a83 100644 --- a/DuckDuckGo/NetworkProtectionDNSSettingsView.swift +++ b/DuckDuckGo/NetworkProtectionDNSSettingsView.swift @@ -17,12 +17,9 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import SwiftUI import NetworkProtection -@available(iOS 15.0, *) struct NetworkProtectionDNSSettingsView: View { @StateObject var viewModel = NetworkProtectionDNSSettingsViewModel(settings: VPNSettings(defaults: .networkProtectionGroupDefaults)) @Environment(\.dismiss) private var dismiss @@ -110,7 +107,6 @@ struct NetworkProtectionDNSSettingsView: View { } } -@available(iOS 15, *) private struct ChecklistItem: View where Content: View { let isSelected: Bool let action: () -> Void @@ -135,5 +131,3 @@ private struct ChecklistItem: View where Content: View { .listRowInsets(EdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16)) } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionDebugUtilities.swift b/DuckDuckGo/NetworkProtectionDebugUtilities.swift index 5c5656d55c..6b29b9ab1e 100644 --- a/DuckDuckGo/NetworkProtectionDebugUtilities.swift +++ b/DuckDuckGo/NetworkProtectionDebugUtilities.swift @@ -19,8 +19,6 @@ import Common import Foundation - -#if NETWORK_PROTECTION import NetworkProtection import NetworkExtension @@ -98,5 +96,3 @@ private extension NetworkProtectionSimulationOption { } } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index d988ef34a8..c48a3e55f3 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -18,15 +18,6 @@ // import UIKit - -#if !NETWORK_PROTECTION - -final class NetworkProtectionDebugViewController: UITableViewController { - // Just an empty VC -} - -#else - import Common import Network import NetworkExtension @@ -432,7 +423,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { Supports IPv6: \(path.supportsIPv6) """ - if #available(iOS 14.2, *), path.status == .unsatisfied { + if path.status == .unsatisfied { pathDescription.append("\nUnsatisfied Reason: \(path.unsatisfiedReason)") } @@ -738,5 +729,3 @@ extension NWConnection { } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionRootView.swift b/DuckDuckGo/NetworkProtectionRootView.swift index 1d23f301a9..70a936e10f 100644 --- a/DuckDuckGo/NetworkProtectionRootView.swift +++ b/DuckDuckGo/NetworkProtectionRootView.swift @@ -17,13 +17,10 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import SwiftUI import NetworkProtection import Subscription -@available(iOS 15, *) struct NetworkProtectionRootView: View { let statusViewModel: NetworkProtectionStatusViewModel @@ -44,5 +41,3 @@ struct NetworkProtectionRootView: View { } } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionRootViewController.swift b/DuckDuckGo/NetworkProtectionRootViewController.swift index 013f6132bc..07467a8bf1 100644 --- a/DuckDuckGo/NetworkProtectionRootViewController.swift +++ b/DuckDuckGo/NetworkProtectionRootViewController.swift @@ -17,11 +17,8 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import SwiftUI -@available(iOS 15, *) final class NetworkProtectionRootViewController: UIHostingController { init() { @@ -40,7 +37,6 @@ final class NetworkProtectionRootViewController: UIHostingController Void @@ -145,7 +141,6 @@ private struct CountryItem: View { } } -@available(iOS 15, *) private struct ChecklistItem: View where Content: View { let isSelected: Bool let action: () -> Void @@ -170,7 +165,6 @@ private struct ChecklistItem: View where Content: View { } } -@available(iOS 15, *) private struct MenuItem: View { let isSelected: Bool let title: String @@ -194,5 +188,3 @@ private struct MenuItem: View { .tint(Color(designSystemColor: .textPrimary)) } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionVPNLocationViewModel.swift b/DuckDuckGo/NetworkProtectionVPNLocationViewModel.swift index dd6137928f..b87bd2e064 100644 --- a/DuckDuckGo/NetworkProtectionVPNLocationViewModel.swift +++ b/DuckDuckGo/NetworkProtectionVPNLocationViewModel.swift @@ -17,8 +17,6 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import Foundation import Combine import NetworkProtection @@ -164,5 +162,3 @@ struct NetworkProtectionVPNCityItemModel: Identifiable { self.isSelected = isSelected } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionVPNSettingsView.swift b/DuckDuckGo/NetworkProtectionVPNSettingsView.swift index 06a265e736..6d8ab17c8c 100644 --- a/DuckDuckGo/NetworkProtectionVPNSettingsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNSettingsView.swift @@ -17,12 +17,9 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import SwiftUI import DesignResourcesKit -@available(iOS 15, *) struct NetworkProtectionVPNSettingsView: View { @StateObject var viewModel = NetworkProtectionVPNSettingsViewModel() @@ -177,5 +174,3 @@ private extension WidgetEducationView { ) } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift b/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift index 60dc380107..4866dd6055 100644 --- a/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift +++ b/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift @@ -17,8 +17,6 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import Foundation import UserNotifications import NetworkProtection @@ -104,5 +102,3 @@ extension NetworkProtectionVPNSettingsViewModel: NotificationsPermissionsControl updateViewKind(for: status) } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift index 92f6bb6cbf..1944d2a806 100644 --- a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift @@ -17,8 +17,6 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import Foundation import Subscription @@ -46,5 +44,3 @@ struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVis return accountManager.isUserAuthenticated } } - -#endif diff --git a/DuckDuckGo/NetworkProtectionWidgetRefreshModel.swift b/DuckDuckGo/NetworkProtectionWidgetRefreshModel.swift index b9b5871ffc..0e8fcfe2b3 100644 --- a/DuckDuckGo/NetworkProtectionWidgetRefreshModel.swift +++ b/DuckDuckGo/NetworkProtectionWidgetRefreshModel.swift @@ -17,8 +17,6 @@ // limitations under the License. // -#if NETWORK_PROTECTION - import Foundation import Combine import NetworkExtension @@ -42,5 +40,3 @@ class NetworkProtectionWidgetRefreshModel { } } - -#endif diff --git a/DuckDuckGo/NewTabPageMessagesModel.swift b/DuckDuckGo/NewTabPageMessagesModel.swift index bae89bebd0..6be35faf8e 100644 --- a/DuckDuckGo/NewTabPageMessagesModel.swift +++ b/DuckDuckGo/NewTabPageMessagesModel.swift @@ -84,6 +84,11 @@ final class NewTabPageMessagesModel: ObservableObject { params } case .remoteMessage(let remoteMessage): + + // call didAppear here to support marking messages as shown when they appear on the new tab page + // as a result of refreshing a config while the user was on a new tab page already. + didAppear(message) + return HomeMessageViewModelBuilder.build(for: remoteMessage, with: privacyProDataReporter) { [weak self] action in guard let action, let self else { return } @@ -126,7 +131,7 @@ final class NewTabPageMessagesModel: ObservableObject { } } onDidAppear: { [weak self] in - self?.homePageMessagesConfiguration.didAppear(message) + self?.didAppear(message) } } } diff --git a/DuckDuckGo/NewTabPageView.swift b/DuckDuckGo/NewTabPageView.swift index 0c3af6f7fe..3285ce22a7 100644 --- a/DuckDuckGo/NewTabPageView.swift +++ b/DuckDuckGo/NewTabPageView.swift @@ -28,6 +28,8 @@ struct NewTabPageView: View { @ObservedObject private var favoritesModel: FavoritesModelType @ObservedObject private var shortcutsModel: ShortcutsModel + @State var isShowingTooltip: Bool = false + init(messagesModel: NewTabPageMessagesModel, favoritesModel: FavoritesModelType, shortcutsModel: ShortcutsModel) { self.messagesModel = messagesModel self.favoritesModel = favoritesModel @@ -49,7 +51,7 @@ struct NewTabPageView: View { // MARK: Favorites if favoritesModel.isEmpty { - FavoritesEmptyStateView() + FavoritesEmptyStateView(isShowingTooltip: $isShowingTooltip) .padding(Constant.sectionPadding) } else { FavoritesView(model: favoritesModel) @@ -81,6 +83,11 @@ struct NewTabPageView: View { .frame(minHeight: proxy.frame(in: .local).size.height) } .background(Color(designSystemColor: .background)) + .if(isShowingTooltip, transform: { + $0.highPriorityGesture(DragGesture(minimumDistance: 0, coordinateSpace: .global).onEnded { _ in + isShowingTooltip = false + }) + }) } } } diff --git a/DuckDuckGo/OngoingDownloadRow.swift b/DuckDuckGo/OngoingDownloadRow.swift index 435a5fab4f..a0ace4aa57 100644 --- a/DuckDuckGo/OngoingDownloadRow.swift +++ b/DuckDuckGo/OngoingDownloadRow.swift @@ -83,7 +83,7 @@ struct ProgressCircle: View { .stroke(style: StrokeStyle(lineWidth: Const.Size.progressStrokeWidth, lineCap: .butt, lineJoin: .miter)) .foregroundColor(.progressFill) .rotationEffect(Angle(degrees: 270.0)) - .animation(.linear) + .animation(.linear, value: self.progress) } } } diff --git a/DuckDuckGo/PasswordGenerationPromptView.swift b/DuckDuckGo/PasswordGenerationPromptView.swift index 8c01fdabf6..6743d47f12 100644 --- a/DuckDuckGo/PasswordGenerationPromptView.swift +++ b/DuckDuckGo/PasswordGenerationPromptView.swift @@ -169,11 +169,7 @@ private extension View { @ViewBuilder func textSelectionEnabled() -> some View { - if #available(iOS 15.0, *) { - self.textSelection(.enabled) - } else { - self - } + self.textSelection(.enabled) } } diff --git a/DuckDuckGo/PreserveLoginsWorker.swift b/DuckDuckGo/PreserveLoginsWorker.swift index 998ca9147f..54e98f8f58 100644 --- a/DuckDuckGo/PreserveLoginsWorker.swift +++ b/DuckDuckGo/PreserveLoginsWorker.swift @@ -35,7 +35,7 @@ struct PreserveLoginsWorker { if isAutofillEnabled && autofillShouldBlockPrompt(saveLoginPromptLastDismissed, saveLoginPromptIsPresenting: saveLoginPromptIsPresenting) { return false } - if let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first, window.subviews.contains(where: { $0 is ActionMessageView }) { + if let window = UIApplication.shared.firstKeyWindow, window.subviews.contains(where: { $0 is ActionMessageView }) { // if an ActionMessageView is currently displayed wait before prompting to fireproof DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { self.promptToFireproof(domain) diff --git a/DuckDuckGo/RemoteMessagingDebugViewController.swift b/DuckDuckGo/RemoteMessagingDebugViewController.swift index 5527132af4..ac4383e2d7 100644 --- a/DuckDuckGo/RemoteMessagingDebugViewController.swift +++ b/DuckDuckGo/RemoteMessagingDebugViewController.swift @@ -21,6 +21,7 @@ import UIKit import SwiftUI import RemoteMessaging import Core +import CoreData import Combine import Persistence @@ -38,33 +39,48 @@ struct RemoteMessagingDebugRootView: View { var body: some View { List { - Section { - ForEach(model.messages, id: \.id) { entry in - VStack(alignment: .leading) { - Text(entry.id ?? "") - .font(.system(size: 14)) - Text(entry.message ?? "") - .font(.system(size: 12)) - Text("Shown: \(String(describing: entry.shown))") - .font(.system(size: 10)) - Text("Status: \(statusString(for: entry.status))") - .font(.system(size: 10)) + if !model.messages.isEmpty { + Section { + ForEach(model.messages, id: \.id) { message in + VStack(alignment: .leading, spacing: 6) { + Text("ID: \(message.id) | \(message.shown) | \(message.status)") + .font(.system(size: 15)) + Text(message.json ?? "") + .font(.system(size: 12)) + .foregroundStyle(Color.gray70) + } } + } footer: { + Text("This list contains messages that have been shown plus at most 1 message that is scheduled for showing. There may be more messages in the config that will be presented, but they haven't been processed yet.") } - } footer: { - Text("This list contains messages that have been shown plus at most 1 message that is scheduled for showing. There may be more messages in the config that will be presented, but they haven't been processed yet.") + } + Section { + Button("Refresh Config", action: model.refreshConfig) } } - .navigationTitle("\(model.messages.count) Remote Messages") + .navigationTitle("\(model.messages.count) Remote Message(s)") .toolbar { - Button("Delete All", role: .destructive) { - model.deleteAll() - } + Button("Delete All", role: .destructive, action: model.deleteAll) + .disabled(model.messages.isEmpty) } } +} + +struct MessageDebugModel { + var id: String + var shown: String + var status: String + var json: String? + + init(_ message: RemoteMessageManagedObject) { + id = message.id ?? "?" + shown = message.shown ? "shown" : "not shown" + status = Self.statusString(for: message.status) + json = message.message + } /// This should be kept in sync with `RemoteMessageStatus` private enum from BSK - private func statusString(for status: NSNumber?) -> String { + private static func statusString(for status: NSNumber?) -> String { switch status?.int16Value { case 0: return "scheduled" @@ -80,17 +96,24 @@ struct RemoteMessagingDebugRootView: View { class RemoteMessagingDebugViewModel: ObservableObject { - @Published var messages: [RemoteMessageManagedObject] = [] + @Published var messages: [MessageDebugModel] = [] let database: CoreDataDatabase init() { database = Database.shared fetchMessages() + + notificationCancellable = NotificationCenter.default.publisher(for: RemoteMessagingStore.Notifications.remoteMessagesDidChange) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.fetchMessages() + } } func deleteAll() { let context = database.makeContext(concurrencyType: .mainQueueConcurrencyType) + context.refreshAllObjects() context.deleteAll(entityDescriptions: [ RemoteMessageManagedObject.entity(in: context), RemoteMessagingConfigManagedObject.entity(in: context) @@ -104,10 +127,17 @@ class RemoteMessagingDebugViewModel: ObservableObject { fetchMessages() } + func refreshConfig() { + (UIApplication.shared.delegate as? AppDelegate)?.refreshRemoteMessages() + } + func fetchMessages() { let context = database.makeContext(concurrencyType: .mainQueueConcurrencyType) + context.refreshAllObjects() let fetchRequest = RemoteMessageManagedObject.fetchRequest() fetchRequest.returnsObjectsAsFaults = false - messages = (try? context.fetch(fetchRequest)) ?? [] + messages = ((try? context.fetch(fetchRequest)) ?? []).map(MessageDebugModel.init) } + + private var notificationCancellable: AnyCancellable? } diff --git a/DuckDuckGo/RemoteMessagingStoreErrorHandling.swift b/DuckDuckGo/RemoteMessagingStoreErrorHandling.swift index eed4e00165..f273f8e1e8 100644 --- a/DuckDuckGo/RemoteMessagingStoreErrorHandling.swift +++ b/DuckDuckGo/RemoteMessagingStoreErrorHandling.swift @@ -29,16 +29,10 @@ public class RemoteMessagingStoreErrorHandling: EventMapping