diff --git a/.github/workflows/build_appstore.yml b/.github/workflows/build_appstore.yml index 3ed67cdd61..fe5fdb9add 100644 --- a/.github/workflows/build_appstore.yml +++ b/.github/workflows/build_appstore.yml @@ -26,6 +26,10 @@ on: description: "Asana release task URL" required: true type: string + branch: + description: "Branch name" + required: false + type: string secrets: SSH_PRIVATE_KEY_FASTLANE_MATCH: required: true @@ -60,9 +64,9 @@ jobs: - name: Assert release branch if: env.destination == 'appstore' run: | - case "${{ github.ref }}" in - refs/heads/release/*) ;; - refs/heads/hotfix/*) ;; + case "${{ inputs.branch || github.ref_name }}" in + release/*) ;; + hotfix/*) ;; *) echo "👎 Not a release or hotfix branch"; exit 1 ;; esac @@ -76,7 +80,7 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - ref: ${{ github.ref_name }} + ref: ${{ inputs.branch || github.ref_name }} - name: Select Xcode run: sudo xcode-select -s /Applications/Xcode_$(<.xcode-version).app/Contents/Developer @@ -104,7 +108,7 @@ jobs: echo "app_version=${version}.${build_number}" >> $GITHUB_ENV - name: Upload dSYMs artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: DuckDuckGo-${{ env.destination }}-dSYM-${{ env.app_version }} path: ${{ env.dsyms_path }} diff --git a/.github/workflows/build_notarized.yml b/.github/workflows/build_notarized.yml index e6486a1877..99fd243a0a 100644 --- a/.github/workflows/build_notarized.yml +++ b/.github/workflows/build_notarized.yml @@ -38,6 +38,10 @@ on: description: "Asana release task URL" required: true type: string + branch: + description: "Branch name" + required: false + type: string secrets: BUILD_CERTIFICATE_BASE64: required: true @@ -103,7 +107,7 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - ref: ${{ github.ref_name }} + ref: ${{ inputs.branch || github.ref_name }} - name: Install Apple Developer ID Application certificate uses: ./.github/actions/install-certs-and-profiles @@ -155,13 +159,13 @@ jobs: echo "app-name=${{ env.app-name }}" >> $GITHUB_OUTPUT - name: Upload app artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: DuckDuckGo-${{ env.release-type }}-${{ env.app-version }}.app path: ${{ github.workspace }}/release/DuckDuckGo-${{ env.app-version }}.zip - name: Upload dSYMs artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: DuckDuckGo-${{ env.release-type }}-dSYM-${{ env.app-version }} path: ${{ github.workspace }}/release/DuckDuckGo-${{ env.app-version }}-dSYM.zip @@ -205,7 +209,7 @@ jobs: steps: - name: Fetch app bundle - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: DuckDuckGo-${{ env.release-type }}-${{ env.app-version }}.app path: ${{ github.workspace }}/dmg @@ -234,7 +238,7 @@ jobs: "dmg" - name: Upload DMG artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: DuckDuckGo-${{ env.release-type }}-${{ env.app-version }}.dmg path: ${{ github.workspace }}/duckduckgo*-${{ env.app-version }}.dmg diff --git a/.github/workflows/code_freeze.yml b/.github/workflows/code_freeze.yml new file mode 100644 index 0000000000..7883f9200c --- /dev/null +++ b/.github/workflows/code_freeze.yml @@ -0,0 +1,140 @@ +name: Code Freeze + +on: + workflow_dispatch: + +jobs: + + create_release_branch: + + name: Create Release Branch + + runs-on: macos-13-xlarge + timeout-minutes: 10 + + outputs: + release_branch_name: ${{ steps.make_release_branch.outputs.release_branch_name }} + asana_task_url: ${{ steps.create_release_task.outputs.asana_task_url }} + + steps: + + - name: Assert main branch + run: | + if [ "${{ github.ref_name }}" != "main" ]; then + echo "👎 Not the main branch" + exit 1 + fi + + - name: Check out the code + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Prepare fastlane + run: bundle install + + - name: Make release branch + id: make_release_branch + env: + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + run: | + git config --global user.name "Dax the Duck" + git config --global user.email "dax@duckduckgo.com" + bundle exec fastlane make_release_branch + + - name: Create release task + id: create_release_task + env: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + run: | + version="$(echo ${{ steps.make_release_branch.outputs.release_branch_name }} | cut -d '/' -f 2)" + task_name="macOS App Release $version" + asana_task_id="$(curl -fLSs -X POST "https://app.asana.com/api/1.0/task_templates/${{ vars.MACOS_RELEASE_TASK_TEMPLATE_ID }}/instantiateTask" \ + -H "Authorization: Bearer ${{ env.ASANA_ACCESS_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d "{ \"data\": { \"name\": \"$task_name\" }}" \ + | jq -r .data.new_task.gid)" + echo "asana_task_url=https://app.asana.com/0/0/${asana_task_id}/f" >> $GITHUB_OUTPUT + + assignee_id="$(curl -fLSs https://raw.githubusercontent.com/duckduckgo/BrowserServicesKit/main/.github/actions/asana-failed-pr-checks/user_ids.json \ + | jq -r .${{ github.actor }})" + + curl -fLSs -X PUT "https://app.asana.com/api/1.0/tasks/${asana_task_id}" \ + -H "Authorization: Bearer ${{ env.ASANA_ACCESS_TOKEN }}" \ + -H "Content-Type: application/json" \ + --output /dev/null \ + -d "{ \"data\": { \"assignee\": \"$assignee_id\" }}" + + run_tests: + + name: Run Tests + + needs: create_release_branch + uses: ./.github/workflows/pr.yml + with: + branch: ${{ needs.create_release_branch.outputs.release_branch_name }} + secrets: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + + increment_build_number: + + name: Increment Build Number + + needs: [ create_release_branch, run_tests ] + runs-on: macos-13-xlarge + timeout-minutes: 10 + + steps: + + - name: Check out the code + uses: actions/checkout@v3 + with: + submodules: recursive + ref: ${{ needs.create_release_branch.outputs.release_branch_name }} + + - name: Prepare fastlane + run: bundle install + + - name: Increment build number + env: + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + run: | + git config --global user.name "Dax the Duck" + git config --global user.email "dax@duckduckgo.com" + bundle exec fastlane bump_internal_release update_embedded_files:false + + prepare_release: + name: Prepare Release + needs: [ create_release_branch, increment_build_number ] + uses: ./.github/workflows/release.yml + with: + asana-task-url: ${{ needs.create_release_branch.outputs.asana_task_url }} + branch: ${{ needs.create_release_branch.outputs.release_branch_name }} + secrets: + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} + RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_PROVISION_PROFILE_BASE64 }} + DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} + DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }} + NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }} + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + MM_HANDLES_BASE64: ${{ secrets.MM_HANDLES_BASE64 }} + MM_WEBHOOK_URL: ${{ secrets.MM_WEBHOOK_URL }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + SSH_PRIVATE_KEY_FASTLANE_MATCH: ${{ secrets.SSH_PRIVATE_KEY_FASTLANE_MATCH }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b3400f2850..4ad1e6db0a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,6 +5,11 @@ on: branches: [ main, "release/**" ] pull_request: workflow_call: + inputs: + branch: + description: "Branch name" + required: false + type: string secrets: ASANA_ACCESS_TOKEN: required: true @@ -38,7 +43,7 @@ jobs: if: github.event_name != 'pull_request' && github.event_name != 'push' uses: actions/checkout@v3 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.branch || github.ref_name }} - name: Run ShellCheck uses: ludeeus/action-shellcheck@master @@ -54,32 +59,24 @@ jobs: strategy: matrix: - flavor: [ "Sandbox", "Non-Sandbox", "DBP" ] + flavor: [ "Sandbox", "Non-Sandbox" ] include: - scheme: DuckDuckGo Privacy Browser flavor: Non-Sandbox - scheme: DuckDuckGo Privacy Browser App Store flavor: Sandbox - - scheme: DataBrokerProtectionTests - flavor: DBP - active-arch: YES flavor: Non-Sandbox - active-arch: NO flavor: Sandbox - - active-arch: YES - flavor: DBP - integration-tests-target: Integration Tests flavor: Non-Sandbox - integration-tests-target: Integration Tests App Store flavor: Sandbox - - integration-tests-target: Integration Tests - flavor: DBP - cache-key: flavor: Non-Sandbox - cache-key: sandbox- flavor: Sandbox - - cache-key: dbp- - flavor: DBP runs-on: macos-13-xlarge timeout-minutes: 30 @@ -100,7 +97,7 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - ref: ${{ github.ref_name }} + ref: ${{ inputs.branch || github.ref_name }} - name: Set cache key hash run: | @@ -144,7 +141,6 @@ jobs: || { mv "$(grep -m 1 '.*\.xcresult' ${{ matrix.flavor }}-unittests-xcodebuild.log | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" ./${{ matrix.flavor }}-unittests.xcresult && exit 1; } - name: Run integration tests - if: matrix.flavor != 'DBP' run: | set -o pipefail && xcodebuild test \ -scheme "${{ matrix.scheme }}" \ @@ -205,7 +201,7 @@ jobs: | xargs -L 1 ./scripts/report-failed-unit-test.sh - name: Upload failed unit tests log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: ${{ matrix.flavor }}-unittests-xcodebuild.log @@ -213,7 +209,7 @@ jobs: retention-days: 7 - name: Upload failed unit tests xcresult - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: ${{ matrix.flavor }}-unittests.xcresult @@ -221,16 +217,16 @@ jobs: retention-days: 7 - name: Upload failed integration tests log - uses: actions/upload-artifact@v3 - if: failure() && matrix.flavor != 'DBP' + uses: actions/upload-artifact@v4 + if: failure() with: name: ${{ matrix.flavor }}-integrationtests-xcodebuild.log path: ${{ matrix.flavor }}-integrationtests-xcodebuild.log retention-days: 7 - name: Upload failed integration tests xcresult - uses: actions/upload-artifact@v3 - if: failure() && matrix.flavor != 'DBP' + uses: actions/upload-artifact@v4 + if: failure() with: name: ${{ matrix.flavor }}-integrationtests.xcresult path: ${{ matrix.flavor }}-integrationtests.xcresult @@ -328,7 +324,7 @@ jobs: | xcbeautify - name: Upload failed test log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: release-xcodebuild.log diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 33b902609d..7ea14e28c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,10 @@ on: description: "Asana release task URL" required: true type: string + branch: + description: "Branch name" + required: false + type: string secrets: BUILD_CERTIFICATE_BASE64: required: true @@ -70,6 +74,7 @@ jobs: release-type: release create-dmg: true asana-task-url: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} + branch: ${{ inputs.branch }} secrets: BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} @@ -99,6 +104,7 @@ jobs: with: destination: appstore asana-task-url: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} + branch: ${{ inputs.branch }} secrets: SSH_PRIVATE_KEY_FASTLANE_MATCH: ${{ secrets.SSH_PRIVATE_KEY_FASTLANE_MATCH }} APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} diff --git a/Configuration/Tests/UnitTests.xcconfig b/Configuration/Tests/UnitTests.xcconfig index e81034ac32..de9f4010a0 100644 --- a/Configuration/Tests/UnitTests.xcconfig +++ b/Configuration/Tests/UnitTests.xcconfig @@ -17,7 +17,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -FEATURE_FLAGS = FEEDBACK NETWORK_PROTECTION +FEATURE_FLAGS = FEEDBACK NETWORK_PROTECTION DBP INFOPLIST_FILE = UnitTests/Info.plist PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.macos.browser.DuckDuckGoTests diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f5ebbddfeb..497db1cc7a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -214,7 +214,6 @@ 3706FACC293F65D500E42796 /* SaveCredentialsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8589063B267BCDC000D23B0D /* SaveCredentialsViewController.swift */; }; 3706FACD293F65D500E42796 /* PopUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBE0AA627B9B027003B37A8 /* PopUpButton.swift */; }; 3706FACE293F65D500E42796 /* SuggestionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABEE6A424AA0A7F0043105B /* SuggestionViewController.swift */; }; - 3706FACF293F65D500E42796 /* AddEditFavoriteWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7A27BBB8620038AD11 /* AddEditFavoriteWindow.swift */; }; 3706FAD1293F65D500E42796 /* VisitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E919E287872EA00AB6B62 /* VisitViewModel.swift */; }; 3706FAD2293F65D500E42796 /* Atb.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50352726A11F00758A2B /* Atb.swift */; }; 3706FAD3293F65D500E42796 /* DownloadsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87F26D5DA9B0062C350 /* DownloadsViewController.swift */; }; @@ -639,7 +638,7 @@ 3706FCBE293F65D500E42796 /* autoconsent-bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055C327A1BA1D001AC618 /* autoconsent-bundle.js */; }; 3706FCBF293F65D500E42796 /* ContentOverlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */; }; 3706FCC0293F65D500E42796 /* FindInPage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85A0117325AF2EDF00FA6A0C /* FindInPage.storyboard */; }; - 3706FCC1293F65D500E42796 /* HomePage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E7B27BBB8630038AD11 /* HomePage.storyboard */; }; + 3706FCC1293F65D500E42796 /* AddEditFavoriteViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E7B27BBB8630038AD11 /* AddEditFavoriteViewController.storyboard */; }; 3706FCC3293F65D500E42796 /* userscript.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055BE27A1BA1D001AC618 /* userscript.js */; }; 3706FCC4293F65D500E42796 /* fb-tds.json in Resources */ = {isa = PBXBuildFile; fileRef = EA4617EF273A28A700F110A2 /* fb-tds.json */; }; 3706FCC5293F65D500E42796 /* TabPreview.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAE8B101258A41C000E81239 /* TabPreview.storyboard */; }; @@ -667,7 +666,6 @@ 3706FCE2293F65D500E42796 /* dark-trackers-3.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439772754D55100B241FA /* dark-trackers-3.json */; }; 3706FCE3293F65D500E42796 /* dark-trackers-2.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439722754D55100B241FA /* dark-trackers-2.json */; }; 3706FCE4293F65D500E42796 /* Fire.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAB7320626DD0C37002FACF9 /* Fire.storyboard */; }; - 3706FCE5293F65D500E42796 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E8E27BBBBF10038AD11 /* Main.storyboard */; }; 3706FCE6293F65D500E42796 /* social_images in Resources */ = {isa = PBXBuildFile; fileRef = EA18D1C9272F0DC8006DC101 /* social_images */; }; 3706FCE7293F65D500E42796 /* shield-dot-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6E827E880A600036718 /* shield-dot-mouse-over.json */; }; 3706FCE9293F65D500E42796 /* fb-sdk.js in Resources */ = {isa = PBXBuildFile; fileRef = EAC80DDF271F6C0100BBF02D /* fb-sdk.js */; }; @@ -744,7 +742,6 @@ 3706FE14293F661700E42796 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; 3706FE15293F661700E42796 /* PrivacyIconViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA91F83827076F1900771A0D /* PrivacyIconViewModelTests.swift */; }; 3706FE16293F661700E42796 /* CSVImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723E0126B0003E00E14D75 /* CSVImporterTests.swift */; }; - 3706FE17293F661700E42796 /* BookmarkStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */; }; 3706FE19293F661700E42796 /* DeviceAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC16A427C488C900E00A38 /* DeviceAuthenticatorTests.swift */; }; 3706FE1A293F661700E42796 /* BrowserProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F641D27A8D3BD00E0C118 /* BrowserProfileTests.swift */; }; 3706FE1B293F661700E42796 /* PermissionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9F26A7BE0B0013B453 /* PermissionManagerTests.swift */; }; @@ -1194,6 +1191,10 @@ 4B7534CC2A1FD7EA00158A99 /* NetworkProtectionInviteDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D606C2A0B29FA00BCD287 /* NetworkProtectionInviteDialog.swift */; }; 4B7A57CF279A4EF300B1C70E /* ChromiumPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A57CE279A4EF300B1C70E /* ChromiumPreferences.swift */; }; 4B7A60A1273E0BE400BBDFEB /* WKWebsiteDataStoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A60A0273E0BE400BBDFEB /* WKWebsiteDataStoreExtension.swift */; }; + 4B81AD322B29507300706C96 /* DataBrokerProtectionPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B81AD312B29507300706C96 /* DataBrokerProtectionPixelTests.swift */; }; + 4B81AD332B29507300706C96 /* DataBrokerProtectionPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B81AD312B29507300706C96 /* DataBrokerProtectionPixelTests.swift */; }; + 4B81AD352B29512B00706C96 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = 4B81AD342B29512B00706C96 /* PixelKitTestingUtilities */; }; + 4B81AD372B29513100706C96 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = 4B81AD362B29513100706C96 /* PixelKitTestingUtilities */; }; 4B85A48028821CC500FC4C39 /* NSPasteboardItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B85A47F28821CC500FC4C39 /* NSPasteboardItemExtension.swift */; }; 4B8A4DFF27C83B29005F40E8 /* SaveIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */; }; 4B8A4E0127C8447E005F40E8 /* SaveIdentityPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4E0027C8447E005F40E8 /* SaveIdentityPopover.swift */; }; @@ -1335,7 +1336,6 @@ 4B9579A32AC7AE700062CA31 /* PopUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBE0AA627B9B027003B37A8 /* PopUpButton.swift */; }; 4B9579A42AC7AE700062CA31 /* NetworkProtectionInviteDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D606C2A0B29FA00BCD287 /* NetworkProtectionInviteDialog.swift */; }; 4B9579A52AC7AE700062CA31 /* SuggestionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABEE6A424AA0A7F0043105B /* SuggestionViewController.swift */; }; - 4B9579A72AC7AE700062CA31 /* AddEditFavoriteWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7A27BBB8620038AD11 /* AddEditFavoriteWindow.swift */; }; 4B9579A82AC7AE700062CA31 /* BWKeyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6216B129069BBF00386B2C /* BWKeyStorage.swift */; }; 4B9579A92AC7AE700062CA31 /* VisitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E919E287872EA00AB6B62 /* VisitViewModel.swift */; }; 4B9579AA2AC7AE700062CA31 /* AddressBarTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */; }; @@ -1915,7 +1915,7 @@ 4B957BFB2AC7AE700062CA31 /* ContentOverlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */; }; 4B957BFC2AC7AE700062CA31 /* FindInPage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85A0117325AF2EDF00FA6A0C /* FindInPage.storyboard */; }; 4B957BFD2AC7AE700062CA31 /* JSAlert.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EEC111E3294D06020086524F /* JSAlert.storyboard */; }; - 4B957BFE2AC7AE700062CA31 /* HomePage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E7B27BBB8630038AD11 /* HomePage.storyboard */; }; + 4B957BFE2AC7AE700062CA31 /* AddEditFavoriteViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E7B27BBB8630038AD11 /* AddEditFavoriteViewController.storyboard */; }; 4B957C002AC7AE700062CA31 /* userscript.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055BE27A1BA1D001AC618 /* userscript.js */; }; 4B957C012AC7AE700062CA31 /* fb-tds.json in Resources */ = {isa = PBXBuildFile; fileRef = EA4617EF273A28A700F110A2 /* fb-tds.json */; }; 4B957C022AC7AE700062CA31 /* TabPreview.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAE8B101258A41C000E81239 /* TabPreview.storyboard */; }; @@ -1943,7 +1943,6 @@ 4B957C1B2AC7AE700062CA31 /* dark-trackers-3.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439772754D55100B241FA /* dark-trackers-3.json */; }; 4B957C1C2AC7AE700062CA31 /* dark-trackers-2.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439722754D55100B241FA /* dark-trackers-2.json */; }; 4B957C1D2AC7AE700062CA31 /* Fire.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAB7320626DD0C37002FACF9 /* Fire.storyboard */; }; - 4B957C1E2AC7AE700062CA31 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E8E27BBBBF10038AD11 /* Main.storyboard */; }; 4B957C1F2AC7AE700062CA31 /* social_images in Resources */ = {isa = PBXBuildFile; fileRef = EA18D1C9272F0DC8006DC101 /* social_images */; }; 4B957C202AC7AE700062CA31 /* shield-dot-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6E827E880A600036718 /* shield-dot-mouse-over.json */; }; 4B957C222AC7AE700062CA31 /* fb-sdk.js in Resources */ = {isa = PBXBuildFile; fileRef = EAC80DDF271F6C0100BBF02D /* fb-sdk.js */; }; @@ -2137,7 +2136,6 @@ 7B1E819E27C8874900FF0E60 /* ContentOverlayPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819B27C8874900FF0E60 /* ContentOverlayPopover.swift */; }; 7B1E819F27C8874900FF0E60 /* ContentOverlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */; }; 7B1E81A027C8874900FF0E60 /* ContentOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */; }; - 7B20D5C62ADFEC6E0053C42A /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */; }; 7B2DDCF82A93A8BB0039D884 /* NetworkProtectionAppEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */; }; 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 7B2DDCFB2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; @@ -2153,7 +2151,6 @@ 7B5F9A752AE2BE4E002AEBC0 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B5F9A742AE2BE4E002AEBC0 /* PixelKit */; }; 7B8C083C2AE1268E00F4C67F /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B8C083B2AE1268E00F4C67F /* PixelKit */; }; 7B934C412A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; - 7B96D0DC2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */; }; 7BA4727D26F01BC400EAA165 /* CoreDataTestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292C42667104B00AD2C21 /* CoreDataTestUtilities.swift */; }; 7BA59C9B2AE18B49009A97B1 /* SystemExtensionManager in Frameworks */ = {isa = PBXBuildFile; productRef = 7BA59C9A2AE18B49009A97B1 /* SystemExtensionManager */; }; 7BA7CC392AD11E2D0042E5CE /* DuckDuckGoVPNAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */; }; @@ -2209,8 +2206,6 @@ 7BEEA5122AD1235B00A9E72B /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5112AD1235B00A9E72B /* NetworkProtectionIPC */; }; 7BEEA5142AD1236300A9E72B /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5132AD1236300A9E72B /* NetworkProtectionIPC */; }; 7BEEA5162AD1236E00A9E72B /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5152AD1236E00A9E72B /* NetworkProtectionUI */; }; - 7BF1A9D82AE054D300FCA683 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7BF1A9D72AE054D300FCA683 /* Info.plist */; }; - 7BF1A9DC2AE0551C00FCA683 /* DBPUnitTests.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; }; 7BFCB74E2ADE7E1A00DA3EA7 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BFCB74D2ADE7E1A00DA3EA7 /* PixelKit */; }; 7BFCB7502ADE7E2300DA3EA7 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BFCB74F2ADE7E2300DA3EA7 /* PixelKit */; }; 7BFE95522A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */; }; @@ -2234,13 +2229,11 @@ 85480FCF25D1AA22009424E3 /* ConfigurationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85480FCE25D1AA22009424E3 /* ConfigurationStore.swift */; }; 854DAAAE2A72B613001E2E24 /* BookmarksBarPromptAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 859F30662A72B38500C20372 /* BookmarksBarPromptAssets.xcassets */; }; 85589E7F27BBB8630038AD11 /* AddEditFavoriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7927BBB8620038AD11 /* AddEditFavoriteViewController.swift */; }; - 85589E8027BBB8630038AD11 /* AddEditFavoriteWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7A27BBB8620038AD11 /* AddEditFavoriteWindow.swift */; }; - 85589E8127BBB8630038AD11 /* HomePage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E7B27BBB8630038AD11 /* HomePage.storyboard */; }; + 85589E8127BBB8630038AD11 /* AddEditFavoriteViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E7B27BBB8630038AD11 /* AddEditFavoriteViewController.storyboard */; }; 85589E8227BBB8630038AD11 /* HomePageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7C27BBB8630038AD11 /* HomePageView.swift */; }; 85589E8327BBB8630038AD11 /* HomePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7D27BBB8630038AD11 /* HomePageViewController.swift */; }; 85589E8727BBB8F20038AD11 /* HomePageFavoritesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E8627BBB8F20038AD11 /* HomePageFavoritesModel.swift */; }; 85589E8D27BBBB870038AD11 /* NavigationBar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E8C27BBBB870038AD11 /* NavigationBar.storyboard */; }; - 85589E8F27BBBBF10038AD11 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85589E8E27BBBBF10038AD11 /* Main.storyboard */; }; 85589E9127BFB9810038AD11 /* HomePageRecentlyVisitedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E9027BFB9810038AD11 /* HomePageRecentlyVisitedModel.swift */; }; 85589E9427BFE1E70038AD11 /* FavoritesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E9327BFE1E70038AD11 /* FavoritesView.swift */; }; 85589E9A27BFE3C30038AD11 /* FaviconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E9927BFE3C30038AD11 /* FaviconView.swift */; }; @@ -2409,7 +2402,6 @@ AA652CB125DD825B009059CC /* LocalBookmarkStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CB025DD825B009059CC /* LocalBookmarkStoreTests.swift */; }; AA652CCE25DD9071009059CC /* BookmarkListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CCD25DD9071009059CC /* BookmarkListTests.swift */; }; AA652CD325DDA6E9009059CC /* LocalBookmarkManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CD225DDA6E9009059CC /* LocalBookmarkManagerTests.swift */; }; - AA652CDB25DDAB32009059CC /* BookmarkStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */; }; AA6820E425502F19005ED0D5 /* WebsiteDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6820E325502F19005ED0D5 /* WebsiteDataStore.swift */; }; AA6820EB25503D6A005ED0D5 /* Fire.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6820EA25503D6A005ED0D5 /* Fire.swift */; }; AA6820F125503DA9005ED0D5 /* FireViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6820F025503DA9005ED0D5 /* FireViewModel.swift */; }; @@ -2671,6 +2663,9 @@ B64C853826944B880048FEBE /* StoredPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64C853726944B880048FEBE /* StoredPermission.swift */; }; B64C853D26944B940048FEBE /* PermissionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64C853C26944B940048FEBE /* PermissionStore.swift */; }; B64C85422694590B0048FEBE /* PermissionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64C85412694590B0048FEBE /* PermissionButton.swift */; }; + B65211252B29A42C00B30633 /* BookmarkStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */; }; + B65211262B29A42E00B30633 /* BookmarkStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */; }; + B65211272B29A43000B30633 /* BookmarkStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */; }; B65349AA265CF45000DCC645 /* DispatchQueueExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65349A9265CF45000DCC645 /* DispatchQueueExtensionsTests.swift */; }; B655124829A79465009BFE1C /* NavigationActionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66B9C5B29A5EBAD0010E8F3 /* NavigationActionExtension.swift */; }; B655124929A79465009BFE1C /* NavigationActionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66B9C5B29A5EBAD0010E8F3 /* NavigationActionExtension.swift */; }; @@ -2715,6 +2710,7 @@ B6656E0D2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */; }; B6656E0E2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */; }; B6656E5B2B2ADB1C008798A1 /* RequestFilePermissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5F5832B03580A008DB58A /* RequestFilePermissionView.swift */; }; + B6656E122B29E3BE008798A1 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; B6676BE12AA986A700525A21 /* AddressBarTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */; }; B6676BE22AA986A700525A21 /* AddressBarTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */; }; B6685E3D29A602D90043D2EE /* ExternalAppSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B687B7CB2947A1E9001DEA6F /* ExternalAppSchemeHandler.swift */; }; @@ -2781,7 +2777,6 @@ B693955726F04BEC0015B914 /* MouseOverButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693954926F04BEB0015B914 /* MouseOverButton.swift */; }; B693955D26F19CD70015B914 /* DownloadListStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693955C26F19CD70015B914 /* DownloadListStoreTests.swift */; }; B693955F26F1C17F0015B914 /* DownloadListCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693955E26F1C17F0015B914 /* DownloadListCoordinatorTests.swift */; }; - B693956126F1C1BC0015B914 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; B693956326F1C2A40015B914 /* FileDownloadManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956226F1C2A40015B914 /* FileDownloadManagerMock.swift */; }; B693956926F352DB0015B914 /* DownloadsWebViewMock.m in Sources */ = {isa = PBXBuildFile; fileRef = B693956826F352DB0015B914 /* DownloadsWebViewMock.m */; }; B696AFFB2AC5924800C93203 /* FileLineError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696AFFA2AC5924800C93203 /* FileLineError.swift */; }; @@ -2854,6 +2849,9 @@ B6B5F5892B03673B008DB58A /* BrowserImportMoreInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5F5882B03673B008DB58A /* BrowserImportMoreInfoView.swift */; }; B6B5F58A2B03673B008DB58A /* BrowserImportMoreInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5F5882B03673B008DB58A /* BrowserImportMoreInfoView.swift */; }; B6B5F58C2B03673B008DB58A /* BrowserImportMoreInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5F5882B03673B008DB58A /* BrowserImportMoreInfoView.swift */; }; + B6B71C582B23379600487131 /* NSLayoutConstraintExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B71C572B23379600487131 /* NSLayoutConstraintExtension.swift */; }; + B6B71C592B23379600487131 /* NSLayoutConstraintExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B71C572B23379600487131 /* NSLayoutConstraintExtension.swift */; }; + B6B71C5A2B23379600487131 /* NSLayoutConstraintExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B71C572B23379600487131 /* NSLayoutConstraintExtension.swift */; }; B6B77BE8297973D4001E68A1 /* Navigation in Frameworks */ = {isa = PBXBuildFile; productRef = B6B77BE7297973D4001E68A1 /* Navigation */; }; B6BBF1702744CDE1004F850E /* CoreDataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBF16F2744CDE1004F850E /* CoreDataStoreTests.swift */; }; B6BBF1722744CE36004F850E /* FireproofDomainsStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBF1712744CE36004F850E /* FireproofDomainsStoreMock.swift */; }; @@ -3432,6 +3430,7 @@ 4B723E1726B000DC00E14D75 /* TemporaryFileCreator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemporaryFileCreator.swift; sourceTree = ""; }; 4B7A57CE279A4EF300B1C70E /* ChromiumPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromiumPreferences.swift; sourceTree = ""; }; 4B7A60A0273E0BE400BBDFEB /* WKWebsiteDataStoreExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebsiteDataStoreExtension.swift; sourceTree = ""; }; + 4B81AD312B29507300706C96 /* DataBrokerProtectionPixelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DataBrokerProtectionPixelTests.swift; path = LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionPixelTests.swift; sourceTree = SOURCE_ROOT; }; 4B85A47F28821CC500FC4C39 /* NSPasteboardItemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPasteboardItemExtension.swift; sourceTree = ""; }; 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveIdentityViewController.swift; sourceTree = ""; }; 4B8A4E0027C8447E005F40E8 /* SaveIdentityPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveIdentityPopover.swift; sourceTree = ""; }; @@ -3622,8 +3621,6 @@ 7B76E6852AD5D77600186A84 /* XPCHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = XPCHelper; sourceTree = ""; }; 7B934C3D2A866CFF00FC8F9C /* NetworkProtectionOnboardingMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOnboardingMenu.swift; sourceTree = ""; }; 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionShared.swift"; sourceTree = ""; }; - 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DuckDuckGoDBPTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionPixelTests.swift; sourceTree = ""; }; 7BA7CC0B2AD11D1E0042E5CE /* DuckDuckGoVPNAppStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DuckDuckGoVPNAppStore.xcconfig; sourceTree = ""; }; 7BA7CC0C2AD11D1E0042E5CE /* DuckDuckGoVPN.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DuckDuckGoVPN.xcconfig; sourceTree = ""; }; 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuckDuckGoVPNAppDelegate.swift; sourceTree = ""; }; @@ -3651,7 +3648,6 @@ 7BEC20402B0F505F00243D3E /* BookmarkAddPopoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkAddPopoverViewController.swift; sourceTree = ""; }; 7BEC20412B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkAddFolderPopoverViewController.swift; sourceTree = ""; }; 7BF1A9D72AE054D300FCA683 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DBPUnitTests.xcconfig; sourceTree = ""; }; 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift; sourceTree = ""; }; 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionWaitlist.swift"; sourceTree = ""; }; 85012B0129133F9F003D0DCC /* NavigationBarPopovers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarPopovers.swift; sourceTree = ""; }; @@ -3666,13 +3662,11 @@ 85480FCE25D1AA22009424E3 /* ConfigurationStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationStore.swift; sourceTree = ""; }; 8553FF51257523760029327F /* URLSuggestedFilenameTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSuggestedFilenameTests.swift; sourceTree = ""; }; 85589E7927BBB8620038AD11 /* AddEditFavoriteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEditFavoriteViewController.swift; sourceTree = ""; }; - 85589E7A27BBB8620038AD11 /* AddEditFavoriteWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEditFavoriteWindow.swift; sourceTree = ""; }; - 85589E7B27BBB8630038AD11 /* HomePage.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = HomePage.storyboard; sourceTree = ""; }; + 85589E7B27BBB8630038AD11 /* AddEditFavoriteViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = AddEditFavoriteViewController.storyboard; sourceTree = ""; }; 85589E7C27BBB8630038AD11 /* HomePageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePageView.swift; sourceTree = ""; }; 85589E7D27BBB8630038AD11 /* HomePageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePageViewController.swift; sourceTree = ""; }; 85589E8627BBB8F20038AD11 /* HomePageFavoritesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePageFavoritesModel.swift; sourceTree = ""; }; 85589E8C27BBBB870038AD11 /* NavigationBar.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NavigationBar.storyboard; sourceTree = ""; }; - 85589E8E27BBBBF10038AD11 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 85589E9027BFB9810038AD11 /* HomePageRecentlyVisitedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageRecentlyVisitedModel.swift; sourceTree = ""; }; 85589E9227BFBBD60038AD11 /* History 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "History 4.xcdatamodel"; sourceTree = ""; }; 85589E9327BFE1E70038AD11 /* FavoritesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesView.swift; sourceTree = ""; }; @@ -4160,6 +4154,7 @@ B6B5F57E2B024105008DB58A /* DataImportSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataImportSummaryView.swift; sourceTree = ""; }; B6B5F5832B03580A008DB58A /* RequestFilePermissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestFilePermissionView.swift; sourceTree = ""; }; B6B5F5882B03673B008DB58A /* BrowserImportMoreInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserImportMoreInfoView.swift; sourceTree = ""; }; + B6B71C572B23379600487131 /* NSLayoutConstraintExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraintExtension.swift; sourceTree = ""; }; B6BBF16F2744CDE1004F850E /* CoreDataStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStoreTests.swift; sourceTree = ""; }; B6BBF1712744CE36004F850E /* FireproofDomainsStoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FireproofDomainsStoreMock.swift; sourceTree = ""; }; B6BBF17327475B15004F850E /* PopupBlockedPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupBlockedPopover.swift; sourceTree = ""; }; @@ -4283,6 +4278,7 @@ files = ( 3706FE88293F661700E42796 /* OHHTTPStubs in Frameworks */, 3706FE89293F661700E42796 /* OHHTTPStubsSwift in Frameworks */, + 4B81AD372B29513100706C96 /* PixelKitTestingUtilities in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4401,14 +4397,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7B96D0CC2ADFDA7E007E02C8 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 7B20D5C62ADFEC6E0053C42A /* PixelKitTestingUtilities in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9D9AE8C62AAA39A70026E7DC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -4468,6 +4456,7 @@ files = ( B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */, B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */, + 4B81AD352B29512B00706C96 /* PixelKitTestingUtilities in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4807,7 +4796,6 @@ children = ( 373A26962964CF0B0043FC57 /* TestsTargetsBase.xcconfig */, 378B588D295CF447002C0CC0 /* UnitTests.xcconfig */, - 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */, 378B58C8295CF9A7002C0CC0 /* IntegrationTests.xcconfig */, 37E75733296F4F0500E1C162 /* UnitTestsAppStore.xcconfig */, 37E75734296F4F0500E1C162 /* IntegrationTestsAppStore.xcconfig */, @@ -5436,6 +5424,14 @@ path = DataExport; sourceTree = ""; }; + 4B81AD302B29506300706C96 /* DataBrokerProtection */ = { + isa = PBXGroup; + children = ( + 4B81AD312B29507300706C96 /* DataBrokerProtectionPixelTests.swift */, + ); + path = DataBrokerProtection; + sourceTree = ""; + }; 4B82E9B725B6A04B00656FE7 /* ContentBlocker */ = { isa = PBXGroup; children = ( @@ -5855,7 +5851,6 @@ isa = PBXGroup; children = ( 7BF1A9D72AE054D300FCA683 /* Info.plist */, - 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */, ); path = DuckDuckGoDBPTests; sourceTree = ""; @@ -6405,7 +6400,6 @@ 9D9AE8D12AAA39A70026E7DC /* DuckDuckGoDBPBackgroundAgent.app */, 9D9AE8F22AAA39D30026E7DC /* .app */, 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */, - 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */, ); name = Products; sourceTree = ""; @@ -6487,6 +6481,7 @@ AA585D93248FD31400E9A3E2 /* UnitTests */ = { isa = PBXGroup; children = ( + 4B81AD302B29506300706C96 /* DataBrokerProtection */, 5629846D2AC460DF00AC20EB /* Sync */, B6A5A28C25B962CB00AA7ADA /* App */, 85F1B0C725EF9747004792B6 /* AppDelegate */, @@ -6539,7 +6534,6 @@ AA585DB02490E6FA00E9A3E2 /* MainWindow */ = { isa = PBXGroup; children = ( - 85589E8E27BBBBF10038AD11 /* Main.storyboard */, AA7412BC24D2BEEE00D22FE0 /* MainWindow.swift */, AA7412B424D1536B00D22FE0 /* MainWindowController.swift */, AA585DAE2490E6E600E9A3E2 /* MainViewController.swift */, @@ -6636,7 +6630,6 @@ children = ( AA652CB025DD825B009059CC /* LocalBookmarkStoreTests.swift */, 986189E52A7CFB3E001B4519 /* LocalBookmarkStoreSavingTests.swift */, - AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */, ); path = Services; sourceTree = ""; @@ -7129,6 +7122,7 @@ isa = PBXGroup; children = ( 987799F52999996B005D8EB6 /* BookmarkDatabase.swift */, + AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */, 4B9292DA2667125D00AD2C21 /* ContextualMenu.swift */, B6DA06E52913F39400225DE2 /* MenuItemSelectors.swift */, AAC5E4D625D6A710007F5990 /* BookmarkStore.swift */, @@ -7259,6 +7253,7 @@ B657841925FA484B00D8DB33 /* NSException+Catch.m */, B657841E25FA497600D8DB33 /* NSException+Catch.swift */, 4B139AFC26B60BD800894F82 /* NSImageExtensions.swift */, + B6B71C572B23379600487131 /* NSLayoutConstraintExtension.swift */, AA6EF9B2250785D5004754E6 /* NSMenuExtension.swift */, AA72D5FD25FFF94E00C77619 /* NSMenuItemExtension.swift */, 4B980E202817604000282EE1 /* NSNotificationName+Debug.swift */, @@ -7327,12 +7322,11 @@ AAE71DB325F66A3F00D74437 /* View */ = { isa = PBXGroup; children = ( + 85589E7B27BBB8630038AD11 /* AddEditFavoriteViewController.storyboard */, 85589E7927BBB8620038AD11 /* AddEditFavoriteViewController.swift */, - 85589E7A27BBB8620038AD11 /* AddEditFavoriteWindow.swift */, 85589E9D27BFE4500038AD11 /* DefaultBrowserPromptView.swift */, 85589E9327BFE1E70038AD11 /* FavoritesView.swift */, 56D6A3D529DB2BAB0055215A /* ContinueSetUpView.swift */, - 85589E7B27BBB8630038AD11 /* HomePage.storyboard */, 85AC7AD827BD625000FFB69B /* HomePageAssets.xcassets */, 85589E7C27BBB8630038AD11 /* HomePageView.swift */, 1DCFBC8929ADF32B00313531 /* BurnerHomePageView.swift */, @@ -7989,6 +7983,7 @@ packageProductDependencies = ( 3706FDD6293F661700E42796 /* OHHTTPStubs */, 3706FDD8293F661700E42796 /* OHHTTPStubsSwift */, + 4B81AD362B29513100706C96 /* PixelKitTestingUtilities */, ); productName = DuckDuckGoTests; productReference = 3706FE99293F661700E42796 /* Unit Tests App Store.xctest */; @@ -8229,27 +8224,6 @@ productReference = 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; - 7B96D0CE2ADFDA7E007E02C8 /* DuckDuckGoDBPTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7B96D0D52ADFDA7F007E02C8 /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPTests" */; - buildPhases = ( - 7B96D0CB2ADFDA7E007E02C8 /* Sources */, - 7B96D0CC2ADFDA7E007E02C8 /* Frameworks */, - 7B96D0CD2ADFDA7E007E02C8 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 7B20D5C82ADFEC730053C42A /* PBXTargetDependency */, - ); - name = DuckDuckGoDBPTests; - packageProductDependencies = ( - 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */, - ); - productName = DuckDuckGoDBPTests; - productReference = 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; 9D9AE8B22AAA39A70026E7DC /* DuckDuckGoDBPBackgroundAgent */ = { isa = PBXNativeTarget; buildConfigurationList = 9D9AE8CC2AAA39A70026E7DC /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPBackgroundAgent" */; @@ -8363,6 +8337,7 @@ packageProductDependencies = ( B6DA44162616C13800DD1EC2 /* OHHTTPStubs */, B6DA44182616C13800DD1EC2 /* OHHTTPStubsSwift */, + 4B81AD342B29512B00706C96 /* PixelKitTestingUtilities */, ); productName = DuckDuckGoTests; productReference = AA585D90248FD31400E9A3E2 /* Unit Tests.xctest */; @@ -8429,10 +8404,6 @@ CreatedOnToolsVersion = 12.5.1; TestTargetID = AA585D7D248FD31100E9A3E2; }; - 7B96D0CE2ADFDA7E007E02C8 = { - CreatedOnToolsVersion = 15.0; - TestTargetID = 31929F7B2A4C4CFF0084EA89; - }; AA585D7D248FD31100E9A3E2 = { CreatedOnToolsVersion = 11.5; }; @@ -8479,7 +8450,6 @@ 4B4BEC1F2A11B4E2001D9AC5 /* DuckDuckGoNotifications */, 4B2D06382A11CFBA00DE1F49 /* DuckDuckGoVPN */, 4B2D06682A13318400DE1F49 /* DuckDuckGoVPNAppStore */, - 7B96D0CE2ADFDA7E007E02C8 /* DuckDuckGoDBPTests */, 9D9AE8B22AAA39A70026E7DC /* DuckDuckGoDBPBackgroundAgent */, 9D9AE8D32AAA39D30026E7DC /* DuckDuckGoDBPBackgroundAgentAppStore */, 4B9579252AC7AE700062CA31 /* DuckDuckGo Privacy Pro */, @@ -8509,7 +8479,7 @@ 3706FCBF293F65D500E42796 /* ContentOverlay.storyboard in Resources */, 3706FCC0293F65D500E42796 /* FindInPage.storyboard in Resources */, EEC8EB3F2982CA440065AA39 /* JSAlert.storyboard in Resources */, - 3706FCC1293F65D500E42796 /* HomePage.storyboard in Resources */, + 3706FCC1293F65D500E42796 /* AddEditFavoriteViewController.storyboard in Resources */, 3706FCC3293F65D500E42796 /* userscript.js in Resources */, 3706FCC4293F65D500E42796 /* fb-tds.json in Resources */, 3706FCC5293F65D500E42796 /* TabPreview.storyboard in Resources */, @@ -8538,7 +8508,6 @@ 3706FCE2293F65D500E42796 /* dark-trackers-3.json in Resources */, 3706FCE3293F65D500E42796 /* dark-trackers-2.json in Resources */, 3706FCE4293F65D500E42796 /* Fire.storyboard in Resources */, - 3706FCE5293F65D500E42796 /* Main.storyboard in Resources */, 3706FCE6293F65D500E42796 /* social_images in Resources */, 3706FCE7293F65D500E42796 /* shield-dot-mouse-over.json in Resources */, 3706FCE9293F65D500E42796 /* fb-sdk.js in Resources */, @@ -8634,7 +8603,7 @@ 4B957BFB2AC7AE700062CA31 /* ContentOverlay.storyboard in Resources */, 4B957BFC2AC7AE700062CA31 /* FindInPage.storyboard in Resources */, 4B957BFD2AC7AE700062CA31 /* JSAlert.storyboard in Resources */, - 4B957BFE2AC7AE700062CA31 /* HomePage.storyboard in Resources */, + 4B957BFE2AC7AE700062CA31 /* AddEditFavoriteViewController.storyboard in Resources */, 4B957C002AC7AE700062CA31 /* userscript.js in Resources */, 4B957C012AC7AE700062CA31 /* fb-tds.json in Resources */, 4B957C022AC7AE700062CA31 /* TabPreview.storyboard in Resources */, @@ -8663,7 +8632,6 @@ 4B957C1B2AC7AE700062CA31 /* dark-trackers-3.json in Resources */, 4B957C1C2AC7AE700062CA31 /* dark-trackers-2.json in Resources */, 4B957C1D2AC7AE700062CA31 /* Fire.storyboard in Resources */, - 4B957C1E2AC7AE700062CA31 /* Main.storyboard in Resources */, 4B957C1F2AC7AE700062CA31 /* social_images in Resources */, 4B957C202AC7AE700062CA31 /* shield-dot-mouse-over.json in Resources */, 4B957C222AC7AE700062CA31 /* fb-sdk.js in Resources */, @@ -8692,15 +8660,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7B96D0CD2ADFDA7E007E02C8 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7BF1A9D82AE054D300FCA683 /* Info.plist in Resources */, - 7BF1A9DC2AE0551C00FCA683 /* DBPUnitTests.xcconfig in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9D9AE8C92AAA39A70026E7DC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -8738,7 +8697,7 @@ 7B1E819F27C8874900FF0E60 /* ContentOverlay.storyboard in Resources */, 85A0117425AF2EDF00FA6A0C /* FindInPage.storyboard in Resources */, EEC111E4294D06020086524F /* JSAlert.storyboard in Resources */, - 85589E8127BBB8630038AD11 /* HomePage.storyboard in Resources */, + 85589E8127BBB8630038AD11 /* AddEditFavoriteViewController.storyboard in Resources */, B31055C627A1BA1D001AC618 /* userscript.js in Resources */, EA4617F0273A28A700F110A2 /* fb-tds.json in Resources */, AAE8B102258A41C000E81239 /* TabPreview.storyboard in Resources */, @@ -8767,7 +8726,6 @@ AA34397D2754D55100B241FA /* dark-trackers-3.json in Resources */, AA3439782754D55100B241FA /* dark-trackers-2.json in Resources */, AAB7320726DD0C37002FACF9 /* Fire.storyboard in Resources */, - 85589E8F27BBBBF10038AD11 /* Main.storyboard in Resources */, EA18D1CA272F0DC8006DC101 /* social_images in Resources */, AA7EB6E927E880A600036718 /* shield-dot-mouse-over.json in Resources */, EAC80DE0271F6C0100BBF02D /* fb-sdk.js in Resources */, @@ -9242,7 +9200,6 @@ 3706FACD293F65D500E42796 /* PopUpButton.swift in Sources */, B6BCC54B2AFDF24B002C5499 /* TaskWithProgress.swift in Sources */, 3706FACE293F65D500E42796 /* SuggestionViewController.swift in Sources */, - 3706FACF293F65D500E42796 /* AddEditFavoriteWindow.swift in Sources */, 7BFE95552A9DF2990081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */, 3706FAD1293F65D500E42796 /* VisitViewModel.swift in Sources */, 3706FAD2293F65D500E42796 /* Atb.swift in Sources */, @@ -9284,6 +9241,7 @@ 3706FEBC293F6EFF00E42796 /* BWResponse.swift in Sources */, 3706FAF4293F65D500E42796 /* SafariBookmarksReader.swift in Sources */, 31C9ADE62AF0564500CEF57D /* WaitlistFeatureSetupHandler.swift in Sources */, + B65211262B29A42E00B30633 /* BookmarkStoreMock.swift in Sources */, 3706FAF5293F65D500E42796 /* SafariVersionReader.swift in Sources */, 3706FAF6293F65D500E42796 /* LoginFaviconView.swift in Sources */, 3706FEC0293F6EFF00E42796 /* BWRequest.swift in Sources */, @@ -9384,6 +9342,7 @@ 3706FB48293F65D500E42796 /* AutofillPreferencesModel.swift in Sources */, 3168506E2AF3AD1D009A2828 /* WaitlistViewControllerPresenter.swift in Sources */, 3706FB49293F65D500E42796 /* NSException+Catch.swift in Sources */, + B6B71C592B23379600487131 /* NSLayoutConstraintExtension.swift in Sources */, 3706FB4A293F65D500E42796 /* PasswordManagementNoteModel.swift in Sources */, 3706FB4B293F65D500E42796 /* CookieNotificationAnimationModel.swift in Sources */, 3706FB4C293F65D500E42796 /* SharingMenu.swift in Sources */, @@ -9879,6 +9838,7 @@ 3706FE00293F661700E42796 /* FaviconStoringMock.swift in Sources */, 3706FE01293F661700E42796 /* PixelStoreMock.swift in Sources */, 3706FE02293F661700E42796 /* BookmarksBarViewModelTests.swift in Sources */, + 4B81AD332B29507300706C96 /* DataBrokerProtectionPixelTests.swift in Sources */, 3706FE03293F661700E42796 /* CoreDataStoreTests.swift in Sources */, 3706FE04293F661700E42796 /* TreeControllerTests.swift in Sources */, 3706FE05293F661700E42796 /* DownloadsWebViewMock.m in Sources */, @@ -9902,7 +9862,6 @@ 3706FE15293F661700E42796 /* PrivacyIconViewModelTests.swift in Sources */, 56D145F229E6F06D00E3488A /* MockBookmarkManager.swift in Sources */, 3706FE16293F661700E42796 /* CSVImporterTests.swift in Sources */, - 3706FE17293F661700E42796 /* BookmarkStoreMock.swift in Sources */, 56B234C02A84EFD800F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */, 3706FE19293F661700E42796 /* DeviceAuthenticatorTests.swift in Sources */, 3706FE1A293F661700E42796 /* BrowserProfileTests.swift in Sources */, @@ -10347,7 +10306,6 @@ 4B9579A32AC7AE700062CA31 /* PopUpButton.swift in Sources */, 4B9579A42AC7AE700062CA31 /* NetworkProtectionInviteDialog.swift in Sources */, 4B9579A52AC7AE700062CA31 /* SuggestionViewController.swift in Sources */, - 4B9579A72AC7AE700062CA31 /* AddEditFavoriteWindow.swift in Sources */, 4B9579A82AC7AE700062CA31 /* BWKeyStorage.swift in Sources */, 4B9579A92AC7AE700062CA31 /* VisitViewModel.swift in Sources */, 4B9579AA2AC7AE700062CA31 /* AddressBarTextEditor.swift in Sources */, @@ -10855,6 +10813,8 @@ 4B957B802AC7AE700062CA31 /* NSAlertExtension.swift in Sources */, 4B957B812AC7AE700062CA31 /* ThirdPartyBrowser.swift in Sources */, 4B957B822AC7AE700062CA31 /* SearchNonexistentDomainNavigationResponder.swift in Sources */, + B6B71C5A2B23379600487131 /* NSLayoutConstraintExtension.swift in Sources */, + B65211272B29A43000B30633 /* BookmarkStoreMock.swift in Sources */, 4B957B832AC7AE700062CA31 /* CircularProgressView.swift in Sources */, 4B957B842AC7AE700062CA31 /* SuggestionContainer.swift in Sources */, 4B957B852AC7AE700062CA31 /* FindInPageTabExtension.swift in Sources */, @@ -10956,14 +10916,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7B96D0CB2ADFDA7E007E02C8 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7B96D0DC2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9D9AE8B62AAA39A70026E7DC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -11085,6 +11037,7 @@ 4BBDEE9428FC14760092FAA6 /* ConnectBitwardenViewController.swift in Sources */, 1DDF076428F815AD00EDFBE3 /* BWManager.swift in Sources */, 9833912F27AAA3CE00DAF119 /* AppTrackerDataSetProvider.swift in Sources */, + B65211252B29A42C00B30633 /* BookmarkStoreMock.swift in Sources */, 4BA1A6B3258B080A00F6F690 /* EncryptionKeyGeneration.swift in Sources */, 37B11B3928095E6600CBB621 /* TabLazyLoader.swift in Sources */, 4B9DB03B2A983B24000927DB /* InvitedToWaitlistView.swift in Sources */, @@ -11092,7 +11045,6 @@ 4BBE0AA727B9B027003B37A8 /* PopUpButton.swift in Sources */, 4B4D60CF2A0C849600BCD287 /* NetworkProtectionInviteDialog.swift in Sources */, AABEE6A524AA0A7F0043105B /* SuggestionViewController.swift in Sources */, - 85589E8027BBB8630038AD11 /* AddEditFavoriteWindow.swift in Sources */, 1D6216B229069BBF00386B2C /* BWKeyStorage.swift in Sources */, AA7E919F287872EA00AB6B62 /* VisitViewModel.swift in Sources */, B6676BE12AA986A700525A21 /* AddressBarTextEditor.swift in Sources */, @@ -11267,6 +11219,7 @@ B6DB3CF926A00E2D00D459B7 /* AVCaptureDevice+SwizzledAuthState.swift in Sources */, 1E0C72062ABC63BD00802009 /* SubscriptionPagesUserScript.swift in Sources */, AAAB9116288EB46B00A057A9 /* VisitMenuItem.swift in Sources */, + B6B71C582B23379600487131 /* NSLayoutConstraintExtension.swift in Sources */, 4BA1A6BD258B082300F6F690 /* EncryptionKeyStore.swift in Sources */, B6C00ED7292FB4B4009C73A6 /* TabExtensionsBuilder.swift in Sources */, 4BE65474271FCD40008D1D63 /* PasswordManagementIdentityItemView.swift in Sources */, @@ -11721,6 +11674,7 @@ B662D3DE275613BB0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */, 1D3B1ABF29369FC8006F4388 /* BWEncryptionTests.swift in Sources */, B6F56567299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, + B6656E122B29E3BE008798A1 /* DownloadListStoreMock.swift in Sources */, 37D23780287EFEE200BCE03B /* PinnedTabsManagerTests.swift in Sources */, AA0877BA26D5161D00B05660 /* WebKitVersionProviderTests.swift in Sources */, B69B50462726C5C200758A2B /* AtbAndVariantCleanupTests.swift in Sources */, @@ -11762,13 +11716,12 @@ 4B723E0926B0003E00E14D75 /* CSVLoginExporterTests.swift in Sources */, B6DA06E12913AEDC00225DE2 /* TestNavigationDelegate.swift in Sources */, 56D145E829E6BB6300E3488A /* CapturingDataImportProvider.swift in Sources */, + 4B81AD322B29507300706C96 /* DataBrokerProtectionPixelTests.swift in Sources */, B630793526731BC400DCEE41 /* URLSuggestedFilenameTests.swift in Sources */, B603974E29C1F93600902A34 /* TabPermissionsTests.swift in Sources */, 85AC3B4925DAC9BD00C7D2AA /* ConfigurationStorageTests.swift in Sources */, - B693956126F1C1BC0015B914 /* DownloadListStoreMock.swift in Sources */, AA91F83927076F1900771A0D /* PrivacyIconViewModelTests.swift in Sources */, 4B723E0726B0003E00E14D75 /* CSVImporterTests.swift in Sources */, - AA652CDB25DDAB32009059CC /* BookmarkStoreMock.swift in Sources */, 4BCF15EC2ABB9AF80083F6DF /* NetworkProtectionRemoteMessageTests.swift in Sources */, B62EB47C25BAD3BB005745C6 /* WKWebViewPrivateMethodsAvailabilityTests.swift in Sources */, 4BBC16A527C488C900E00A38 /* DeviceAuthenticatorTests.swift in Sources */, @@ -11976,10 +11929,6 @@ isa = PBXTargetDependency; productRef = 4B5F14FB2A15291D0060320F /* InputFilesChecker */; }; - 7B20D5C82ADFEC730053C42A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = 7B20D5C72ADFEC730053C42A /* PixelKitTestingUtilities */; - }; 7B4CE8E026F02108009134B1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */; @@ -12350,34 +12299,6 @@ }; name = Release; }; - 7B96D0D62ADFDA7F007E02C8 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - 7B96D0D72ADFDA7F007E02C8 /* CI */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; - buildSettings = { - }; - name = CI; - }; - 7B96D0D82ADFDA7F007E02C8 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; - buildSettings = { - }; - name = Release; - }; - 7B96D0D92ADFDA7F007E02C8 /* Review */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; - buildSettings = { - }; - name = Review; - }; 9D9AE8CD2AAA39A70026E7DC /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7B6EC5E52AE2D8AF004FE6DF /* DuckDuckGoDBPAgent.xcconfig */; @@ -12663,17 +12584,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 7B96D0D52ADFDA7F007E02C8 /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7B96D0D62ADFDA7F007E02C8 /* Debug */, - 7B96D0D72ADFDA7F007E02C8 /* CI */, - 7B96D0D82ADFDA7F007E02C8 /* Release */, - 7B96D0D92ADFDA7F007E02C8 /* Review */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 9D9AE8CC2AAA39A70026E7DC /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPBackgroundAgent" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -13046,6 +12956,14 @@ isa = XCSwiftPackageProductDependency; productName = "plugin:InputFilesChecker"; }; + 4B81AD342B29512B00706C96 /* PixelKitTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKitTestingUtilities; + }; + 4B81AD362B29513100706C96 /* PixelKitTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKitTestingUtilities; + }; 4B8F52342A169D2D00BE7131 /* Common */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -13154,14 +13072,6 @@ isa = XCSwiftPackageProductDependency; productName = Purchase; }; - 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */ = { - isa = XCSwiftPackageProductDependency; - productName = PixelKitTestingUtilities; - }; - 7B20D5C72ADFEC730053C42A /* PixelKitTestingUtilities */ = { - isa = XCSwiftPackageProductDependency; - productName = PixelKitTestingUtilities; - }; 7B31FD8B2AD125620086AA24 /* NetworkProtectionIPC */ = { isa = XCSwiftPackageProductDependency; productName = NetworkProtectionIPC; diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index 77b2c34fba..eaa06a07ec 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -48,6 +48,20 @@ ReferencedContainer = "container:DuckDuckGo.xcodeproj"> + + + + + + + + BookmarksBarViewController { + NSStoryboard(name: "BookmarksBar", bundle: nil).instantiateInitialController { coder in + self.init(coder: coder, tabCollectionViewModel: tabCollectionViewModel, bookmarkManager: bookmarkManager) + }! + } + + init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, bookmarkManager: BookmarkManager) { self.tabCollectionViewModel = tabCollectionViewModel - self.viewModel = BookmarksBarViewModel(bookmarkManager: LocalBookmarkManager.shared, tabCollectionViewModel: tabCollectionViewModel) + self.viewModel = BookmarksBarViewModel(bookmarkManager: bookmarkManager, tabCollectionViewModel: tabCollectionViewModel) super.init(coder: coder) } required init?(coder: NSCoder) { - fatalError("TabBarViewController: Bad initializer") + fatalError("BookmarksBarViewController: Bad initializer") } // MARK: - View Lifecycle diff --git a/DuckDuckGo/Common/Database/Database.swift b/DuckDuckGo/Common/Database/Database.swift index 7af2ce4cf2..574a079ee9 100644 --- a/DuckDuckGo/Common/Database/Database.swift +++ b/DuckDuckGo/Common/Database/Database.swift @@ -58,7 +58,9 @@ final class Database { model: .init(byMerging: [mainModel, httpsUpgradeModel])!), nil) } #if DEBUG - assert(![.unitTests, .xcPreviews].contains(NSApp.runType), "Use CoreData.---Container() methods for testing purposes") + assert(![.unitTests, .xcPreviews].contains(NSApp.runType), { + "Use CoreData.---Container() methods for testing purposes:\n" + Thread.callStackSymbols.description + }()) #endif let keyStore: EncryptionKeyStoring diff --git a/Configuration/Tests/DBPUnitTests.xcconfig b/DuckDuckGo/Common/Extensions/NSLayoutConstraintExtension.swift similarity index 59% rename from Configuration/Tests/DBPUnitTests.xcconfig rename to DuckDuckGo/Common/Extensions/NSLayoutConstraintExtension.swift index 1852b3c454..9bd5ba66aa 100644 --- a/Configuration/Tests/DBPUnitTests.xcconfig +++ b/DuckDuckGo/Common/Extensions/NSLayoutConstraintExtension.swift @@ -1,4 +1,7 @@ -// Copyright © 2022 DuckDuckGo. All rights reserved. +// +// NSLayoutConstraintExtension.swift +// +// Copyright © 2023 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. @@ -13,13 +16,14 @@ // limitations under the License. // -#include "TestsTargetsBase.xcconfig" - -ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +import AppKit +import Foundation -FEATURE_FLAGS = FEEDBACK NETWORK_PROTECTION +extension NSLayoutConstraint { -INFOPLIST_FILE = DuckDuckGoDBPTests/Info.plist -PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.macos.browser.DuckDuckGoDBPTests + func priority(_ priority: Float) -> Self { + self.priority = .init(priority) + return self + } -TEST_HOST=$(BUILT_PRODUCTS_DIR)/DuckDuckGoDBP.app/Contents/MacOS/DuckDuckGoDBP +} diff --git a/DuckDuckGo/Common/Extensions/NSScreenExtension.swift b/DuckDuckGo/Common/Extensions/NSScreenExtension.swift index d8ecfd3735..b740fe6faa 100644 --- a/DuckDuckGo/Common/Extensions/NSScreenExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSScreenExtension.swift @@ -20,6 +20,8 @@ import AppKit extension NSScreen { + static let fallbackHeadlessScreenFrame = NSRect(x: 0, y: 100, width: 1280, height: 900) + static var dockScreen: NSScreen? { screens.min(by: { ($0.frame.height - $0.visibleFrame.height) > ($1.frame.height - $1.visibleFrame.height) }) } diff --git a/DuckDuckGo/Common/Extensions/NSViewControllerExtension.swift b/DuckDuckGo/Common/Extensions/NSViewControllerExtension.swift index 3ca98220b8..5d080f5bfb 100644 --- a/DuckDuckGo/Common/Extensions/NSViewControllerExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSViewControllerExtension.swift @@ -79,9 +79,11 @@ extension NSViewController { } } - func addAndLayoutChild(_ vc: NSViewController) { + func addAndLayoutChild(_ vc: NSViewController, into containerView: NSView? = nil) { + assert(containerView == nil || sequence(first: containerView!, next: { $0.superview }).contains(self.view), + "\(containerView!) is not a part of \(self) view hierarchy") self.addChild(vc) - view.addAndLayout(vc.view) + (containerView ?? self.view).addAndLayout(vc.view) } func removeCompletely() { diff --git a/DuckDuckGo/Common/Extensions/NSViewExtension.swift b/DuckDuckGo/Common/Extensions/NSViewExtension.swift index 3e1526f586..0d8bbc4f59 100644 --- a/DuckDuckGo/Common/Extensions/NSViewExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSViewExtension.swift @@ -47,11 +47,13 @@ extension NSView { layer?.cornerRadius = radius } - func addAndLayout(_ subView: NSView) { - subView.frame = bounds - subView.autoresizingMask = [.height, .width] - subView.translatesAutoresizingMaskIntoConstraints = true - addSubview(subView) + func addAndLayout(_ subview: NSView) { + subview.translatesAutoresizingMaskIntoConstraints = false + subview.frame = bounds + subview.autoresizingMask = [.height, .width] + subview.translatesAutoresizingMaskIntoConstraints = true + + addSubview(subview) } func wrappedInContainer(padding: CGFloat = 0) -> NSView { @@ -72,6 +74,11 @@ extension NSView { return containerView } + func hidden() -> Self { + self.isHidden = true + return self + } + func makeMeFirstResponder() { guard let window = window else { os_log("%s: Window not available", type: .error, className) diff --git a/DuckDuckGo/Common/View/AppKit/ColorView.swift b/DuckDuckGo/Common/View/AppKit/ColorView.swift index 460435aa48..07e5a383f5 100644 --- a/DuckDuckGo/Common/View/AppKit/ColorView.swift +++ b/DuckDuckGo/Common/View/AppKit/ColorView.swift @@ -26,8 +26,14 @@ internal class ColorView: NSView { setupView() } - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) + init(frame: NSRect, backgroundColor: NSColor? = nil, cornerRadius: CGFloat = 0, borderColor: NSColor? = nil, borderWidth: CGFloat = 0, interceptClickEvents: Bool = false) { + super.init(frame: frame) + + self.backgroundColor = backgroundColor + self.cornerRadius = cornerRadius + self.borderColor = borderColor + self.borderWidth = borderWidth + self.interceptClickEvents = interceptClickEvents setupView() } diff --git a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift index a60415329f..98d890e4a7 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift @@ -55,7 +55,7 @@ final class DownloadListStore: DownloadListStoring { private var context: NSManagedObjectContext? { if case .none = _context { #if DEBUG - if case .unitTests = NSApp.runType { + if [.unitTests, .xcPreviews].contains(NSApp.runType) { _context = .some(.none) return .none } diff --git a/DuckDuckGo/FindInPage/FindInPageViewController.swift b/DuckDuckGo/FindInPage/FindInPageViewController.swift index b3db6b9457..c761ac0fc3 100644 --- a/DuckDuckGo/FindInPage/FindInPageViewController.swift +++ b/DuckDuckGo/FindInPage/FindInPageViewController.swift @@ -43,6 +43,10 @@ final class FindInPageViewController: NSViewController { private var modelCancellables = Set() + static func create() -> FindInPageViewController { + (NSStoryboard(name: "FindInPage", bundle: nil).instantiateInitialController() as? FindInPageViewController)! + } + override func viewDidLoad() { super.viewDidLoad() focusRingView.strokedBackgroundColor = NSColor.findInPageFocusedBackgroundColor diff --git a/DuckDuckGo/Fire/View/FireViewController.swift b/DuckDuckGo/Fire/View/FireViewController.swift index 730b483f26..919fc8bd71 100644 --- a/DuckDuckGo/Fire/View/FireViewController.swift +++ b/DuckDuckGo/Fire/View/FireViewController.swift @@ -43,13 +43,17 @@ final class FireViewController: NSViewController { private var fireAnimationView: AnimationView? private var fireAnimationViewLoadingTask: Task<(), Never>? + static func create(tabCollectionViewModel: TabCollectionViewModel, fireViewModel: FireViewModel? = nil) -> FireViewController { + NSStoryboard(name: "Fire", bundle: nil).instantiateInitialController { coder in + self.init(coder: coder, tabCollectionViewModel: tabCollectionViewModel, fireViewModel: fireViewModel) + }! + } + required init?(coder: NSCoder) { fatalError("TabBarViewController: Bad initializer") } - init?(coder: NSCoder, - tabCollectionViewModel: TabCollectionViewModel, - fireViewModel: FireViewModel? = nil) { + init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, fireViewModel: FireViewModel? = nil) { self.tabCollectionViewModel = tabCollectionViewModel self.fireViewModel = fireViewModel ?? FireCoordinator.fireViewModel diff --git a/DuckDuckGo/HomePage/View/HomePage.storyboard b/DuckDuckGo/HomePage/View/AddEditFavoriteViewController.storyboard similarity index 71% rename from DuckDuckGo/HomePage/View/HomePage.storyboard rename to DuckDuckGo/HomePage/View/AddEditFavoriteViewController.storyboard index fec70fd10a..45003a47b6 100644 --- a/DuckDuckGo/HomePage/View/HomePage.storyboard +++ b/DuckDuckGo/HomePage/View/AddEditFavoriteViewController.storyboard @@ -1,54 +1,10 @@ - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -57,7 +13,7 @@ - + @@ -77,7 +33,7 @@ - + @@ -87,7 +43,7 @@ - + @@ -97,7 +53,7 @@ - + @@ -107,7 +63,7 @@ - + @@ -166,12 +122,7 @@ Gw - + - - - - - diff --git a/DuckDuckGo/HomePage/View/AddEditFavoriteViewController.swift b/DuckDuckGo/HomePage/View/AddEditFavoriteViewController.swift index 811df1e6ab..3be8132f39 100644 --- a/DuckDuckGo/HomePage/View/AddEditFavoriteViewController.swift +++ b/DuckDuckGo/HomePage/View/AddEditFavoriteViewController.swift @@ -31,6 +31,16 @@ final class AddEditFavoriteViewController: NSViewController { private var cancellables = Set() + static func create(bookmark: Bookmark? = nil) -> AddEditFavoriteViewController { + NSStoryboard(name: "AddEditFavoriteViewController", bundle: .main).instantiateInitialController { + let addEditFavoriteViewController = AddEditFavoriteViewController.init(coder: $0) + if let bookmark { + addEditFavoriteViewController?.edit(bookmark: bookmark) + } + return addEditFavoriteViewController + }! + } + override func viewDidLoad() { super.viewDidLoad() diff --git a/DuckDuckGo/HomePage/View/AddEditFavoriteWindow.swift b/DuckDuckGo/HomePage/View/AddEditFavoriteWindow.swift deleted file mode 100644 index c153324937..0000000000 --- a/DuckDuckGo/HomePage/View/AddEditFavoriteWindow.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// AddEditFavoriteWindow.swift -// -// Copyright © 2021 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 Cocoa - -final class AddEditFavoriteWindow: NSWindow { - - enum Size { - static let width: CGFloat = 450 - static let height: CGFloat = 175 - } - - override var canBecomeMain: Bool { false } - - // swiftlint:disable force_cast - var addEditFavoriteViewController: AddEditFavoriteViewController { - contentViewController as! AddEditFavoriteViewController - } - // swiftlint:enable force_cast - -} diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index 4147eecacf..c15caf443c 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -37,8 +37,6 @@ final class HomePageViewController: NSViewController { return .init(syncService: syncService, syncBookmarksAdapter: syncBookmarksAdapter) }() - private weak var host: NSView? - var favoritesModel: HomePage.Models.FavoritesModel! var defaultBrowserModel: HomePage.Models.DefaultBrowserModel! var recentlyVisitedModel: HomePage.Models.RecentlyVisitedModel! @@ -53,12 +51,11 @@ final class HomePageViewController: NSViewController { fatalError("HomePageViewController: Bad initializer") } - init?(coder: NSCoder, - tabCollectionViewModel: TabCollectionViewModel, - bookmarkManager: BookmarkManager, - historyCoordinating: HistoryCoordinating = HistoryCoordinator.shared, - fireViewModel: FireViewModel? = nil, - onboardingViewModel: OnboardingViewModel = OnboardingViewModel()) { + init(tabCollectionViewModel: TabCollectionViewModel, + bookmarkManager: BookmarkManager, + historyCoordinating: HistoryCoordinating = HistoryCoordinator.shared, + fireViewModel: FireViewModel? = nil, + onboardingViewModel: OnboardingViewModel = OnboardingViewModel()) { self.tabCollectionViewModel = tabCollectionViewModel self.bookmarkManager = bookmarkManager @@ -66,14 +63,10 @@ final class HomePageViewController: NSViewController { self.fireViewModel = fireViewModel ?? FireCoordinator.fireViewModel self.onboardingViewModel = onboardingViewModel - super.init(coder: coder) + super.init(nibName: nil, bundle: nil) } - override func viewDidLoad() { - super.viewDidLoad() - - refreshModelsOnAppBecomingActive() - + override func loadView() { favoritesModel = createFavoritesModel() defaultBrowserModel = createDefaultBrowserModel() recentlyVisitedModel = createRecentlyVisitedModel() @@ -93,11 +86,11 @@ final class HomePageViewController: NSViewController { self?.view.makeMeFirstResponder() } - let host = NSHostingView(rootView: rootView) - host.frame = view.frame - view.addSubview(host) - self.host = host + self.view = NSHostingView(rootView: rootView) + } + override func viewDidLoad() { + refreshModelsOnAppBecomingActive() subscribeToBookmarks() subscribeToBurningData() } @@ -117,11 +110,6 @@ final class HomePageViewController: NSViewController { refreshModels() } - override func viewDidLayout() { - super.viewDidLayout() - host?.frame = self.view.frame - } - override func viewWillDisappear() { super.viewWillDisappear() @@ -256,32 +244,9 @@ final class HomePageViewController: NSViewController { } private func showAddEditController(for bookmark: Bookmark? = nil) { - // swiftlint:disable force_cast - let windowController = NSStoryboard.homePage.instantiateController(withIdentifier: "AddEditFavoriteWindowController") as! NSWindowController - // swiftlint:enable force_cast + let addEditFavoriteViewController = AddEditFavoriteViewController.create(bookmark: bookmark) - guard let window = windowController.window as? AddEditFavoriteWindow else { - assertionFailure("HomePageViewController: Failed to present AddEditFavoriteWindowController") - return - } - - guard let screen = window.screen else { - assertionFailure("HomePageViewController: No screen") - return - } - - if let bookmark = bookmark { - window.addEditFavoriteViewController.edit(bookmark: bookmark) - } - - let windowFrame = NSRect(x: screen.frame.origin.x + screen.frame.size.width / 2.0 - AddEditFavoriteWindow.Size.width / 2.0, - y: screen.frame.origin.y + screen.frame.size.height / 2.0 - AddEditFavoriteWindow.Size.height / 2.0, - width: AddEditFavoriteWindow.Size.width, - height: AddEditFavoriteWindow.Size.height) - - view.window?.addChildWindow(window, ordered: .above) - window.setFrame(windowFrame, display: true) - window.makeKey() + self.beginSheet(addEditFavoriteViewController) } private var burningDataCancellable: AnyCancellable? @@ -305,9 +270,3 @@ final class HomePageViewController: NSViewController { } } - -fileprivate extension NSStoryboard { - - static let homePage = NSStoryboard(name: "HomePage", bundle: .main) - -} diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index ee85568834..d6c9aa0557 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -3453,7 +3453,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Importing %d bookmarks" + "value" : "Importing %d bookmarks…" } } } @@ -3957,7 +3957,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Importing %d passwords" + "value" : "Importing %d passwords…" } } } diff --git a/DuckDuckGo/MainWindow/Main.storyboard b/DuckDuckGo/MainWindow/Main.storyboard deleted file mode 100644 index faaf57be5f..0000000000 --- a/DuckDuckGo/MainWindow/Main.storyboard +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 4a74cd34d3..f5cfe4a1ea 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -23,23 +23,24 @@ import Common final class MainViewController: NSViewController { - @IBOutlet weak var tabBarContainerView: NSView! - @IBOutlet weak var navigationBarContainerView: NSView! - @IBOutlet weak var webContainerView: NSView! - @IBOutlet weak var findInPageContainerView: NSView! - @IBOutlet weak var bookmarksBarContainerView: NSView! - @IBOutlet var navigationBarTopConstraint: NSLayoutConstraint! - @IBOutlet var addressBarHeightConstraint: NSLayoutConstraint! - @IBOutlet var bookmarksBarHeightConstraint: NSLayoutConstraint! - - @IBOutlet var divider: NSView! - - private(set) var tabBarViewController: TabBarViewController! - private(set) var navigationBarViewController: NavigationBarViewController! - private(set) var browserTabViewController: BrowserTabViewController! - private(set) var findInPageViewController: FindInPageViewController! - private(set) var fireViewController: FireViewController! - private(set) var bookmarksBarViewController: BookmarksBarViewController! + private let tabBarContainerView = NSView() + private let navigationBarContainerView = NSView() + private let webContainerView = NSView() + private let findInPageContainerView = NSView().hidden() + private let bookmarksBarContainerView = NSView() + private let fireContainerView = NSView() + private var navigationBarTopConstraint: NSLayoutConstraint! + private var addressBarHeightConstraint: NSLayoutConstraint! + private var bookmarksBarHeightConstraint: NSLayoutConstraint! + + private let divider = ColorView(frame: .zero, backgroundColor: .separatorColor) + + let tabBarViewController: TabBarViewController + let navigationBarViewController: NavigationBarViewController + let browserTabViewController: BrowserTabViewController + let findInPageViewController: FindInPageViewController + let fireViewController: FireViewController + let bookmarksBarViewController: BookmarksBarViewController let tabCollectionViewModel: TabCollectionViewModel let isBurner: Bool @@ -59,15 +60,98 @@ final class MainViewController: NSViewController { } required init?(coder: NSCoder) { - self.tabCollectionViewModel = TabCollectionViewModel() - self.isBurner = tabCollectionViewModel.isBurner - super.init(coder: coder) + fatalError("MainViewController: Bad initializer") } - init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel) { + init(tabCollectionViewModel: TabCollectionViewModel? = nil, + bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + let tabCollectionViewModel = tabCollectionViewModel ?? TabCollectionViewModel() self.tabCollectionViewModel = tabCollectionViewModel self.isBurner = tabCollectionViewModel.isBurner - super.init(coder: coder) + + tabBarViewController = TabBarViewController.create(tabCollectionViewModel: tabCollectionViewModel) + navigationBarViewController = NavigationBarViewController.create(tabCollectionViewModel: tabCollectionViewModel, isBurner: isBurner) + browserTabViewController = BrowserTabViewController.create(tabCollectionViewModel: tabCollectionViewModel) + findInPageViewController = FindInPageViewController.create() + fireViewController = FireViewController.create(tabCollectionViewModel: tabCollectionViewModel) + bookmarksBarViewController = BookmarksBarViewController.create(tabCollectionViewModel: tabCollectionViewModel, bookmarkManager: bookmarkManager) + + super.init(nibName: nil, bundle: nil) + + findInPageViewController.delegate = self + } + + override func loadView() { + view = MainView(frame: NSRect(x: 0, y: 0, width: 600, height: 660)) + + for subview in [ + tabBarContainerView, + divider, + bookmarksBarContainerView, + navigationBarContainerView, + webContainerView, + findInPageContainerView, + fireContainerView, + ] { + subview.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(subview) + } + + addConstraints() + + addAndLayoutChild(tabBarViewController, into: tabBarContainerView) + addAndLayoutChild(bookmarksBarViewController, into: bookmarksBarContainerView) + addAndLayoutChild(navigationBarViewController, into: navigationBarContainerView) + addAndLayoutChild(browserTabViewController, into: webContainerView) + addAndLayoutChild(findInPageViewController, into: findInPageContainerView) + addAndLayoutChild(fireViewController, into: fireContainerView) + } + + private func addConstraints() { + bookmarksBarHeightConstraint = bookmarksBarContainerView.heightAnchor.constraint(equalToConstant: 34) + + navigationBarTopConstraint = navigationBarContainerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 38) + addressBarHeightConstraint = navigationBarContainerView.heightAnchor.constraint(equalToConstant: 42) + + NSLayoutConstraint.activate([ + tabBarContainerView.topAnchor.constraint(equalTo: view.topAnchor), + tabBarContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tabBarContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tabBarContainerView.heightAnchor.constraint(equalToConstant: 38), + + divider.topAnchor.constraint(equalTo: navigationBarContainerView.bottomAnchor), + divider.leadingAnchor.constraint(equalTo: view.leadingAnchor), + divider.trailingAnchor.constraint(equalTo: view.trailingAnchor), + divider.heightAnchor.constraint(equalToConstant: 1), + + bookmarksBarContainerView.topAnchor.constraint(equalTo: divider.bottomAnchor), + bookmarksBarContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + bookmarksBarContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + bookmarksBarHeightConstraint, + + navigationBarTopConstraint, + navigationBarContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationBarContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + addressBarHeightConstraint, + + webContainerView.topAnchor.constraint(equalTo: bookmarksBarContainerView.bottomAnchor), + webContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + webContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + webContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + webContainerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 512), + webContainerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 178), + + findInPageContainerView.topAnchor.constraint(equalTo: bookmarksBarContainerView.bottomAnchor, constant: -4), + findInPageContainerView.topAnchor.constraint(equalTo: navigationBarContainerView.bottomAnchor, constant: -4).priority(900), + findInPageContainerView.centerXAnchor.constraint(equalTo: navigationBarContainerView.centerXAnchor), + findInPageContainerView.widthAnchor.constraint(equalToConstant: 400), + findInPageContainerView.heightAnchor.constraint(equalToConstant: 40), + + fireContainerView.topAnchor.constraint(equalTo: view.topAnchor), + fireContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + fireContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + fireContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + ]) } override func viewDidLoad() { @@ -178,62 +262,7 @@ final class MainViewController: NSViewController { func windowWillClose() { eventMonitorCancellables.removeAll() - tabBarViewController?.hideTabPreview() - } - - @IBSegueAction - func createTabBarViewController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> TabBarViewController? { - guard let tabBarViewController = TabBarViewController(coder: coder, - tabCollectionViewModel: tabCollectionViewModel) else { - fatalError("MainViewController: Failed to init TabBarViewController") - } - - self.tabBarViewController = tabBarViewController - return tabBarViewController - } - - @IBSegueAction - func createNavigationBarViewController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> NavigationBarViewController? { - guard let navigationBarViewController = NavigationBarViewController(coder: coder, tabCollectionViewModel: tabCollectionViewModel, isBurner: isBurner) else { - fatalError("MainViewController: Failed to init NavigationBarViewController") - } - - self.navigationBarViewController = navigationBarViewController - return navigationBarViewController - } - - @IBSegueAction - func createWebViewController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> BrowserTabViewController? { - guard let browserTabViewController = BrowserTabViewController(coder: coder, - tabCollectionViewModel: tabCollectionViewModel) else { - fatalError("MainViewController: Failed to init BrowserTabViewController") - } - - self.browserTabViewController = browserTabViewController - return browserTabViewController - } - - @IBSegueAction - func createFindInPageViewController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> FindInPageViewController? { - let findInPageViewController = FindInPageViewController(coder: coder) - findInPageViewController?.delegate = self - self.findInPageViewController = findInPageViewController - return findInPageViewController - } - - @IBSegueAction - func createFireViewController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> FireViewController? { - let fireViewController = FireViewController(coder: coder, - tabCollectionViewModel: tabCollectionViewModel) - self.fireViewController = fireViewController - return fireViewController - } - - @IBSegueAction - func createBookmarksBar(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> BookmarksBarViewController? { - let bookmarksBarViewController = BookmarksBarViewController(coder: coder, tabCollectionViewModel: tabCollectionViewModel) - self.bookmarksBarViewController = bookmarksBarViewController - return bookmarksBarViewController + tabBarViewController.hideTabPreview() } func toggleBookmarksBarVisibility() { @@ -256,7 +285,7 @@ final class MainViewController: NSViewController { bookmarksBarViewController.view.removeFromSuperview() } - bookmarksBarHeightConstraint.constant = showBookmarksBar ? 34 : 0 + bookmarksBarHeightConstraint?.constant = showBookmarksBar ? 34 : 0 updateDividerColor() } @@ -265,7 +294,7 @@ final class MainViewController: NSViewController { NSAppearance.withAppAppearance { let isHomePage = tabCollectionViewModel.selectedTabViewModel?.tab.content == .homePage let backgroundColor: NSColor = (bookmarksBarIsVisible || isHomePage) ? .addressBarFocusedBackgroundColor : .addressBarSolidSeparatorColor - (divider as? ColorView)?.backgroundColor = backgroundColor + divider.backgroundColor = backgroundColor } } @@ -362,15 +391,15 @@ final class MainViewController: NSViewController { private func updateFindInPage() { guard let model = tabCollectionViewModel.selectedTabViewModel?.findInPage else { - findInPageViewController?.makeMeFirstResponder() + findInPageViewController.makeMeFirstResponder() os_log("MainViewController: Failed to get find in page model", type: .error) return } findInPageContainerView.isHidden = !model.isVisible - findInPageViewController?.model = model + findInPageViewController.model = model if model.isVisible { - findInPageViewController?.makeMeFirstResponder() + findInPageViewController.makeMeFirstResponder() } } @@ -505,7 +534,7 @@ extension MainViewController { case kVK_Escape: var isHandled = false if !findInPageContainerView.isHidden { - findInPageViewController?.findInPageDone(self) + findInPageViewController.findInPageDone(self) isHandled = true } if let addressBarVC = navigationBarViewController.addressBarViewController { @@ -556,3 +585,27 @@ extension MainViewController { } } + +#if DEBUG +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: 700, height: 660)) { + + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ + BookmarkFolder(id: "1", title: "Folder", children: [ + Bookmark(id: "2", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: true) + ]), + Bookmark(id: "3", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: true, parentFolderUUID: "1") + ])) + bkman.loadBookmarks() + + let vc = MainViewController(bookmarkManager: bkman) + var c: AnyCancellable! + c = vc.publisher(for: \.view.window).sink { window in + window?.titlebarAppearsTransparent = true + window?.titleVisibility = .hidden + withExtendedLifetime(c) {} + } + + return vc +} +#endif diff --git a/DuckDuckGo/MainWindow/MainWindowController.swift b/DuckDuckGo/MainWindow/MainWindowController.swift index ad8d2bce08..56099fe508 100644 --- a/DuckDuckGo/MainWindow/MainWindowController.swift +++ b/DuckDuckGo/MainWindow/MainWindowController.swift @@ -106,10 +106,7 @@ final class MainWindowController: NSWindowController { private var trafficLightsAlphaCancellable: AnyCancellable? private func subscribeToTrafficLightsAlpha() { - guard let tabBarViewController = mainViewController.tabBarViewController else { - assertionFailure("MainWindowController: tabBarViewController is nil" ) - return - } + let tabBarViewController = mainViewController.tabBarViewController // slide tabs to the left in full screen trafficLightsAlphaCancellable = window?.standardWindowButton(.closeButton)? @@ -150,11 +147,11 @@ final class MainWindowController: NSWindowController { } private func moveTabBarView(toTitlebarView: Bool) { - guard let newParentView = toTitlebarView ? titlebarView : mainViewController.view, - let tabBarViewController = mainViewController.tabBarViewController else { + guard let newParentView = toTitlebarView ? titlebarView : mainViewController.view else { assertionFailure("Failed to move tab bar view") return } + let tabBarViewController = mainViewController.tabBarViewController tabBarViewController.view.removeFromSuperview() if toTitlebarView { diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index c2f157c076..a5ebf23908 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -278,7 +278,7 @@ extension MainViewController { @objc func openLocation(_ sender: Any?) { makeKeyIfNeeded() - guard let addressBarTextField = navigationBarViewController?.addressBarViewController?.addressBarTextField else { + guard let addressBarTextField = navigationBarViewController.addressBarViewController?.addressBarTextField else { os_log("MainViewController: Cannot reference address bar text field", type: .error) return } @@ -348,9 +348,9 @@ extension MainViewController { } navigationBarViewController = wc.mainViewController.navigationBarViewController } - navigationBarViewController?.view.window?.makeKeyAndOrderFront(nil) + navigationBarViewController.view.window?.makeKeyAndOrderFront(nil) } - navigationBarViewController?.toggleDownloadsPopover(keepButtonVisible: false) + navigationBarViewController.toggleDownloadsPopover(keepButtonVisible: false) } @objc func toggleBookmarksBarFromMenu(_ sender: Any) { @@ -459,7 +459,7 @@ extension MainViewController { } makeKeyIfNeeded() - navigationBarViewController? + navigationBarViewController .addressBarViewController? .addressBarButtonsViewController? .openBookmarkPopover(setFavorite: false, accessPoint: .init(sender: sender, default: .moreMenu)) @@ -472,7 +472,7 @@ extension MainViewController { } makeKeyIfNeeded() - navigationBarViewController? + navigationBarViewController .addressBarViewController? .addressBarButtonsViewController? .openBookmarkPopover(setFavorite: true, accessPoint: .init(sender: sender, default: .moreMenu)) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 778a1ad9a4..ffbffc429c 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -63,6 +63,8 @@ final class NavigationBarViewController: NSViewController { @IBOutlet var buttonsTopConstraint: NSLayoutConstraint! @IBOutlet var logoWidthConstraint: NSLayoutConstraint! + private let downloadListCoordinator: DownloadListCoordinator + lazy var downloadsProgressView: CircularProgressView = { let bounds = downloadsButton.bounds let width: CGFloat = 27.0 @@ -111,12 +113,14 @@ final class NavigationBarViewController: NSViewController { private let networkProtectionFeatureActivation: NetworkProtectionFeatureActivation #endif - required init?(coder: NSCoder) { - fatalError("NavigationBarViewController: Bad initializer") +#if NETWORK_PROTECTION + static func create(tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), downloadListCoordinator: DownloadListCoordinator = .shared) -> NavigationBarViewController { + NSStoryboard(name: "NavigationBar", bundle: nil).instantiateInitialController { coder in + self.init(coder: coder, tabCollectionViewModel: tabCollectionViewModel, isBurner: isBurner, networkProtectionFeatureActivation: networkProtectionFeatureActivation, downloadListCoordinator: downloadListCoordinator) + }! } -#if NETWORK_PROTECTION - init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore()) { + init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation, downloadListCoordinator: DownloadListCoordinator) { let vpnBundleID = Bundle.main.vpnMenuAgentBundleId let ipcClient = TunnelControllerIPCClient(machServiceName: vpnBundleID) @@ -129,21 +133,33 @@ final class NavigationBarViewController: NSViewController { self.networkProtectionButtonModel = NetworkProtectionNavBarButtonModel(popoverManager: networkProtectionPopoverManager) self.isBurner = isBurner self.networkProtectionFeatureActivation = networkProtectionFeatureActivation + self.downloadListCoordinator = downloadListCoordinator goBackButtonMenuDelegate = NavigationButtonMenuDelegate(buttonType: .back, tabCollectionViewModel: tabCollectionViewModel) goForwardButtonMenuDelegate = NavigationButtonMenuDelegate(buttonType: .forward, tabCollectionViewModel: tabCollectionViewModel) super.init(coder: coder) } #else - init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool) { + static func create(tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, downloadListCoordinator: DownloadListCoordinator = .shared) -> NavigationBarViewController { + NSStoryboard(name: "NavigationBar", bundle: nil).instantiateInitialController { coder in + self.init(coder: coder, tabCollectionViewModel: tabCollectionViewModel, isBurner: isBurner, downloadListCoordinator: downloadListCoordinator) + }! + } + + init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, downloadListCoordinator: DownloadListCoordinator) { self.popovers = NavigationBarPopovers() self.tabCollectionViewModel = tabCollectionViewModel self.isBurner = isBurner + self.downloadListCoordinator = downloadListCoordinator goBackButtonMenuDelegate = NavigationButtonMenuDelegate(buttonType: .back, tabCollectionViewModel: tabCollectionViewModel) goForwardButtonMenuDelegate = NavigationButtonMenuDelegate(buttonType: .forward, tabCollectionViewModel: tabCollectionViewModel) super.init(coder: coder) } #endif + required init?(coder: NSCoder) { + fatalError("NavigationBarViewController: Bad initializer") + } + override func viewDidLoad() { super.viewDidLoad() @@ -582,7 +598,7 @@ final class NavigationBarViewController: NSViewController { } private func subscribeToDownloads() { - DownloadListCoordinator.shared.updates + downloadListCoordinator.updates .throttle(for: 1.0, scheduler: DispatchQueue.main, latest: true) .sink { [weak self] update in guard let self = self else { return } @@ -606,11 +622,11 @@ final class NavigationBarViewController: NSViewController { self.updateDownloadsButton() } .store(in: &downloadsCancellables) - DownloadListCoordinator.shared.progress + downloadListCoordinator.progress .publisher(for: \.fractionCompleted) .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) - .map { _ in - let progress = DownloadListCoordinator.shared.progress + .map { [downloadListCoordinator] _ in + let progress = downloadListCoordinator.progress return progress.fractionCompleted == 1.0 || progress.totalUnitCount == 0 ? nil : progress.fractionCompleted } .assign(to: \.progress, onWeaklyHeld: downloadsProgressView) @@ -708,7 +724,7 @@ final class NavigationBarViewController: NSViewController { return } - let hasActiveDownloads = DownloadListCoordinator.shared.hasActiveDownloads + let hasActiveDownloads = downloadListCoordinator.hasActiveDownloads downloadsButton.image = hasActiveDownloads ? Self.Constants.activeDownloadsImage : Self.Constants.inactiveDownloadsImage let isTimerActive = downloadsButtonHidingTimer != nil @@ -754,7 +770,7 @@ final class NavigationBarViewController: NSViewController { private func hideDownloadButtonIfPossible() { if LocalPinningManager.shared.isPinned(.downloads) || - DownloadListCoordinator.shared.hasActiveDownloads || + downloadListCoordinator.hasActiveDownloads || popovers.isDownloadsPopoverShown { return } downloadsButton.isHidden = true diff --git a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift index e77c4434b8..c7f246c0ee 100644 --- a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift @@ -121,7 +121,7 @@ final class AutofillPreferencesModel: ObservableObject { @MainActor func showAutofillPopover(_ selectedCategory: SecureVaultSorting.Category = .allItems) { guard let parentWindowController = WindowControllersManager.shared.lastKeyMainWindowController else { return } - guard let navigationViewController = parentWindowController.mainViewController.navigationBarViewController else { return } + let navigationViewController = parentWindowController.mainViewController.navigationBarViewController navigationViewController.showPasswordManagerPopover(selectedCategory: selectedCategory) } diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index e2cd020d69..60933937e5 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -57,7 +57,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { } @Published var shouldShowErrorMessage: Bool = false - @Published private(set) var errorMessage: String? + @Published private(set) var syncErrorMessage: SyncErrorMessage? @Published var isCreatingAccount: Bool = false @@ -120,7 +120,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { } .store(in: &cancellables) - $errorMessage + $syncErrorMessage .map { $0 != nil } .receive(on: DispatchQueue.main) .assign(to: \.shouldShowErrorMessage, onWeaklyHeld: self) @@ -179,7 +179,8 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncBookmarksPaused.rawValue) UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) } catch { - errorMessage = String(describing: error) + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription) } } } @@ -193,7 +194,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { @MainActor func manageLogins() { guard let parentWindowController = WindowControllersManager.shared.lastKeyMainWindowController else { return } - guard let navigationViewController = parentWindowController.mainViewController.navigationBarViewController else { return } + let navigationViewController = parentWindowController.mainViewController.navigationBarViewController navigationViewController.showPasswordManagerPopover(selectedCategory: .allItems) } @@ -328,7 +329,8 @@ extension SyncPreferences: ManagementDialogModelDelegate { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncBookmarksPaused.rawValue) UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) } catch { - managementDialogModel.errorMessage = String(describing: error) + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .unableToDeleteData, description: error.localizedDescription) } } } @@ -338,9 +340,11 @@ extension SyncPreferences: ManagementDialogModelDelegate { do { self.devices = [] let devices = try await syncService.updateDeviceName(name) + managementDialogModel.endFlow() mapDevices(devices) } catch { - managementDialogModel.errorMessage = String(describing: error) + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .unableToUpdateDeviceName, description: error.localizedDescription) } } } @@ -380,7 +384,8 @@ extension SyncPreferences: ManagementDialogModelDelegate { Pixel.fire(.syncSignupDirect) presentDialog(for: .saveRecoveryCode(recoveryCode ?? "")) } catch { - managementDialogModel.errorMessage = String(describing: error) + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .unableToSync, description: error.localizedDescription) } } } @@ -405,7 +410,8 @@ extension SyncPreferences: ManagementDialogModelDelegate { } } catch { if syncService.account == nil { - managementDialogModel.errorMessage = String(describing: error) + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .unableToSync, description: error.localizedDescription) } } } @@ -420,7 +426,8 @@ extension SyncPreferences: ManagementDialogModelDelegate { Task { @MainActor in do { guard let syncCode = try? SyncCode.decodeBase64String(recoveryCode) else { - managementDialogModel.errorMessage = "Invalid code" + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .invalidCode, description: "") return } presentDialog(for: .prepareToSync) @@ -447,11 +454,13 @@ extension SyncPreferences: ManagementDialogModelDelegate { // The UI will update when the devices list changes. } else { - managementDialogModel.errorMessage = "Invalid code" + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .invalidCode, description: "") return } } catch { - managementDialogModel.errorMessage = String(describing: error) + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .unableToSync, description: error.localizedDescription) } } } @@ -481,7 +490,8 @@ extension SyncPreferences: ManagementDialogModelDelegate { do { try data.writeFileWithProgress(to: location) } catch { - managementDialogModel.errorMessage = String(describing: error) + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .unableCreateRecoveryPDF, description: error.localizedDescription) } } @@ -495,8 +505,8 @@ extension SyncPreferences: ManagementDialogModelDelegate { refreshDevices() managementDialogModel.endFlow() } catch { - managementDialogModel.errorMessage = String(describing: error) - } + managementDialogModel.syncErrorMessage + = SyncErrorMessage(type: .unableToRemoveDevice, description: error.localizedDescription) } } } diff --git a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift index 8c0c41fb07..95b037d074 100644 --- a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift +++ b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift @@ -777,9 +777,9 @@ final class PasswordManagementViewController: NSViewController { } menu.items = [ - createMenuItem(title: UserText.pmNewCard, action: #selector(createNewCreditCard), imageName: "CreditCardGlyph"), createMenuItem(title: UserText.pmNewLogin, action: #selector(createNewLogin), imageName: "LoginGlyph"), - createMenuItem(title: UserText.pmNewIdentity, action: #selector(createNewIdentity), imageName: "IdentityGlyph") + createMenuItem(title: UserText.pmNewIdentity, action: #selector(createNewIdentity), imageName: "IdentityGlyph"), + createMenuItem(title: UserText.pmNewCard, action: #selector(createNewCreditCard), imageName: "CreditCardGlyph"), ] return menu diff --git a/DuckDuckGo/Tab/View/Base.lproj/BrowserTab.storyboard b/DuckDuckGo/Tab/View/Base.lproj/BrowserTab.storyboard index 5a36ed9b95..b21cbc5cda 100644 --- a/DuckDuckGo/Tab/View/Base.lproj/BrowserTab.storyboard +++ b/DuckDuckGo/Tab/View/Base.lproj/BrowserTab.storyboard @@ -1,7 +1,7 @@ - + - + @@ -9,18 +9,15 @@ - - + + - - - - - - - - - - - - diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index 18ada18f75..4d24017ae6 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -24,11 +24,11 @@ import SwiftUI import BrowserServicesKit final class BrowserTabViewController: NSViewController { - @IBOutlet weak var errorView: NSView! - @IBOutlet weak var homePageView: NSView! - @IBOutlet weak var errorMessageLabel: NSTextField! - @IBOutlet weak var hoverLabel: NSTextField! - @IBOutlet weak var hoverLabelContainer: NSView! + @IBOutlet var errorView: NSView! + @IBOutlet var homePageView: NSView! + @IBOutlet var errorMessageLabel: NSTextField! + @IBOutlet var hoverLabel: NSTextField! + @IBOutlet var hoverLabelContainer: NSView! private weak var webView: WebView? private weak var webViewContainer: NSView? private weak var webViewSnapshot: NSView? @@ -53,6 +53,12 @@ final class BrowserTabViewController: NSViewController { private var transientTabContentViewController: NSViewController? + static func create(tabCollectionViewModel: TabCollectionViewModel) -> BrowserTabViewController { + NSStoryboard(name: "BrowserTab", bundle: nil).instantiateInitialController { coder in + self.init(coder: coder, tabCollectionViewModel: tabCollectionViewModel) + }! + } + required init?(coder: NSCoder) { fatalError("BrowserTabViewController: Bad initializer") } @@ -63,18 +69,12 @@ final class BrowserTabViewController: NSViewController { super.init(coder: coder) } - @IBSegueAction func createHomePageViewController(_ coder: NSCoder) -> NSViewController? { - guard let controller = HomePageViewController(coder: coder, - tabCollectionViewModel: tabCollectionViewModel, - bookmarkManager: LocalBookmarkManager.shared) else { - fatalError("BrowserTabViewController: Failed to init HomePageViewController") - } - return controller - } - override func viewDidLoad() { super.viewDidLoad() + let homePageViewController = HomePageViewController(tabCollectionViewModel: tabCollectionViewModel, bookmarkManager: LocalBookmarkManager.shared) + self.addAndLayoutChild(homePageViewController, into: homePageView) + hoverLabelContainer.alphaValue = 0 subscribeToTabs() subscribeToSelectedTabViewModel() diff --git a/DuckDuckGo/TabBar/View/TabBarViewController.swift b/DuckDuckGo/TabBar/View/TabBarViewController.swift index 0799f7bdb1..56ae7d4279 100644 --- a/DuckDuckGo/TabBar/View/TabBarViewController.swift +++ b/DuckDuckGo/TabBar/View/TabBarViewController.swift @@ -69,6 +69,12 @@ final class TabBarViewController: NSViewController { } } + static func create(tabCollectionViewModel: TabCollectionViewModel) -> TabBarViewController { + NSStoryboard(name: "TabBar", bundle: nil).instantiateInitialController { coder in + self.init(coder: coder, tabCollectionViewModel: tabCollectionViewModel) + }! + } + required init?(coder: NSCoder) { fatalError("TabBarViewController: Bad initializer") } diff --git a/DuckDuckGo/Windows/View/WindowsManager.swift b/DuckDuckGo/Windows/View/WindowsManager.swift index 12cebb760a..d19dad2a42 100644 --- a/DuckDuckGo/Windows/View/WindowsManager.swift +++ b/DuckDuckGo/Windows/View/WindowsManager.swift @@ -123,7 +123,6 @@ final class WindowsManager { private static let defaultPopUpWidth: CGFloat = 1024 private static let defaultPopUpHeight: CGFloat = 752 - private static let fallbackHeadlessScreenFrame = NSRect(x: 0, y: 100, width: 1280, height: 900) class func openPopUpWindow(with tab: Tab, origin: NSPoint?, contentSize: NSSize?) { if let mainWindowController = WindowControllersManager.shared.lastKeyMainWindowController, @@ -133,7 +132,7 @@ final class WindowsManager { mainWindowController.mainViewController.tabCollectionViewModel.insert(tab, selected: true) } else { - let screenFrame = (self.findPositioningSourceWindow(for: tab)?.screen ?? .main)?.visibleFrame ?? Self.fallbackHeadlessScreenFrame + let screenFrame = (self.findPositioningSourceWindow(for: tab)?.screen ?? .main)?.visibleFrame ?? NSScreen.fallbackHeadlessScreenFrame // limit popUp content size to screen visible frame // fallback to default if nil or zero @@ -159,23 +158,7 @@ final class WindowsManager { contentSize: NSSize? = nil, popUp: Bool = false, burnerMode: BurnerMode) -> MainWindowController { - let mainViewController: MainViewController - do { - mainViewController = try NSException.catch { - NSStoryboard(name: "Main", bundle: .main) - .instantiateController(identifier: .mainViewController) { coder -> MainViewController? in - let model = tabCollectionViewModel ?? TabCollectionViewModel(burnerMode: burnerMode) - assert(model.burnerMode == burnerMode) - return MainViewController(coder: coder, tabCollectionViewModel: model) - } - } - } catch { -#if DEBUG - fatalError("WindowsManager.makeNewWindow: \(error)") -#else - fatalError("WindowsManager.makeNewWindow: the App Bundle seems to be removed") -#endif - } + let mainViewController = MainViewController(tabCollectionViewModel: tabCollectionViewModel ?? TabCollectionViewModel(burnerMode: burnerMode)) var contentSize = contentSize ?? NSSize(width: 1024, height: 790) contentSize.width = min(NSScreen.main?.frame.size.width ?? 1024, max(contentSize.width, 300)) diff --git a/DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionPixelTests.swift similarity index 98% rename from DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift rename to LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionPixelTests.swift index 212606714a..1d32a2e762 100644 --- a/DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionPixelTests.swift @@ -16,13 +16,15 @@ // limitations under the License. // +#if DBP + import DataBrokerProtection import Networking import Foundation import PixelKit import PixelKitTestingUtilities import XCTest -@testable import DuckDuckGo_DBP +@testable import DuckDuckGo_Privacy_Browser /// Tests to ensure that DBP pixels sent from the main app work well /// @@ -143,3 +145,5 @@ final class DataBrokerProtectionPixelTests: XCTestCase { } } } + +#endif diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift index 366f35035d..65cf08664c 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift @@ -259,7 +259,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - await database.save(newProfile) + _ = await database.save(newProfile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertFalse(vault.wasUpdateProfileQueryCalled) diff --git a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift index 99504d64c1..b9576d6a6a 100644 --- a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift +++ b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift @@ -228,51 +228,51 @@ final class PixelKitTests: XCTestCase { wait(for: [fireCallbackCalled], timeout: 0.5) } -// /// Test firing a daily pixel a few times -// func testDailyPixelFrequency() { -// // Prepare test parameters -// let appVersion = "1.0.5" -// let headers = ["a": "2", "b": "3", "c": "2000"] -// let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") -// let event = TestEvent.dailyEvent -// let userDefaults = userDefaults() -// -// let timeMachine = TimeMachine() -// -// // Set expectations -// let fireCallbackCalled = expectation(description: "Expect the pixel firing callback to be called") -// fireCallbackCalled.expectedFulfillmentCount = 3 -// fireCallbackCalled.assertForOverFulfill = true -// -// // Prepare mock to validate expectations -// let pixelKit = PixelKit(dryRun: false, -// appVersion: appVersion, -// defaultHeaders: headers, -// log: log, -// dailyPixelCalendar: nil, -// dateGenerator: timeMachine.now, -// defaults: userDefaults) { _, _, _, _, _, _ in -// fireCallbackCalled.fulfill() -// } -// -// // Run test -// pixelKit.fire(event, frequency: .dailyOnly) // Fired -// -// timeMachine.travel(by: 60 * 60 * 2) -// pixelKit.fire(event, frequency: .dailyOnly) // Skipped (2 hours since last fire) -// -// timeMachine.travel(by: 60 * 60 * 24 + 1000) -// pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours + 1000 seconds since last fire) -// -// timeMachine.travel(by: 60 * 60 * 10) -// pixelKit.fire(event, frequency: .dailyOnly) // Skipped (10 hours since last fire) -// -// timeMachine.travel(by: 60 * 60 * 14) -// pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours since last fire) -// -// // Wait for expectations to be fulfilled -// wait(for: [fireCallbackCalled], timeout: 0.5) -// } + /// Test firing a daily pixel a few times + func testDailyPixelFrequency() { + // Prepare test parameters + let appVersion = "1.0.5" + let headers = ["a": "2", "b": "3", "c": "2000"] + let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") + let event = TestEvent.dailyEvent + let userDefaults = userDefaults() + + let timeMachine = TimeMachine() + + // Set expectations + let fireCallbackCalled = expectation(description: "Expect the pixel firing callback to be called") + fireCallbackCalled.expectedFulfillmentCount = 3 + fireCallbackCalled.assertForOverFulfill = true + + // Prepare mock to validate expectations + let pixelKit = PixelKit(dryRun: false, + appVersion: appVersion, + defaultHeaders: headers, + log: log, + dailyPixelCalendar: nil, + dateGenerator: timeMachine.now, + defaults: userDefaults) { _, _, _, _, _, _ in + fireCallbackCalled.fulfill() + } + + // Run test + pixelKit.fire(event, frequency: .dailyOnly) // Fired + + timeMachine.travel(by: 60 * 60 * 2) + pixelKit.fire(event, frequency: .dailyOnly) // Skipped (2 hours since last fire) + + timeMachine.travel(by: 60 * 60 * 24 + 1000) + pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours + 1000 seconds since last fire) + + timeMachine.travel(by: 60 * 60 * 10) + pixelKit.fire(event, frequency: .dailyOnly) // Skipped (10 hours since last fire) + + timeMachine.travel(by: 60 * 60 * 14) + pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours since last fire) + + // Wait for expectations to be fulfilled + wait(for: [fireCallbackCalled], timeout: 0.5) + } /// Test firing a unique pixel func testUniquePixel() { @@ -321,7 +321,7 @@ final class PixelKitTests: XCTestCase { } private class TimeMachine { - private var date = Calendar.current.startOfDay(for: Date()) + private var date = Date(timeIntervalSince1970: 0) func travel(by timeInterval: TimeInterval) { date = date.addingTimeInterval(timeInterval) diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementDialogModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementDialogModel.swift index 2265e212e0..eaf0aa2808 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementDialogModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementDialogModel.swift @@ -40,12 +40,12 @@ public final class ManagementDialogModel: ObservableObject { public var codeToDisplay: String? @Published public var shouldShowErrorMessage: Bool = false - @Published public var errorMessage: String? + @Published public var syncErrorMessage: SyncErrorMessage? public weak var delegate: ManagementDialogModelDelegate? public init() { - shouldShowErrorMessageCancellable = $errorMessage + shouldShowErrorMessageCancellable = $syncErrorMessage .map { $0 != nil } .receive(on: DispatchQueue.main) .sink { [weak self] hasError in diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift index a1218f2153..89cb583ab2 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift @@ -23,7 +23,7 @@ public protocol ManagementViewModel: ObservableObject { var isSyncEnabled: Bool { get } var isCreatingAccount: Bool { get } var shouldShowErrorMessage: Bool { get set } - var errorMessage: String? { get } + var syncErrorMessage: SyncErrorMessage? { get } var isSyncBookmarksPaused: Bool { get } var isSyncCredentialsPaused: Bool { get } @@ -48,3 +48,49 @@ public protocol ManagementViewModel: ObservableObject { func recoverDataPressed() func turnOffSyncPressed() } + +public enum SyncErrorType { + case unableToSync + case unableToGetDevices + case unableToUpdateDeviceName + case unableToTurnSyncOff + case unableToDeleteData + case unableToRemoveDevice + case invalidCode + case unableCreateRecoveryPDF + + var title: String { + return UserText.syncErrorAlertTitle + } + + var description: String { + switch self { + case .unableToSync: + return UserText.unableToSyncDescription + case .unableToGetDevices: + return UserText.unableToGetDevicesDescription + case .unableToUpdateDeviceName: + return UserText.unableToUpdateDeviceNameDescription + case .unableToTurnSyncOff: + return UserText.unableToTurnSyncOffDescription + case .unableToDeleteData: + return UserText.unableToDeleteDataDescription + case .unableToRemoveDevice: + return UserText.unableToRemoveDeviceDescription + case .invalidCode: + return UserText.invalidCodeDescription + case .unableCreateRecoveryPDF: + return UserText.unableCreateRecoveryPdfDescription + } + } +} + +public struct SyncErrorMessage { + var type: SyncErrorType + var errorDescription: String + + public init(type: SyncErrorType, description: String) { + self.type = type + self.errorDescription = description + } +} diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/Dialogs/DeviceDetailsView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/Dialogs/DeviceDetailsView.swift index 017021da64..dc3b53f8b1 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/Dialogs/DeviceDetailsView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/Dialogs/DeviceDetailsView.swift @@ -26,6 +26,7 @@ struct DeviceDetailsView: View { let device: SyncDevice @State var deviceName = "" + @State private var isLoading = false var canSave: Bool { !deviceName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && @@ -35,39 +36,42 @@ struct DeviceDetailsView: View { func submit() { guard canSave else { return } model.delegate?.updateDeviceName(deviceName) - model.endFlow() } var body: some View { - SyncDialog { - VStack(spacing: 20) { - SyncUIViews.TextHeader(text: UserText.deviceDetailsTitle) - - HStack { - Text(UserText.deviceDetailsLabel) - .font(.system(size: 13, weight: .semibold)) - TextField(UserText.deviceDetailsPrompt, text: $deviceName, onCommit: submit) - .textFieldStyle(RoundedBorderTextFieldStyle()) + if isLoading { + ProgressView() + .padding() + } else { + SyncDialog { + VStack(spacing: 20) { + SyncUIViews.TextHeader(text: UserText.deviceDetailsTitle) + HStack { + Text(UserText.deviceDetailsLabel) + .font(.system(size: 13, weight: .semibold)) + TextField(UserText.deviceDetailsPrompt, text: $deviceName, onCommit: submit) + .textFieldStyle(RoundedBorderTextFieldStyle()) + } + .padding(.horizontal, 10) + .padding(.vertical, 14.5) + .roundedBorder() } - .padding(.horizontal, 10) - .padding(.vertical, 14.5) - .roundedBorder() - } - } buttons: { - Button(UserText.cancel) { - model.endFlow() + } buttons: { + Button(UserText.cancel) { + model.endFlow() + } + .buttonStyle(DismissActionButtonStyle()) + Button(UserText.ok) { + submit() + isLoading = true + } + .disabled(!canSave) + .buttonStyle(DefaultActionButtonStyle(enabled: canSave)) } - .buttonStyle(DismissActionButtonStyle()) - Button(UserText.ok) { - submit() + .frame(width: 360, height: 178) + .onAppear { + deviceName = device.name } - .disabled(!canSave) - .buttonStyle(DefaultActionButtonStyle(enabled: canSave)) - - } - .frame(width: 360, height: 178) - .onAppear { - deviceName = device.name } } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/Dialogs/SaveRecoveryPDFView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/Dialogs/SaveRecoveryPDFView.swift index e4b965bd0c..61e93944d3 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/Dialogs/SaveRecoveryPDFView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/Dialogs/SaveRecoveryPDFView.swift @@ -31,17 +31,14 @@ struct SaveRecoveryPDFView: View { SyncUIViews.TextDetailMultiline(text: UserText.recoveryPDFExplanation) } VStack(alignment: .leading, spacing: 20) { - HStack { - QRCode(string: code, size: CGSize(width: 56, height: 56)) - Text(code) - .kerning(2) - .multilineTextAlignment(.leading) - .lineSpacing(5) - .lineLimit(3) - .font(Font.custom("SF Mono", size: 12)) - .fixedSize(horizontal: false, vertical: true) - } - .frame(width: 340) + Text(code) + .kerning(2) + .multilineTextAlignment(.leading) + .lineSpacing(5) + .lineLimit(3) + .font(Font.custom("SF Mono", size: 12)) + .fixedSize(horizontal: false, vertical: true) + .frame(width: 340) HStack { Button { viewModel.delegate?.copyCode() diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementDialog.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementDialog.swift index 34e25dc3af..6af2621ee1 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementDialog.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementDialog.swift @@ -36,6 +36,19 @@ public struct ManagementDialog: View { @ObservedObject public var model: ManagementDialogModel @ObservedObject public var recoveryCodeModel: RecoveryCodeViewModel + var errorTitle: String { + return model.syncErrorMessage?.type.title ?? "Sync Error" + } + + var errorDescription: String { + guard let typeDescription = model.syncErrorMessage?.type.description, + let errorDescription = model.syncErrorMessage?.errorDescription + else { + return "" + } + return typeDescription + "\n" + errorDescription + } + public init(model: ManagementDialogModel, recoveryCodeModel: RecoveryCodeViewModel = .init()) { self.model = model self.recoveryCodeModel = recoveryCodeModel @@ -45,9 +58,11 @@ public struct ManagementDialog: View { content .alert(isPresented: $model.shouldShowErrorMessage) { Alert( - title: Text("Unable to turn on Sync"), - message: Text(model.errorMessage ?? "An error occurred"), - dismissButton: .default(Text(UserText.ok)) + title: Text(errorTitle), + message: Text(errorDescription), + dismissButton: .default(Text(UserText.ok)) { + model.endFlow() + } ) } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView.swift index feb18ccd3a..11072dd02f 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView.swift @@ -51,8 +51,5 @@ public struct ManagementView: View where ViewModel: ManagementViewMod } } } - .alert(isPresented: $model.shouldShowErrorMessage) { - Alert(title: Text("Unable to turn on Sync"), message: Text(model.errorMessage ?? "An error occurred"), dismissButton: .default(Text(UserText.ok))) - } } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift index 38c7c0aa46..fe4f6c3fef 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift @@ -144,6 +144,15 @@ enum UserText { static let credentialsLimitExceededDescription = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-description", value: "Logins limit exceeded. Delete some to resume syncing.", comment: "Description for sync credentials limits exceeded warning") static let bookmarksLimitExceededAction = NSLocalizedString("prefrences.sync.bookmarks-limit-exceeded-action", value: "Manage Bookmarks", comment: "Button title for sync bookmarks limits exceeded warning to manage bookmarks") static let credentialsLimitExceededAction = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-action", value: "Manage Logins", comment: "Button title for sync credentials limits exceeded warning to manage logins") + static let syncErrorAlertTitle = NSLocalizedString("alert.sync-error", value: "Sync Error", comment: "Title for sync error alert") + static let unableToSyncDescription = NSLocalizedString("alert.unable-to-sync-description", value: "Unable to sync.", comment: "Description for unable to sync error") + static let unableToGetDevicesDescription = NSLocalizedString("alert.unable-to-get-devices-description", value: "Unable to retrieve the list of connected devices.", comment: "Description for unable to get devices error") + static let unableToUpdateDeviceNameDescription = NSLocalizedString("alert.unable-to-update-device-name-description", value: "Unable to update the name of the device.", comment: "Description for unable to update device name error") + static let unableToTurnSyncOffDescription = NSLocalizedString("alert.unable-to-turn-sync-off-description", value: "Unable to turn sync off.", comment: "Description for unable to turn sync off error") + static let unableToDeleteDataDescription = NSLocalizedString("alert.unable-to-delete-data-description", value: "Unable to delete data on the server.", comment: "Description for unable to delete data error") + static let unableToRemoveDeviceDescription = NSLocalizedString("alert.unable-to-remove-device-description", value: "Unable to remove the specified device from the synchronized devices.", comment: "Description for unable to remove device error") + static let invalidCodeDescription = NSLocalizedString("alert.invalid-code-description", value: "The code used is invalid.", comment: "Description for invalid code error") + static let unableCreateRecoveryPdfDescription = NSLocalizedString("alert.unable-to-create-recovery-pdf-description", value: "There was a problem creating the recovery PDF.", comment: "Description for unable to create recovery pdf error") static let fetchFaviconsOnboardingTitle = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-title", value: "Download Missing Icons?", comment: "Title for fetch favicons onboarding dialog") static let fetchFaviconsOnboardingMessage = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-message", value: "Do you want this device to automatically download icons for any new bookmarks synced from your other devices? This will expose the download to your network any time a bookmark is synced.", comment: "Text for fetch favicons onboarding dialog") diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 75e14c98df..5e67e77929 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -100,6 +100,41 @@ platform :mac do })) end + # Creates a new release branch and updates embedded files. + # + # - Cuts a new release branch + # - Updates submodules and embedded files + # - Pushes changes to remote + # + # @option [String] version (default: nil) Marketing version string + # + desc 'Executes the release preparation work in the repository' + lane :make_release_branch do |options| + begin + macos_codefreeze_prechecks + new_version = validate_new_version(options) + macos_create_release_branch(version: new_version) + macos_update_embedded_files + macos_update_version_config(version: new_version) + sh('git', 'push') + + sh("echo \"release_branch_name=#{RELEASE_BRANCH}/#{new_version}\" >> $GITHUB_OUTPUT") if is_ci + + rescue => exception + if exception.message == "Tests have failed" + UI.user_error! %{Tests have failed. +* If you believe the failing test is flaky, please retry the same fastlane command, + appending `resume:true`. +* If the failure looks legitimate, try to fix it, commit the fix (be sure to only + include the files you've changed while making a fix and leave other changed files + unmodified), and run the command again appending `resume:true`. + } + else + raise exception + end + end + end + # Executes the release preparation work in the repository # # - Cuts a new release branch @@ -500,10 +535,8 @@ release in progress and you're making a follow-up internal release that includes end end - unless is_ci - # Run tests (CI will run them separately) - run_tests(scheme: 'DuckDuckGo Privacy Browser') - end + # Run tests (CI will run them separately) + run_tests(scheme: 'DuckDuckGo Privacy Browser') unless is_ci # Every thing looks good: commit and push unless modified_files.empty? @@ -513,7 +546,7 @@ release in progress and you're making a follow-up internal release that includes end end - # Updates version in the config file + # Updates version and build number in respective config files # # @option [String] version Marketing version string # @option [String] build_number Build number @@ -532,6 +565,19 @@ release in progress and you're making a follow-up internal release that includes ) end + # Updates version in the config file + # + # @option [String] version Marketing version string + # + private_lane :macos_update_version_config do |options| + version = options[:version] + File.write(VERSION_CONFIG_PATH, "#{VERSION_CONFIG_DEFINITION} = #{version}\n") + git_commit( + path: VERSION_CONFIG_PATH, + message: "Set marketing version to #{version}" + ) + end + # Reads build number from the config file # # @return [String] build number read from the file, or nil in case of failure diff --git a/fastlane/README.md b/fastlane/README.md index bd417f28d9..13752cbc3f 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -47,6 +47,14 @@ Makes App Store release build and uploads it to App Store Connect Updates App Store metadata +### mac make_release_branch + +```sh +[bundle exec] fastlane mac make_release_branch +``` + +Executes the release preparation work in the repository + ### mac code_freeze ```sh @@ -71,6 +79,14 @@ Prepares new internal release on top of an existing one Executes the hotfix release preparation work in the repository +### mac update_embedded_files + +```sh +[bundle exec] fastlane mac update_embedded_files +``` + +Updates embedded files and pushes to remote. + ### mac set_version ```sh