From 7d8be57b110aa14996db91b52dbe86e7bb1c9733 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Fri, 5 Apr 2024 18:56:01 +0600 Subject: [PATCH 1/6] drop Downloads storyboard --- DuckDuckGo.xcodeproj/project.pbxproj | 68 ++- .../View/BookmarkOutlineCellView.swift | 2 + .../View/AppKit/PreviewViewController.swift | 70 +++ .../Model}/DownloadListStoreMock.swift | 5 +- .../FileDownload/View/Downloads.storyboard | 483 ------------------ .../FileDownload/View/DownloadsCellView.swift | 231 ++++++++- .../FileDownload/View/DownloadsPopover.swift | 2 +- .../View/DownloadsViewController.swift | 275 ++++++---- .../View/NoDownloadsCellView.swift | 82 +++ .../View/OpenDownloadsCellView.swift | 67 +++ 10 files changed, 666 insertions(+), 619 deletions(-) create mode 100644 DuckDuckGo/Common/View/AppKit/PreviewViewController.swift rename {UnitTests/FileDownload/Helpers => DuckDuckGo/FileDownload/Model}/DownloadListStoreMock.swift (92%) delete mode 100644 DuckDuckGo/FileDownload/View/Downloads.storyboard create mode 100644 DuckDuckGo/FileDownload/View/NoDownloadsCellView.swift create mode 100644 DuckDuckGo/FileDownload/View/OpenDownloadsCellView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3269bcf0a5..a6cc55e3bd 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -706,7 +706,6 @@ 3706FCB8293F65D500E42796 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85B7184927677C2D00B4277F /* Onboarding.storyboard */; }; 3706FCB9293F65D500E42796 /* FireproofDomains.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B0511AD262CAA5A00F6079C /* FireproofDomains.storyboard */; }; 3706FCBA293F65D500E42796 /* clickToLoadConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = EA47767F272A21B700419EDA /* clickToLoadConfig.json */; }; - 3706FCBB293F65D500E42796 /* Downloads.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6B1E88126D5DAC30062C350 /* Downloads.storyboard */; }; 3706FCBC293F65D500E42796 /* dark-shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396F2754D4E900B241FA /* dark-shield.json */; }; 3706FCBD293F65D500E42796 /* dark-shield-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6EA27E880AE00036718 /* dark-shield-mouse-over.json */; }; 3706FCBE293F65D500E42796 /* autoconsent-bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055C327A1BA1D001AC618 /* autoconsent-bundle.js */; }; @@ -805,7 +804,6 @@ 3706FE10293F661700E42796 /* TestNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E02913AEDB00225DE2 /* TestNavigationDelegate.swift */; }; 3706FE11293F661700E42796 /* URLSuggestedFilenameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8553FF51257523760029327F /* URLSuggestedFilenameTests.swift */; }; 3706FE13293F661700E42796 /* ConfigurationStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B4825DAC9BD00C7D2AA /* ConfigurationStorageTests.swift */; }; - 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 */; }; 3706FE19293F661700E42796 /* DeviceAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC16A427C488C900E00A38 /* DeviceAuthenticatorTests.swift */; }; @@ -1971,7 +1969,6 @@ 4B957BF32AC7AE700062CA31 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85B7184927677C2D00B4277F /* Onboarding.storyboard */; }; 4B957BF42AC7AE700062CA31 /* FireproofDomains.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B0511AD262CAA5A00F6079C /* FireproofDomains.storyboard */; }; 4B957BF52AC7AE700062CA31 /* clickToLoadConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = EA47767F272A21B700419EDA /* clickToLoadConfig.json */; }; - 4B957BF62AC7AE700062CA31 /* Downloads.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6B1E88126D5DAC30062C350 /* Downloads.storyboard */; }; 4B957BF72AC7AE700062CA31 /* dark-shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396F2754D4E900B241FA /* dark-shield.json */; }; 4B957BF82AC7AE700062CA31 /* BookmarksBarPromptAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 859F30662A72B38500C20372 /* BookmarksBarPromptAssets.xcassets */; }; 4B957BF92AC7AE700062CA31 /* dark-shield-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6EA27E880AE00036718 /* dark-shield-mouse-over.json */; }; @@ -2926,7 +2923,6 @@ B662D3DF275616FF0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3DD275613BB0035D4D6 /* EncryptionKeyStoreMock.swift */; }; B6656E0D2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */; }; B6656E0E2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */; }; - B6656E122B29E3BE008798A1 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; B6656E5B2B2ADB1C008798A1 /* RequestFilePermissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5F5832B03580A008DB58A /* RequestFilePermissionView.swift */; }; B6676BE12AA986A700525A21 /* AddressBarTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */; }; B6676BE22AA986A700525A21 /* AddressBarTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */; }; @@ -3069,7 +3065,6 @@ B6B1E87B26D381710062C350 /* DownloadListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */; }; B6B1E87E26D5DA0E0062C350 /* DownloadsPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */; }; B6B1E88026D5DA9B0062C350 /* DownloadsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87F26D5DA9B0062C350 /* DownloadsViewController.swift */; }; - B6B1E88226D5DAC30062C350 /* Downloads.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6B1E88126D5DAC30062C350 /* Downloads.storyboard */; }; B6B1E88426D5EB570062C350 /* DownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E88326D5EB570062C350 /* DownloadsCellView.swift */; }; B6B1E88B26D774090062C350 /* LinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E88A26D774090062C350 /* LinkButton.swift */; }; B6B2400E28083B49001B8F3A /* WebViewContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B2400D28083B49001B8F3A /* WebViewContainerView.swift */; }; @@ -3174,6 +3169,18 @@ B6E1491029A5C30500AAFBE8 /* ContentBlockingTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D574B12947224C008ED1B6 /* ContentBlockingTabExtension.swift */; }; B6E1491129A5C30A00AAFBE8 /* FBProtectionTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D574B329472253008ED1B6 /* FBProtectionTabExtension.swift */; }; B6E319382953446000DD3BCF /* Assertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E319372953446000DD3BCF /* Assertions.swift */; }; + B6E3E5502BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */; }; + B6E3E5512BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */; }; + B6E3E5522BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */; }; + B6E3E5542BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */; }; + B6E3E5552BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */; }; + B6E3E5562BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */; }; + B6E3E5582BBFD51400A41922 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */; }; + B6E3E5592BBFD51400A41922 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */; }; + B6E3E55A2BBFD51400A41922 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */; }; + B6E3E55B2BC0041900A41922 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; + B6E3E55C2BC0041A00A41922 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; + B6E3E55D2BC0041C00A41922 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; B6E61EE3263AC0C8004E11AB /* FileManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E61EE2263AC0C8004E11AB /* FileManagerExtension.swift */; }; B6E6B9E32BA1F5F1008AA7E1 /* FilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */; }; B6E6B9E42BA1F5F1008AA7E1 /* FilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */; }; @@ -4640,7 +4647,6 @@ B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadListCoordinator.swift; sourceTree = ""; }; B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsPopover.swift; sourceTree = ""; }; B6B1E87F26D5DA9B0062C350 /* DownloadsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsViewController.swift; sourceTree = ""; }; - B6B1E88126D5DAC30062C350 /* Downloads.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Downloads.storyboard; sourceTree = ""; }; B6B1E88326D5EB570062C350 /* DownloadsCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsCellView.swift; sourceTree = ""; }; B6B1E88A26D774090062C350 /* LinkButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkButton.swift; sourceTree = ""; }; B6B2400D28083B49001B8F3A /* WebViewContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewContainerView.swift; sourceTree = ""; }; @@ -4704,6 +4710,9 @@ B6DB3CFA26A17CB800D459B7 /* PermissionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionModel.swift; sourceTree = ""; }; B6DE57F52B05EA9000CD54B9 /* SheetHostingWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetHostingWindow.swift; sourceTree = ""; }; B6E319372953446000DD3BCF /* Assertions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assertions.swift; sourceTree = ""; }; + B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenDownloadsCellView.swift; sourceTree = ""; }; + B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoDownloadsCellView.swift; sourceTree = ""; }; + B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = ""; }; B6E61EE2263AC0C8004E11AB /* FileManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExtension.swift; sourceTree = ""; }; B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePresenter.swift; sourceTree = ""; }; B6E6B9E82BA1FA1C008AA7E1 /* SandboxTestTool.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SandboxTestTool.xcconfig; sourceTree = ""; }; @@ -5992,8 +6001,8 @@ 4B67743D255DBEEA00025BD8 /* Database */ = { isa = PBXGroup; children = ( - 4B677440255DBEEA00025BD8 /* Database.swift */, B6085D052743905F00A9C456 /* CoreDataStore.swift */, + 4B677440255DBEEA00025BD8 /* Database.swift */, ); path = Database; sourceTree = ""; @@ -6664,6 +6673,7 @@ isa = PBXGroup; children = ( B6C0B23526E732000031CB7F /* DownloadListItem.swift */, + B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */, B6C0B23D26E8BF1F0031CB7F /* DownloadListViewModel.swift */, B6CC26672BAD959500F53F8D /* DownloadProgress.swift */, B6104E9A2BA9C173008636B2 /* DownloadResumeData.swift */, @@ -6671,8 +6681,8 @@ B6C0B23826E742610031CB7F /* FileDownloadError.swift */, 856C98DE257014BD00A22F1F /* FileDownloadManager.swift */, B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */, - B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */, B6CC266B2BAD9CD800F53F8D /* FileProgressPresenter.swift */, + B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */, ); path = Model; sourceTree = ""; @@ -6735,26 +6745,27 @@ 8585B63626D6E61500C1416F /* AppKit */ = { isa = PBXGroup; children = ( + 85774B022A71CDD000DE0561 /* BlockMenuItem.swift */, B65E6B9D26D9EC0800095F96 /* CircularProgressView.swift */, B693954626F04BEA0015B914 /* ColorView.swift */, + 4B379C2327BDE1B0008A968E /* FlatButton.swift */, B693953E26F04BE70015B914 /* FocusRingView.swift */, B693954326F04BE90015B914 /* GradientView.swift */, - B6B1E88A26D774090062C350 /* LinkButton.swift */, B6B140872ABDBCC1004F8E85 /* HoverTrackingArea.swift */, + B6B1E88A26D774090062C350 /* LinkButton.swift */, + B693954026F04BE80015B914 /* LoadingProgressView.swift */, B693954426F04BE90015B914 /* LongPressButton.swift */, - B693954926F04BEB0015B914 /* MouseOverButton.swift */, AA7EB6DE27E7C57D00036718 /* MouseOverAnimationButton.swift */, + B693954926F04BEB0015B914 /* MouseOverButton.swift */, B693953D26F04BE70015B914 /* MouseOverView.swift */, - 4B379C2327BDE1B0008A968E /* FlatButton.swift */, + 1D26EBAB2B74BECB0002A93F /* NSImageSendable.swift */, B693954726F04BEA0015B914 /* NSSavePanelExtension.swift */, B693954126F04BE80015B914 /* PaddedImageButton.swift */, - B693954026F04BE80015B914 /* LoadingProgressView.swift */, + B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */, B60C6F8C29B200AB007BFAA8 /* SavePanelAccessoryView.swift */, B693954226F04BE90015B914 /* ShadowView.swift */, - B693954526F04BEA0015B914 /* WindowDraggingView.swift */, 4BDFA4AD27BF19E500648192 /* ToggleableScrollView.swift */, - 85774B022A71CDD000DE0561 /* BlockMenuItem.swift */, - 1D26EBAB2B74BECB0002A93F /* NSImageSendable.swift */, + B693954526F04BEA0015B914 /* WindowDraggingView.swift */, ); path = AppKit; sourceTree = ""; @@ -8556,11 +8567,12 @@ B6B1E87C26D5DA020062C350 /* View */ = { isa = PBXGroup; children = ( + B6B1E88326D5EB570062C350 /* DownloadsCellView.swift */, B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */, B6B1E87F26D5DA9B0062C350 /* DownloadsViewController.swift */, - B6B1E88126D5DAC30062C350 /* Downloads.storyboard */, - B6B1E88326D5EB570062C350 /* DownloadsCellView.swift */, + B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */, B6C0B23B26E87D900031CB7F /* NSAlert+ActiveDownloadsTermination.swift */, + B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */, ); path = View; sourceTree = ""; @@ -8605,9 +8617,9 @@ B6C0B23126E71A800031CB7F /* Services */ = { isa = PBXGroup; children = ( - B6C0B23226E71BCD0031CB7F /* Downloads.xcdatamodeld */, - B6C0B22F26E61D630031CB7F /* DownloadListStore.swift */, B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */, + B6C0B22F26E61D630031CB7F /* DownloadListStore.swift */, + B6C0B23226E71BCD0031CB7F /* Downloads.xcdatamodeld */, ); path = Services; sourceTree = ""; @@ -8665,7 +8677,6 @@ B693956726F352DB0015B914 /* DownloadsWebViewMock.h */, B693956826F352DB0015B914 /* DownloadsWebViewMock.m */, B630794126731F5400DCEE41 /* WKDownloadMock.swift */, - B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */, B693956226F1C2A40015B914 /* FileDownloadManagerMock.swift */, 9F180D112B69C665000D695F /* DownloadsTabExtensionMock.swift */, ); @@ -9563,7 +9574,6 @@ 56CEE90F2B7A725C00CF10AA /* InfoPlist.xcstrings in Resources */, 3706FCB9293F65D500E42796 /* FireproofDomains.storyboard in Resources */, 3706FCBA293F65D500E42796 /* clickToLoadConfig.json in Resources */, - 3706FCBB293F65D500E42796 /* Downloads.storyboard in Resources */, 3706FCBC293F65D500E42796 /* dark-shield.json in Resources */, 854DAAAE2A72B613001E2E24 /* BookmarksBarPromptAssets.xcassets in Resources */, 3706FCBD293F65D500E42796 /* dark-shield-mouse-over.json in Resources */, @@ -9692,7 +9702,6 @@ 56CEE9102B7A72FE00CF10AA /* InfoPlist.xcstrings in Resources */, 4B957BF42AC7AE700062CA31 /* FireproofDomains.storyboard in Resources */, 4B957BF52AC7AE700062CA31 /* clickToLoadConfig.json in Resources */, - 4B957BF62AC7AE700062CA31 /* Downloads.storyboard in Resources */, 4B957BF72AC7AE700062CA31 /* dark-shield.json in Resources */, 4B957BF82AC7AE700062CA31 /* BookmarksBarPromptAssets.xcassets in Resources */, 4B957BF92AC7AE700062CA31 /* dark-shield-mouse-over.json in Resources */, @@ -9808,7 +9817,6 @@ 56CEE90E2B7A725B00CF10AA /* InfoPlist.xcstrings in Resources */, 4B0511C3262CAA5A00F6079C /* FireproofDomains.storyboard in Resources */, EA477680272A21B700419EDA /* clickToLoadConfig.json in Resources */, - B6B1E88226D5DAC30062C350 /* Downloads.storyboard in Resources */, AA3439712754D4E900B241FA /* dark-shield.json in Resources */, 859F30672A72B38500C20372 /* BookmarksBarPromptAssets.xcassets in Resources */, AA7EB6EB27E880AE00036718 /* dark-shield-mouse-over.json in Resources */, @@ -10307,6 +10315,7 @@ 3706FAAD293F65D500E42796 /* BadgeNotificationAnimationModel.swift in Sources */, 3706FAAE293F65D500E42796 /* HyperLink.swift in Sources */, 3706FAAF293F65D500E42796 /* PasteboardWriting.swift in Sources */, + B6E3E5512BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */, 3706FAB0293F65D500E42796 /* BookmarkOutlineCellView.swift in Sources */, 3706FAB1293F65D500E42796 /* UnprotectedDomains.xcdatamodeld in Sources */, 85393C872A6FF1B600F11EB3 /* BookmarksBarAppearance.swift in Sources */, @@ -10446,6 +10455,7 @@ 3706FB17293F65D500E42796 /* FirePopoverCollectionViewHeader.swift in Sources */, 85774B042A71CDD000DE0561 /* BlockMenuItem.swift in Sources */, 3706FB19293F65D500E42796 /* FireViewController.swift in Sources */, + B6E3E55C2BC0041A00A41922 /* DownloadListStoreMock.swift in Sources */, 4B4D60D42A0C84F700BCD287 /* UserText+NetworkProtection.swift in Sources */, 3707C71F294B5D2900682A9F /* WKUserContentControllerExtension.swift in Sources */, 3706FB1A293F65D500E42796 /* OutlineSeparatorViewCell.swift in Sources */, @@ -10808,6 +10818,7 @@ 3706FC13293F65D500E42796 /* FaviconView.swift in Sources */, B69A14F72B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, 3706FC14293F65D500E42796 /* OnboardingFlow.swift in Sources */, + B6E3E5592BBFD51400A41922 /* PreviewViewController.swift in Sources */, EEC8EB3E2982CA3B0065AA39 /* JSAlertViewModel.swift in Sources */, 3706FC16293F65D500E42796 /* PasswordManagementLoginModel.swift in Sources */, 3706FC17293F65D500E42796 /* TabViewModel.swift in Sources */, @@ -10887,6 +10898,7 @@ 3706FC4E293F65D500E42796 /* AtbAndVariantCleanup.swift in Sources */, 3706FC50293F65D500E42796 /* FeedbackWindow.swift in Sources */, 3706FC51293F65D500E42796 /* RecentlyVisitedView.swift in Sources */, + B6E3E5552BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */, B645D8F729FA95440024461F /* WKProcessPoolExtension.swift in Sources */, 9F514F922B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */, 3706FC52293F65D500E42796 /* MouseOverAnimationButton.swift in Sources */, @@ -11083,7 +11095,6 @@ 4BE344EF2B23786F003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */, B6F56569299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, 3706FE13293F661700E42796 /* ConfigurationStorageTests.swift in Sources */, - 3706FE14293F661700E42796 /* DownloadListStoreMock.swift in Sources */, 3706FE15293F661700E42796 /* PrivacyIconViewModelTests.swift in Sources */, B68412212B6A30680092F66A /* StringExtensionTests.swift in Sources */, 1D8C2FEE2B70F5D0005E4BBD /* MockViewSnapshotRenderer.swift in Sources */, @@ -11590,6 +11601,7 @@ 1D01A3D22B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */, 4B9579A02AC7AE700062CA31 /* InvitedToWaitlistView.swift in Sources */, 4B9579A22AC7AE700062CA31 /* SaveCredentialsViewController.swift in Sources */, + B6E3E55D2BC0041C00A41922 /* DownloadListStoreMock.swift in Sources */, 4B9579A32AC7AE700062CA31 /* PopUpButton.swift in Sources */, 4B9579A42AC7AE700062CA31 /* NetworkProtectionInviteDialog.swift in Sources */, 4B9579A52AC7AE700062CA31 /* SuggestionViewController.swift in Sources */, @@ -11622,6 +11634,7 @@ 4B9579C02AC7AE700062CA31 /* DebugUserScript.swift in Sources */, 1DC669722B6CF0D700AA0645 /* TabSnapshotStore.swift in Sources */, 4B9579C12AC7AE700062CA31 /* RecentlyClosedTab.swift in Sources */, + B6E3E55A2BBFD51400A41922 /* PreviewViewController.swift in Sources */, 4B9579C22AC7AE700062CA31 /* PDFSearchTextMenuItemHandler.swift in Sources */, 4B9579C42AC7AE700062CA31 /* HistoryMenu.swift in Sources */, 4B9579C52AC7AE700062CA31 /* ContentScopeFeatureFlagging.swift in Sources */, @@ -11822,6 +11835,7 @@ 4B957A6A2AC7AE700062CA31 /* SecureVaultLoginImporter.swift in Sources */, 4B957A6B2AC7AE700062CA31 /* WKProcessPoolExtension.swift in Sources */, 4B957A6D2AC7AE700062CA31 /* LoginItemsManager.swift in Sources */, + B6E3E5562BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */, 4B957A6E2AC7AE700062CA31 /* PixelExperiment.swift in Sources */, 4B957A6F2AC7AE700062CA31 /* DuckPlayerTabExtension.swift in Sources */, 4B957A702AC7AE700062CA31 /* RecentlyClosedCoordinator.swift in Sources */, @@ -12058,6 +12072,7 @@ 7BEC20472B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */, 4B957B392AC7AE700062CA31 /* StatisticsStore.swift in Sources */, EEC4A66B2B2C87D300F7C0AA /* VPNLocationView.swift in Sources */, + B6E3E5522BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */, 4B957B3A2AC7AE700062CA31 /* BWInstallationService.swift in Sources */, 4B957B3B2AC7AE700062CA31 /* BookmarksBarPromptPopover.swift in Sources */, 4B957B3C2AC7AE700062CA31 /* NetworkProtectionInvitePresenter.swift in Sources */, @@ -12328,6 +12343,7 @@ 0230C0A3272080090018F728 /* KeyedCodingExtension.swift in Sources */, 31AA6B972B960B870025014E /* DataBrokerProtectionLoginItemPixels.swift in Sources */, B6BF5D852946FFDA006742B1 /* PrivacyDashboardTabExtension.swift in Sources */, + B6E3E55B2BC0041900A41922 /* DownloadListStoreMock.swift in Sources */, B6C0B23026E61D630031CB7F /* DownloadListStore.swift in Sources */, 85799C1825DEBB3F0007EC87 /* Logging.swift in Sources */, AAC30A2E268F1EE300D2D9CD /* CrashReportPromptPresenter.swift in Sources */, @@ -12413,6 +12429,7 @@ AA3D531727A1EEED00074EC1 /* FeedbackViewController.swift in Sources */, AAEF6BC8276A081C0024DCF4 /* FaviconSelector.swift in Sources */, 4B2E7D6326FF9D6500D2DB17 /* PrintingUserScript.swift in Sources */, + B6E3E5542BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */, 4BBDEE9428FC14760092FAA6 /* ConnectBitwardenViewController.swift in Sources */, 1DDF076428F815AD00EDFBE3 /* BWManager.swift in Sources */, 9833912F27AAA3CE00DAF119 /* AppTrackerDataSetProvider.swift in Sources */, @@ -12899,6 +12916,7 @@ B6BBF17427475B15004F850E /* PopupBlockedPopover.swift in Sources */, 8589063A267BCD8E00D23B0D /* SaveCredentialsPopover.swift in Sources */, 987799F32999993C005D8EB6 /* LegacyBookmarksStoreMigration.swift in Sources */, + B6E3E5582BBFD51400A41922 /* PreviewViewController.swift in Sources */, 4B379C1527BD91E3008A968E /* QuartzIdleStateProvider.swift in Sources */, 37F19A6728E1B43200740DC6 /* DuckPlayerPreferences.swift in Sources */, 1D01A3D42B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */, @@ -13027,6 +13045,7 @@ 1DDD3EC02B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */, B693955026F04BEB0015B914 /* ShadowView.swift in Sources */, AA3D531D27A2F58F00074EC1 /* FeedbackSender.swift in Sources */, + B6E3E5502BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */, B6BDDA012942389000F68088 /* TabExtensions.swift in Sources */, AA7412B224D0B3AC00D22FE0 /* TabBarViewItem.swift in Sources */, 856C98D52570116900A22F1F /* NSWindow+Toast.swift in Sources */, @@ -13111,7 +13130,6 @@ 1D3B1ABF29369FC8006F4388 /* BWEncryptionTests.swift in Sources */, B6F56567299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, 1D9FDEC62B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift in Sources */, - B6656E122B29E3BE008798A1 /* DownloadListStoreMock.swift in Sources */, 37D23780287EFEE200BCE03B /* PinnedTabsManagerTests.swift in Sources */, AA0877BA26D5161D00B05660 /* WebKitVersionProviderTests.swift in Sources */, 9FA75A3E2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift in Sources */, diff --git a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift index 603849bbbf..b9c1ec9b77 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift @@ -40,6 +40,8 @@ final class BookmarkOutlineCellView: NSTableCellView { init(identifier: NSUserInterfaceItemIdentifier) { super.init(frame: .zero) + self.identifier = identifier + setupUI() } diff --git a/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift new file mode 100644 index 0000000000..e1e455e0bc --- /dev/null +++ b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift @@ -0,0 +1,70 @@ +// +// PreviewViewController.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit + +@resultBuilder +struct NSViewBuilder { + static func buildBlock(_ component: NSView) -> NSView { + return component + } +} + +#if DEBUG +/// Used to preview an NSView using Xcode #Preview macro +/// Usage: +/// ``` +/// @available(macOS 14.0, *) +/// #Preview { +/// PreviewViewController(showWindowTitle: false /*hide preview window title*/, adjustWindowFrame: true /*set the window size to the view size*/) { +/// MyNSView() +/// } +/// } +/// ``` +@available(macOS 14.0, *) +final class PreviewViewController: NSViewController { + let showWindowTitle: Bool + let adjustWindowFrame: Bool + + init(showWindowTitle: Bool = true, adjustWindowFrame: Bool = false, @NSViewBuilder builder: () -> NSView) { + self.showWindowTitle = showWindowTitle + self.adjustWindowFrame = adjustWindowFrame + super.init(nibName: nil, bundle: nil) + self.view = builder() + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + + override func viewDidAppear() { + guard let window = view.window else { return } + if !showWindowTitle { + window.titlebarAppearsTransparent = true + window.titleVisibility = .hidden + window.styleMask = [] + } + if adjustWindowFrame { + window.setFrame(NSRect(origin: .zero, size: view.bounds.size), display: true) + } + } + +} +#else +final class PreviewViewController: NSViewController {} +#endif diff --git a/UnitTests/FileDownload/Helpers/DownloadListStoreMock.swift b/DuckDuckGo/FileDownload/Model/DownloadListStoreMock.swift similarity index 92% rename from UnitTests/FileDownload/Helpers/DownloadListStoreMock.swift rename to DuckDuckGo/FileDownload/Model/DownloadListStoreMock.swift index 28001ba4e1..edae68b750 100644 --- a/UnitTests/FileDownload/Helpers/DownloadListStoreMock.swift +++ b/DuckDuckGo/FileDownload/Model/DownloadListStoreMock.swift @@ -17,12 +17,12 @@ // import Foundation -@testable import DuckDuckGo_Privacy_Browser +#if DEBUG final class DownloadListStoreMock: DownloadListStoring { var fetchBlock: ((@escaping @MainActor (Result<[DownloadListItem], Error>) -> Void) -> Void)? - func fetch(completionHandler: @escaping @MainActor (Result<[DuckDuckGo_Privacy_Browser.DownloadListItem], any Error>) -> Void) { + func fetch(completionHandler: @escaping @MainActor (Result<[DownloadListItem], any Error>) -> Void) { fetchBlock?(completionHandler) } @@ -42,3 +42,4 @@ final class DownloadListStoreMock: DownloadListStoring { } } +#endif diff --git a/DuckDuckGo/FileDownload/View/Downloads.storyboard b/DuckDuckGo/FileDownload/View/Downloads.storyboard deleted file mode 100644 index 41e5511da3..0000000000 --- a/DuckDuckGo/FileDownload/View/Downloads.storyboard +++ /dev/null @@ -1,483 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/FileDownload/View/DownloadsCellView.swift b/DuckDuckGo/FileDownload/View/DownloadsCellView.swift index ad3ed7bdd5..b6eadb2d1c 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsCellView.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsCellView.swift @@ -22,6 +22,11 @@ import UniformTypeIdentifiers final class DownloadsCellView: NSTableCellView { + fileprivate enum Constants { + static let width: CGFloat = 420 + static let height: CGFloat = 60 + } + enum DownloadError: Error { case urlNotSet case fileRemoved @@ -38,13 +43,22 @@ final class DownloadsCellView: NSTableCellView { } } - @IBOutlet var titleLabel: NSTextField! - @IBOutlet var detailLabel: NSTextField! - @IBOutlet var progressView: CircularProgressView! - @IBOutlet var cancelButton: MouseOverButton! - @IBOutlet var revealButton: MouseOverButton! - @IBOutlet var restartButton: MouseOverButton! - @IBOutlet var separator: NSBox! + private let fileIconView = NSImageView() + private let titleLabel = NSTextField() + private let detailLabel = NSTextField() + + private let progressView = CircularProgressView() + private let cancelButton = MouseOverButton(image: .cancelDownload, + target: nil, + action: #selector(DownloadsViewController.cancelDownloadAction)) + private let revealButton = MouseOverButton(image: .revealDownload, + target: nil, + action: #selector(DownloadsViewController.revealDownloadAction)) + private let restartButton = MouseOverButton(image: .restartDownload, + target: nil, + action: #selector(DownloadsViewController.restartDownloadAction)) + + private let separator = NSBox() private var buttonOverCancellables = Set() private var cancellables = Set() @@ -82,7 +96,149 @@ final class DownloadsCellView: NSTableCellView { } } - override func awakeFromNib() { + init(identifier: NSUserInterfaceItemIdentifier) { + super.init(frame: CGRect(x: 0, y: 0, width: Constants.width, height: Constants.height)) + self.identifier = identifier + + setupUI() + subscribeToMouseOverEvents() + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + + // swiftlint:disable:next function_body_length + private func setupUI() { + self.imageView = fileIconView + self.wantsLayer = true + + addSubview(fileIconView) + addSubview(titleLabel) + addSubview(detailLabel) + addSubview(cancelButton) + addSubview(revealButton) + addSubview(restartButton) + addSubview(progressView) + addSubview(separator) + + fileIconView.translatesAutoresizingMaskIntoConstraints = false + fileIconView.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + fileIconView.setContentHuggingPriority(.init(rawValue: 251), for: .vertical) + fileIconView.imageScaling = .scaleProportionallyDown + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.isSelectable = false + titleLabel.drawsBackground = false + titleLabel.font = .systemFont(ofSize: 13) + titleLabel.textColor = .controlTextColor + titleLabel.lineBreakMode = .byTruncatingMiddle + titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + titleLabel.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + + detailLabel.translatesAutoresizingMaskIntoConstraints = false + detailLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + detailLabel.isEditable = false + detailLabel.isBordered = false + detailLabel.isSelectable = false + detailLabel.drawsBackground = false + detailLabel.font = .systemFont(ofSize: 13) + detailLabel.textColor = .secondaryLabelColor + detailLabel.lineBreakMode = .byClipping + detailLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + detailLabel.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + + progressView.translatesAutoresizingMaskIntoConstraints = false + + cancelButton.translatesAutoresizingMaskIntoConstraints = false + cancelButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) + cancelButton.setContentHuggingPriority(.defaultHigh, for: .vertical) + cancelButton.bezelStyle = .shadowlessSquare + cancelButton.isBordered = false + cancelButton.imagePosition = .imageOnly + cancelButton.imageScaling = .scaleProportionallyDown + cancelButton.cornerRadius = 4 + cancelButton.backgroundInset = CGPoint(x: 2, y: 2) + cancelButton.mouseDownColor = .buttonMouseDown + cancelButton.mouseOverColor = .buttonMouseOver + + revealButton.translatesAutoresizingMaskIntoConstraints = false + revealButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) + revealButton.setContentHuggingPriority(.defaultHigh, for: .vertical) + revealButton.alignment = .center + revealButton.bezelStyle = .shadowlessSquare + cancelButton.isBordered = false + revealButton.imagePosition = .imageOnly + revealButton.imageScaling = .scaleProportionallyDown + revealButton.cornerRadius = 4 + revealButton.backgroundInset = CGPoint(x: 2, y: 2) + revealButton.mouseDownColor = .buttonMouseDown + revealButton.mouseOverColor = .buttonMouseOver + + restartButton.translatesAutoresizingMaskIntoConstraints = false + restartButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) + restartButton.setContentHuggingPriority(.defaultHigh, for: .vertical) + restartButton.alignment = .center + restartButton.bezelStyle = .shadowlessSquare + restartButton.isBordered = false + restartButton.imagePosition = .imageOnly + restartButton.imageScaling = .scaleProportionallyDown + restartButton.cornerRadius = 4 + restartButton.backgroundInset = CGPoint(x: 2, y: 2) + restartButton.mouseDownColor = .buttonMouseDown + restartButton.mouseOverColor = .buttonMouseOver + + separator.boxType = .separator + separator.translatesAutoresizingMaskIntoConstraints = false + + setupLayout() + } + + private func setupLayout() { + NSLayoutConstraint.activate([ + fileIconView.heightAnchor.constraint(equalToConstant: 32), + fileIconView.widthAnchor.constraint(equalToConstant: 32), + fileIconView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 7), + fileIconView.centerYAnchor.constraint(equalTo: centerYAnchor), + + titleLabel.heightAnchor.constraint(equalToConstant: 16), + titleLabel.leadingAnchor.constraint(equalTo: fileIconView.trailingAnchor, constant: 6), + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12), + detailLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), + + cancelButton.heightAnchor.constraint(equalToConstant: 32), + cancelButton.widthAnchor.constraint(equalToConstant: 32), + cancelButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 8), + cancelButton.leadingAnchor.constraint(equalTo: detailLabel.trailingAnchor, constant: 8), + cancelButton.centerYAnchor.constraint(equalTo: centerYAnchor), + trailingAnchor.constraint(equalTo: cancelButton.trailingAnchor, constant: 4), + + revealButton.widthAnchor.constraint(equalToConstant: 32), + revealButton.heightAnchor.constraint(equalToConstant: 32), + revealButton.centerYAnchor.constraint(equalTo: cancelButton.centerYAnchor), + revealButton.centerXAnchor.constraint(equalTo: cancelButton.centerXAnchor), + + restartButton.widthAnchor.constraint(equalToConstant: 32), + restartButton.heightAnchor.constraint(equalToConstant: 32), + restartButton.centerXAnchor.constraint(equalTo: revealButton.centerXAnchor), + restartButton.centerYAnchor.constraint(equalTo: revealButton.centerYAnchor), + + progressView.widthAnchor.constraint(equalToConstant: 27), + progressView.heightAnchor.constraint(equalToConstant: 27), + progressView.centerXAnchor.constraint(equalTo: cancelButton.centerXAnchor), + progressView.centerYAnchor.constraint(equalTo: cancelButton.centerYAnchor), + + separator.topAnchor.constraint(equalTo: detailLabel.bottomAnchor, constant: 12), + separator.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: separator.trailingAnchor), + bottomAnchor.constraint(equalTo: separator.bottomAnchor), + ]) + } + + private func subscribeToMouseOverEvents() { cancelButton.$isMouseOver.sink { [weak self] isMouseOver in self?.onButtonMouseOverChange?(isMouseOver) }.store(in: &buttonOverCancellables) @@ -163,8 +319,10 @@ final class DownloadsCellView: NSTableCellView { .store(in: &cancellables) } - private static let fileRemovedTitleAttributes: [NSAttributedString.Key: Any] = [.strikethroughStyle: 1, - .foregroundColor: NSColor.disabledControlTextColor] + private static let fileRemovedTitleAttributes: [NSAttributedString.Key: Any] = [ + .strikethroughStyle: 1, + .foregroundColor: NSColor.disabledControlTextColor + ] private func updateFilename(_ filename: String, state: DownloadViewModel.State) { // hide progress with animation on completion/failure @@ -357,3 +515,56 @@ extension DownloadsCellView.DownloadError: LocalizedError { } } + +#if DEBUG +@available(macOS 14.0, *) +#Preview { + DownloadsCellView.PreviewView() +} +@available(macOS 14.0, *) +let previewDownloadListItems = [ + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Indefinite progress download with long filename for clipping.zip", progress: Progress(totalUnitCount: -1), isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: URL(fileURLWithPath: "\(#file)"), tempFileBookmarkData: nil, error: nil), + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Active download.pdf", progress: Progress(totalUnitCount: 100, completedUnitCount: 42), isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: URL(fileURLWithPath: "\(#file)"), tempFileBookmarkData: nil, error: nil), + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Completed download.dmg", progress: nil, isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: nil, tempFileBookmarkData: nil, error: nil), + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Non-retryable download.txt", progress: nil, isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: URL(fileURLWithPath: "\(#file)"), tempFileBookmarkData: nil, error: nil), + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Retryable download.rtf", progress: nil, isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: URL(fileURLWithPath: "\(#file)"), tempFileBookmarkData: nil, error: FileDownloadError(URLError(.networkConnectionLost, userInfo: ["isRetryable": true]) as NSError)), +] +@available(macOS 14.0, *) +extension DownloadsCellView { + final class PreviewView: NSView { + + init() { + super.init(frame: .zero) + translatesAutoresizingMaskIntoConstraints = true + + let cells = [ + DownloadsCellView(identifier: .init("")), + DownloadsCellView(identifier: .init("")), + DownloadsCellView(identifier: .init("")), + DownloadsCellView(identifier: .init("")), + DownloadsCellView(identifier: .init("")), + ] + + for (idx, cell) in cells.enumerated() { + cell.widthAnchor.constraint(equalToConstant: 420).isActive = true + cell.heightAnchor.constraint(equalToConstant: 60).isActive = true + let item = previewDownloadListItems[idx] + cell.objectValue = DownloadViewModel(item: item) + } + + let stackView = NSStackView(views: cells as [NSView]) + stackView.orientation = .vertical + stackView.spacing = 1 + addAndLayout(stackView) + + widthAnchor.constraint(equalToConstant: 420).isActive = true + heightAnchor.constraint(equalToConstant: CGFloat((60 + 1) * cells.count)).isActive = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + } +} +#endif diff --git a/DuckDuckGo/FileDownload/View/DownloadsPopover.swift b/DuckDuckGo/FileDownload/View/DownloadsPopover.swift index 31bc627e2f..b41bf222c3 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsPopover.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsPopover.swift @@ -38,7 +38,7 @@ final class DownloadsPopover: NSPopover { // swiftlint:enable force_cast private func setupContentController() { - let controller = DownloadsViewController.create() + let controller = DownloadsViewController() contentViewController = controller } diff --git a/DuckDuckGo/FileDownload/View/DownloadsViewController.swift b/DuckDuckGo/FileDownload/View/DownloadsViewController.swift index fb33a0788d..ba94adc402 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsViewController.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsViewController.swift @@ -20,56 +20,158 @@ import Cocoa import Combine protocol DownloadsViewControllerDelegate: AnyObject { - func clearDownloadsActionTriggered() - } final class DownloadsViewController: NSViewController { static let preferredContentSize = CGSize(width: 420, height: 500) - static func create() -> Self { - let storyboard = NSStoryboard(name: "Downloads", bundle: nil) - // swiftlint:disable force_cast - let controller = storyboard.instantiateInitialController() as! Self - controller.loadView() - // swiftlint:enable force_cast - return controller - } - - @IBOutlet weak var openItem: NSMenuItem! - @IBOutlet weak var showInFinderItem: NSMenuItem! - @IBOutlet weak var copyDownloadLinkItem: NSMenuItem! - @IBOutlet weak var openWebsiteItem: NSMenuItem! - @IBOutlet weak var removeFromListItem: NSMenuItem! - @IBOutlet weak var stopItem: NSMenuItem! - @IBOutlet weak var restartItem: NSMenuItem! - @IBOutlet weak var clearAllItem: NSMenuItem! + private lazy var titleLabel = NSTextField(string: UserText.downloadsDialogTitle) - @IBOutlet weak var titleLabel: NSTextField! + private lazy var openDownloadsFolderButton = MouseOverButton(image: .openDownloadsFolder, target: self, action: #selector(openDownloadsFolderAction)) + private lazy var clearDownloadsButton = MouseOverButton(image: .clearDownloads, target: self, action: #selector(clearDownloadsAction)) - @IBOutlet var openDownloadsFolderButton: NSButton! - @IBOutlet var clearDownloadsButton: NSButton! - - @IBOutlet var contextMenu: NSMenu! - @IBOutlet var tableView: NSTableView! - @IBOutlet var tableViewHeightConstraint: NSLayoutConstraint? + private lazy var scrollView = NSScrollView() + private lazy var tableView = NSTableView() + private var tableViewHeightConstraint: NSLayoutConstraint! private var cellIndexToUnselect: Int? weak var delegate: DownloadsViewControllerDelegate? - var viewModel = DownloadListViewModel() - var downloadsCancellable: AnyCancellable? + private let viewModel: DownloadListViewModel + private var downloadsCancellable: AnyCancellable? - override func viewDidLoad() { - super.viewDidLoad() + init(viewModel: DownloadListViewModel? = nil) { + self.viewModel = viewModel ?? DownloadListViewModel() + super.init(nibName: nil, bundle: nil) + } - setupDragAndDrop() - setUpStrings() + required init?(coder: NSCoder) { + self.viewModel = DownloadListViewModel() + super.init(coder: coder) + } + override func loadView() { // swiftlint:disable:this function_body_length + view = NSView() + + view.addSubview(titleLabel) + view.addSubview(openDownloadsFolderButton) + view.addSubview(clearDownloadsButton) + view.addSubview(scrollView) + + titleLabel.isSelectable = false + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + titleLabel.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.drawsBackground = false + titleLabel.font = .preferredFont(forTextStyle: .title3) + titleLabel.textColor = .labelColor + + openDownloadsFolderButton.translatesAutoresizingMaskIntoConstraints = false + openDownloadsFolderButton.alignment = .center + openDownloadsFolderButton.bezelStyle = .shadowlessSquare + openDownloadsFolderButton.isBordered = false + openDownloadsFolderButton.imagePosition = .imageOnly + openDownloadsFolderButton.imageScaling = .scaleProportionallyDown openDownloadsFolderButton.toolTip = UserText.openDownloadsFolderTooltip + openDownloadsFolderButton.cornerRadius = 4 + openDownloadsFolderButton.backgroundInset = CGPoint(x: 2, y: 2) + openDownloadsFolderButton.normalTintColor = .button + openDownloadsFolderButton.mouseDownColor = .buttonMouseDown + openDownloadsFolderButton.mouseOverColor = .buttonMouseOver + + clearDownloadsButton.translatesAutoresizingMaskIntoConstraints = false + clearDownloadsButton.alignment = .center + clearDownloadsButton.bezelStyle = .shadowlessSquare + clearDownloadsButton.isBordered = false + clearDownloadsButton.imagePosition = .imageOnly + clearDownloadsButton.imageScaling = .scaleProportionallyDown clearDownloadsButton.toolTip = UserText.clearDownloadHistoryTooltip + clearDownloadsButton.cornerRadius = 4 + clearDownloadsButton.backgroundInset = CGPoint(x: 2, y: 2) + clearDownloadsButton.normalTintColor = .button + clearDownloadsButton.mouseDownColor = .buttonMouseDown + clearDownloadsButton.mouseOverColor = .buttonMouseOver + + scrollView.autohidesScrollers = true + scrollView.borderType = .noBorder + scrollView.hasHorizontalScroller = false + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.usesPredominantAxisScrolling = false + scrollView.automaticallyAdjustsContentInsets = false + + let clipView = NSClipView() + clipView.documentView = tableView + + clipView.autoresizingMask = [.width, .height] + clipView.drawsBackground = false + clipView.frame = CGRect(x: 0, y: 0, width: 420, height: 440) + + tableView.addTableColumn(NSTableColumn()) + + tableView.headerView = nil + tableView.backgroundColor = .clear + tableView.gridColor = .clear + tableView.style = .fullWidth + tableView.rowHeight = 60 + tableView.setContentHuggingPriority(.defaultHigh, for: .vertical) + tableView.allowsMultipleSelection = false + tableView.doubleAction = #selector(DownloadsViewController.doubleClickAction) + tableView.target = self + tableView.delegate = self + tableView.dataSource = self + tableView.menu = setUpContextMenu() + + tableView.registerForDraggedTypes([.fileURL]) + tableView.setDraggingSourceOperationMask(NSDragOperation.none, forLocal: true) + tableView.setDraggingSourceOperationMask(NSDragOperation.move, forLocal: false) + + scrollView.contentView = clipView + + let separator = NSBox() + separator.boxType = .separator + separator.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(separator) + + setupLayout(separator: separator) + } + + private func setupLayout(separator: NSBox) { + tableViewHeightConstraint = scrollView.heightAnchor.constraint(equalToConstant: 440) + + NSLayoutConstraint.activate([ + titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12), + titleLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 12), + + openDownloadsFolderButton.widthAnchor.constraint(equalToConstant: 32), + openDownloadsFolderButton.heightAnchor.constraint(equalToConstant: 32), + openDownloadsFolderButton.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 8), + openDownloadsFolderButton.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), + + clearDownloadsButton.widthAnchor.constraint(equalToConstant: 32), + clearDownloadsButton.heightAnchor.constraint(equalToConstant: 32), + clearDownloadsButton.leadingAnchor.constraint(equalTo: openDownloadsFolderButton.trailingAnchor), + view.trailingAnchor.constraint(equalTo: clearDownloadsButton.trailingAnchor, constant: 11), + clearDownloadsButton.centerYAnchor.constraint(equalTo: openDownloadsFolderButton.centerYAnchor), + + view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), + scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 44), + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + + separator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + separator.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -2), + separator.topAnchor.constraint(equalTo: view.topAnchor, constant: 43), + + tableViewHeightConstraint + ]) + } + + override func viewDidLoad() { + super.viewDidLoad() preferredContentSize = Self.preferredContentSize } @@ -116,16 +218,21 @@ final class DownloadsViewController: NSViewController { downloadsCancellable = nil } - private func setUpStrings() { - titleLabel.stringValue = UserText.downloadsDialogTitle - openItem.title = UserText.downloadsOpenItem - showInFinderItem.title = UserText.downloadsShowInFinderItem - copyDownloadLinkItem.title = UserText.downloadsCopyLinkItem - openWebsiteItem.title = UserText.downloadsOpenWebsiteItem - removeFromListItem.title = UserText.downloadsRemoveFromListItem - stopItem.title = UserText.downloadsStopItem - restartItem.title = UserText.downloadsRestartItem - clearAllItem.title = UserText.downloadsClearAllItem + private func setUpContextMenu() -> NSMenu { + let menu = NSMenu { + NSMenuItem(title: UserText.downloadsOpenItem, action: #selector(openDownloadAction), target: self) + NSMenuItem(title: UserText.downloadsShowInFinderItem, action: #selector(revealDownloadAction), target: self) + NSMenuItem.separator() + NSMenuItem(title: UserText.downloadsCopyLinkItem, action: #selector(copyDownloadLinkAction), target: self) + NSMenuItem(title: UserText.downloadsOpenWebsiteItem, action: #selector(openOriginatingWebsiteAction), target: self) + NSMenuItem.separator() + NSMenuItem(title: UserText.downloadsRemoveFromListItem, action: #selector(removeDownloadAction), target: self) + NSMenuItem(title: UserText.downloadsStopItem, action: #selector(cancelDownloadAction), target: self) + NSMenuItem(title: UserText.downloadsRestartItem, action: #selector(restartDownloadAction), target: self) + NSMenuItem(title: UserText.downloadsClearAllItem, action: #selector(clearDownloadsAction), target: self) + } + menu.delegate = self + return menu } private func index(for sender: Any) -> Int? { @@ -156,7 +263,7 @@ final class DownloadsViewController: NSViewController { // MARK: User Actions - @IBAction func openDownloadsFolderAction(_ sender: Any) { + @objc func openDownloadsFolderAction(_ sender: Any) { let prefs = DownloadsPreferences.shared var url: URL? var itemToSelect: URL? @@ -189,30 +296,23 @@ final class DownloadsViewController: NSViewController { NSWorkspace.shared.selectFile(itemToSelect?.path, inFileViewerRootedAtPath: url.path) } - @IBAction func clearDownloadsAction(_ sender: Any) { + @objc func clearDownloadsAction(_ sender: Any) { viewModel.cleanupInactiveDownloads() self.dismiss() delegate?.clearDownloadsActionTriggered() } - @IBAction func openDownloadedFileAction(_ sender: Any) { - guard let index = index(for: sender), - let url = viewModel.items[safe: index]?.localURL - else { return } - NSWorkspace.shared.open(url) - } - - @IBAction func cancelDownloadAction(_ sender: Any) { + @objc func cancelDownloadAction(_ sender: Any) { guard let index = index(for: sender) else { return } viewModel.cancelDownload(at: index) } - @IBAction func removeDownloadAction(_ sender: Any) { + @objc func removeDownloadAction(_ sender: Any) { guard let index = index(for: sender) else { return } viewModel.removeDownload(at: index) } - @IBAction func revealDownloadAction(_ sender: Any) { + @objc func revealDownloadAction(_ sender: Any) { guard let index = index(for: sender), let url = viewModel.items[safe: index]?.localURL else { return } @@ -220,7 +320,7 @@ final class DownloadsViewController: NSViewController { NSWorkspace.shared.activateFileViewerSelecting([url]) } - func openDownloadAction(_ sender: Any) { + @objc func openDownloadAction(_ sender: Any) { guard let index = index(for: sender), let url = viewModel.items[safe: index]?.localURL else { return } @@ -228,12 +328,12 @@ final class DownloadsViewController: NSViewController { NSWorkspace.shared.open(url) } - @IBAction func restartDownloadAction(_ sender: Any) { + @objc func restartDownloadAction(_ sender: Any) { guard let index = index(for: sender) else { return } viewModel.restartDownload(at: index) } - @IBAction func copyDownloadLinkAction(_ sender: Any) { + @objc func copyDownloadLinkAction(_ sender: Any) { guard let index = index(for: sender), let url = viewModel.items[safe: index]?.url else { return } @@ -241,7 +341,7 @@ final class DownloadsViewController: NSViewController { NSPasteboard.general.copy(url) } - @IBAction func openOriginatingWebsiteAction(_ sender: Any) { + @objc func openOriginatingWebsiteAction(_ sender: Any) { guard let index = index(for: sender), let url = viewModel.items[safe: index]?.websiteURL else { return } @@ -250,7 +350,7 @@ final class DownloadsViewController: NSViewController { WindowControllersManager.shared.show(url: url, source: .historyEntry, newTab: true) } - @IBAction func doubleClickAction(_ sender: Any) { + @objc func doubleClickAction(_ sender: Any) { if index(for: sender) != nil { openDownloadAction(sender) } else { @@ -258,12 +358,6 @@ final class DownloadsViewController: NSViewController { } } - private func setupDragAndDrop() { - tableView.registerForDraggedTypes([.fileURL]) - tableView.setDraggingSourceOperationMask(NSDragOperation.none, forLocal: true) - tableView.setDraggingSourceOperationMask(NSDragOperation.move, forLocal: false) - } - } extension DownloadsViewController: NSMenuDelegate { @@ -279,7 +373,7 @@ extension DownloadsViewController: NSMenuDelegate { for menuItem in menu.items { switch menuItem.action { - case #selector(openDownloadedFileAction(_:)), + case #selector(openDownloadAction(_:)), #selector(revealDownloadAction(_:)): if case .complete(.some(let url)) = item.state, FileManager.default.fileExists(atPath: url.path) { @@ -322,20 +416,17 @@ extension DownloadsViewController: NSTableViewDataSource, NSTableViewDelegate { } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - let identifier: NSUserInterfaceItemIdentifier if viewModel.items.isEmpty { - identifier = .noDownloadsCell + return tableView.makeView(withIdentifier: .init(NoDownloadsCellView.className()), owner: self) as? NoDownloadsCellView + ?? NoDownloadsCellView(identifier: .init(NoDownloadsCellView.className())) + } else if viewModel.items.indices.contains(row) { - identifier = .downloadCell + return tableView.makeView(withIdentifier: .init(DownloadsCellView.className()), owner: self) as? DownloadsCellView + ?? DownloadsCellView(identifier: .init(DownloadsCellView.className())) } else { - identifier = .openDownloadsCell - } - let cell = tableView.makeView(withIdentifier: identifier, owner: nil) - if identifier == .downloadCell { - cell?.menu = contextMenu + return tableView.makeView(withIdentifier: .init(OpenDownloadsCellView.className()), owner: self) as? OpenDownloadsCellView + ?? OpenDownloadsCellView(identifier: .init(OpenDownloadsCellView.className())) } - - return cell } func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { @@ -380,27 +471,15 @@ extension DownloadsViewController: NSTableViewDataSource, NSTableViewDelegate { } -private extension NSUserInterfaceItemIdentifier { - static let downloadCell = NSUserInterfaceItemIdentifier(rawValue: "cell") - static let noDownloadsCell = NSUserInterfaceItemIdentifier(rawValue: "NoDownloads") - static let openDownloadsCell = NSUserInterfaceItemIdentifier(rawValue: "OpenDownloads") -} - -final class NoDownloadViewCell: NSTableCellView { - @IBOutlet weak var openFolderButton: LinkButton! - @IBOutlet weak var titleLabel: NSTextField! +#if DEBUG +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: DownloadsViewController.preferredContentSize.width, height: DownloadsViewController.preferredContentSize.height)) { { - override func awakeFromNib() { - titleLabel.stringValue = UserText.downloadsNoRecentDownload - openFolderButton.title = UserText.downloadsOpenDownloadsFolder + let store = DownloadListStoreMock() + store.fetchBlock = { completion in + completion(.success(previewDownloadListItems)) } -} - -final class OpenDownloadViewCell: NSTableCellView { - @IBOutlet weak var openFolderButton: LinkButton! - - override func awakeFromNib() { - openFolderButton.title = UserText.downloadsOpenDownloadsFolder - } - -} + let viewModel = DownloadListViewModel(coordinator: DownloadListCoordinator(store: store)) + return DownloadsViewController(viewModel: viewModel) +}() } +#endif diff --git a/DuckDuckGo/FileDownload/View/NoDownloadsCellView.swift b/DuckDuckGo/FileDownload/View/NoDownloadsCellView.swift new file mode 100644 index 0000000000..9796154c32 --- /dev/null +++ b/DuckDuckGo/FileDownload/View/NoDownloadsCellView.swift @@ -0,0 +1,82 @@ +// +// NoDownloadsCellView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit + +final class NoDownloadsCellView: NSTableCellView { + + fileprivate enum Constants { + static let width: CGFloat = 420 + static let height: CGFloat = 60 + } + + private let titleLabel = NSTextField(string: UserText.downloadsNoRecentDownload) + private let openFolderButton = LinkButton(title: UserText.downloadsOpenDownloadsFolder, + target: nil, + action: #selector(DownloadsViewController.openDownloadsFolderAction)) + + init(identifier: NSUserInterfaceItemIdentifier) { + super.init(frame: CGRect(x: 0, y: 0, width: Constants.width, height: Constants.height)) + self.identifier = identifier + + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + + private func setupUI() { + addSubview(titleLabel) + addSubview(openFolderButton) + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.isSelectable = false + titleLabel.drawsBackground = false + titleLabel.font = .systemFont(ofSize: 13) + titleLabel.textColor = .secondaryLabelColor + titleLabel.lineBreakMode = .byTruncatingMiddle + + openFolderButton.translatesAutoresizingMaskIntoConstraints = false + openFolderButton.bezelStyle = .shadowlessSquare + openFolderButton.isBordered = false + openFolderButton.alignment = .center + openFolderButton.font = .systemFont(ofSize: 13) + openFolderButton.contentTintColor = .linkColor + + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12), + titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor), + + openFolderButton.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, + constant: 4), + openFolderButton.centerXAnchor.constraint(equalTo: centerXAnchor), + ]) + } + +} + +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: NoDownloadsCellView.Constants.width, + height: NoDownloadsCellView.Constants.height)) { + PreviewViewController(showWindowTitle: false) { + NoDownloadsCellView(identifier: .init("")) + } +} diff --git a/DuckDuckGo/FileDownload/View/OpenDownloadsCellView.swift b/DuckDuckGo/FileDownload/View/OpenDownloadsCellView.swift new file mode 100644 index 0000000000..fbfed592da --- /dev/null +++ b/DuckDuckGo/FileDownload/View/OpenDownloadsCellView.swift @@ -0,0 +1,67 @@ +// +// OpenDownloadsCellView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit + +final class OpenDownloadsCellView: NSTableCellView { + + fileprivate enum Constants { + static let width: CGFloat = 420 + static let height: CGFloat = 60 + } + + private let openFolderButton = LinkButton(title: UserText.downloadsOpenDownloadsFolder, + target: nil, + action: #selector(DownloadsViewController.openDownloadsFolderAction)) + + init(identifier: NSUserInterfaceItemIdentifier) { + super.init(frame: CGRect(x: 0, y: 0, width: Constants.width, height: Constants.height)) + self.identifier = identifier + + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + + private func setupUI() { + addSubview(openFolderButton) + + openFolderButton.translatesAutoresizingMaskIntoConstraints = false + openFolderButton.bezelStyle = .shadowlessSquare + openFolderButton.isBordered = false + openFolderButton.alignment = .center + openFolderButton.font = .systemFont(ofSize: 13) + openFolderButton.contentTintColor = .linkColor + + NSLayoutConstraint.activate([ + openFolderButton.centerYAnchor.constraint(equalTo: centerYAnchor), + openFolderButton.centerXAnchor.constraint(equalTo: centerXAnchor), + ]) + } + +} + +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: OpenDownloadsCellView.Constants.width, + height: OpenDownloadsCellView.Constants.height)) { + PreviewViewController(showWindowTitle: false) { + OpenDownloadsCellView(identifier: .init("")) + } +} From 507f6f33f3878c809ed3c4ccf9c89de520d9e0a8 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Mon, 8 Apr 2024 22:29:59 +0600 Subject: [PATCH 2/6] fix RELEASE --- DuckDuckGo/Common/View/AppKit/PreviewViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift index e1e455e0bc..72c9449ed6 100644 --- a/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift +++ b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift @@ -66,5 +66,7 @@ final class PreviewViewController: NSViewController { } #else -final class PreviewViewController: NSViewController {} +final class PreviewViewController: NSViewController { + init(showWindowTitle: Bool = true, adjustWindowFrame: Bool = false, @NSViewBuilder builder: () -> NSView) {} +} #endif From 49636f458e098ae9ab4be077232ef65af65a6c94 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 9 Apr 2024 15:27:18 +0600 Subject: [PATCH 3/6] fix RELEASE --- DuckDuckGo/Common/View/AppKit/PreviewViewController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift index 72c9449ed6..7b0058a93e 100644 --- a/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift +++ b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift @@ -68,5 +68,8 @@ final class PreviewViewController: NSViewController { #else final class PreviewViewController: NSViewController { init(showWindowTitle: Bool = true, adjustWindowFrame: Bool = false, @NSViewBuilder builder: () -> NSView) {} + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } } #endif From 6afd257fc7326cff88fed633fa1e314351467294 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 9 Apr 2024 17:00:28 +0600 Subject: [PATCH 4/6] fix RELEASE --- DuckDuckGo/Common/View/AppKit/PreviewViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift index 7b0058a93e..d62feef70b 100644 --- a/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift +++ b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift @@ -67,9 +67,11 @@ final class PreviewViewController: NSViewController { } #else final class PreviewViewController: NSViewController { - init(showWindowTitle: Bool = true, adjustWindowFrame: Bool = false, @NSViewBuilder builder: () -> NSView) {} + init(showWindowTitle: Bool = true, adjustWindowFrame: Bool = false, @NSViewBuilder builder: () -> NSView) { + fatalError("only for DEBUG") + } required init?(coder: NSCoder) { - fatalError("\(Self.self): Bad initializer") + fatalError("only for DEBUG") } } #endif From f9ecbcec49165e52f6f0189fbb2e21bf695f5e59 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 10 Apr 2024 19:46:04 +0600 Subject: [PATCH 5/6] fix Restart download menu item title --- DuckDuckGo/Common/Localizables/UserText.swift | 2 +- DuckDuckGo/Localizable.xcstrings | 60 ------------------- 2 files changed, 1 insertion(+), 61 deletions(-) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 49a5fb4d84..f656d8ffee 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1060,7 +1060,7 @@ struct UserText { static let downloadsOpenWebsiteItem = NSLocalizedString("downloads.open-website.item", value: "Open Originating Website", comment: "Contextual menu item in downloads manager to open the downloaded file originating website") static let downloadsRemoveFromListItem = NSLocalizedString("downloads.remove-from-list.item", value: "Remove from List", comment: "Contextual menu item in downloads manager to remove the given downloaded from the list of downloaded files") static let downloadsStopItem = NSLocalizedString("downloads.stop.item", value: "Stop", comment: "Contextual menu item in downloads manager to stop the download") - static let downloadsRestartItem = NSLocalizedString("downloads.restart.item", value: "Stop", comment: "Contextual menu item in downloads manager to restart the download") + static let downloadsRestartItem = restartDownloadToolTip static let downloadsClearAllItem = NSLocalizedString("downloads.clear-all.item", value: "Clear All", comment: "Contextual menu item in downloads manager to clear all downloaded items from the list") static let downloadsNoRecentDownload = NSLocalizedString("downloads.no-recent-downloads", value: "No recent downloads", comment: "Label in the downloads manager that shows that there are no recently downloaded items") static let downloadsOpenDownloadsFolder = NSLocalizedString("downloads.open-downloads-folder", value: "Open Downloads Folder", comment: "Button in the downloads manager that allows the user to open the downloads folder") diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 288c5a5bc5..82acbd5c71 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -14543,66 +14543,6 @@ } } }, - "downloads.restart.item" : { - "comment" : "Contextual menu item in downloads manager to restart the download", - "extractionState" : "extracted_with_value", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stopp" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Stop" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Detener" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Arrêter" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interrompi" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stoppen" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zatrzymaj" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Parar" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Остановить" - } - } - } - }, "downloads.show-in-finder.item" : { "comment" : "Contextual menu item in downloads manager to show the downloaded file in Finder", "extractionState" : "extracted_with_value", From c85ee64920c38ccdf9ddbbd372fbd07129f64d39 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 10 Apr 2024 20:46:56 +0600 Subject: [PATCH 6/6] target PR comments --- DuckDuckGo/FileDownload/View/DownloadsCellView.swift | 2 +- .../FileDownload/View/DownloadsViewController.swift | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/FileDownload/View/DownloadsCellView.swift b/DuckDuckGo/FileDownload/View/DownloadsCellView.swift index b6eadb2d1c..2c3f803924 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsCellView.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsCellView.swift @@ -170,7 +170,7 @@ final class DownloadsCellView: NSTableCellView { revealButton.setContentHuggingPriority(.defaultHigh, for: .vertical) revealButton.alignment = .center revealButton.bezelStyle = .shadowlessSquare - cancelButton.isBordered = false + revealButton.isBordered = false revealButton.imagePosition = .imageOnly revealButton.imageScaling = .scaleProportionallyDown revealButton.cornerRadius = 4 diff --git a/DuckDuckGo/FileDownload/View/DownloadsViewController.swift b/DuckDuckGo/FileDownload/View/DownloadsViewController.swift index ba94adc402..0db86d6e3c 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsViewController.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsViewController.swift @@ -125,10 +125,6 @@ final class DownloadsViewController: NSViewController { tableView.dataSource = self tableView.menu = setUpContextMenu() - tableView.registerForDraggedTypes([.fileURL]) - tableView.setDraggingSourceOperationMask(NSDragOperation.none, forLocal: true) - tableView.setDraggingSourceOperationMask(NSDragOperation.move, forLocal: false) - scrollView.contentView = clipView let separator = NSBox() @@ -174,6 +170,7 @@ final class DownloadsViewController: NSViewController { super.viewDidLoad() preferredContentSize = Self.preferredContentSize + setupDragAndDrop() } override func viewWillAppear() { @@ -358,6 +355,12 @@ final class DownloadsViewController: NSViewController { } } + private func setupDragAndDrop() { + tableView.registerForDraggedTypes([.fileURL]) + tableView.setDraggingSourceOperationMask(NSDragOperation.none, forLocal: true) + tableView.setDraggingSourceOperationMask(NSDragOperation.move, forLocal: false) + } + } extension DownloadsViewController: NSMenuDelegate {