diff --git a/TestFlight/WhatToTest.en-US.txt b/TestFlight/WhatToTest.en-US.txt index ace9d9ce..816e0f23 100644 --- a/TestFlight/WhatToTest.en-US.txt +++ b/TestFlight/WhatToTest.en-US.txt @@ -1,4 +1,7 @@ - Fixed crash when replying - Fixed crash when importing new themes - Fixed crash when long pressing on comment -- +- Fixed random name in exported themes +- Now exported themes have ".winston" extension (zip will continue to work though) +- Sharing zip to winston is now allowed +- ".winston" open in one tap into winston and imports it diff --git a/winston.xcodeproj/project.pbxproj b/winston.xcodeproj/project.pbxproj index 5dfc5a3d..df7a7fe7 100644 --- a/winston.xcodeproj/project.pbxproj +++ b/winston.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 659F68022A84E32300989300 /* VotesCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 659F68012A84E32300989300 /* VotesCluster.swift */; }; 659FDEA82A87798300103414 /* VoteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 659FDEA72A87798300103414 /* VoteButton.swift */; }; 65AA535D2A8BB27900275299 /* FAQPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AA535C2A8BB27900275299 /* FAQPanel.swift */; }; + EC051C472ACAF2DC00E9D9ED /* themeFileIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = EC051C462ACAF2DC00E9D9ED /* themeFileIcon.png */; }; EC082F252A65FB4300BA2727 /* MeasureOnce.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC082F242A65FB4300BA2727 /* MeasureOnce.swift */; }; EC082F272A66132600BA2727 /* CommentLinkMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC082F262A66132600BA2727 /* CommentLinkMore.swift */; }; EC082F292A66134C00BA2727 /* CommentLinkFull.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC082F282A66134C00BA2727 /* CommentLinkFull.swift */; }; @@ -195,6 +196,7 @@ EC9269F22A51A732003F33B1 /* HighlightedTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = EC9269F12A51A732003F33B1 /* HighlightedTextEditor */; }; EC9269F72A52B02E003F33B1 /* Shrinkable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9269F62A52B02E003F33B1 /* Shrinkable.swift */; }; EC92FBDE2A589BE200391D1D /* SquircleUIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC92FBDD2A589BE200391D1D /* SquircleUIKit.swift */; }; + EC97F0282ACB006A001814A6 /* importTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC97F0272ACB006A001814A6 /* importTheme.swift */; }; EC9BB1BB2A89E98D0070CD97 /* hidePost.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9BB1BA2A89E98D0070CD97 /* hidePost.swift */; }; EC9BB1BD2A8ACD300070CD97 /* AlphabetJumper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9BB1BC2A8ACD300070CD97 /* AlphabetJumper.swift */; }; EC9BB1C02A8B05AA0070CD97 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = EC9BB1BF2A8B05AA0070CD97 /* Nuke */; }; @@ -373,6 +375,8 @@ 659F68012A84E32300989300 /* VotesCluster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotesCluster.swift; sourceTree = ""; }; 659FDEA72A87798300103414 /* VoteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoteButton.swift; sourceTree = ""; }; 65AA535C2A8BB27900275299 /* FAQPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAQPanel.swift; sourceTree = ""; }; + EC051C2A2ACAEFC500E9D9ED /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; }; + EC051C462ACAF2DC00E9D9ED /* themeFileIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = themeFileIcon.png; sourceTree = ""; }; EC082F242A65FB4300BA2727 /* MeasureOnce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasureOnce.swift; sourceTree = ""; }; EC082F262A66132600BA2727 /* CommentLinkMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentLinkMore.swift; sourceTree = ""; }; EC082F282A66134C00BA2727 /* CommentLinkFull.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentLinkFull.swift; sourceTree = ""; }; @@ -543,6 +547,7 @@ EC9269E72A514AA6003F33B1 /* DownAttributedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownAttributedString.swift; sourceTree = ""; }; EC9269F62A52B02E003F33B1 /* Shrinkable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shrinkable.swift; sourceTree = ""; }; EC92FBDD2A589BE200391D1D /* SquircleUIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquircleUIKit.swift; sourceTree = ""; }; + EC97F0272ACB006A001814A6 /* importTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = importTheme.swift; sourceTree = ""; }; EC9BB1BA2A89E98D0070CD97 /* hidePost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = hidePost.swift; sourceTree = ""; }; EC9BB1BC2A8ACD300070CD97 /* AlphabetJumper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlphabetJumper.swift; sourceTree = ""; }; EC9EAEC32A4AAB470074D06B /* fromHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = fromHex.swift; sourceTree = ""; }; @@ -719,6 +724,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + EC051C292ACAEFC500E9D9ED /* Frameworks */ = { + isa = PBXGroup; + children = ( + EC051C2A2ACAEFC500E9D9ED /* QuickLookThumbnailing.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; EC08541C2A4BFEF100E88389 /* RedditAPI */ = { isa = PBXGroup; children = ( @@ -936,6 +949,7 @@ ECAA8B662A75261000D9D636 /* winston-everywhere */, EC6CBE342A7D6E760089EAA0 /* LICENSE.md */, EC6CBE322A7D6BF80089EAA0 /* README.md */, + EC051C292ACAEFC500E9D9ED /* Frameworks */, EC593AD52A4673B2002D6454 /* Products */, ); sourceTree = ""; @@ -952,6 +966,7 @@ EC593AD62A4673B2002D6454 /* winston */ = { isa = PBXGroup; children = ( + EC7078F52ACAD93800A83240 /* assets */, EC8B814C2AC96506000DC827 /* extensions */, ECC10C752A4A8746009124BE /* components */, EC2D275A2A4FBEF7006444D6 /* modifiers */, @@ -1052,6 +1067,14 @@ path = managed; sourceTree = ""; }; + EC7078F52ACAD93800A83240 /* assets */ = { + isa = PBXGroup; + children = ( + EC051C462ACAF2DC00E9D9ED /* themeFileIcon.png */, + ); + path = assets; + sourceTree = ""; + }; EC8B814C2AC96506000DC827 /* extensions */ = { isa = PBXGroup; children = ( @@ -1272,6 +1295,7 @@ ECF314E42ABC187400DFDDBA /* hashableCGSize.swift */, ECF5B3062AC83AE600355589 /* getAppIcon.swift */, EC75D7422AC96D0200594250 /* getEnabledTheme.swift */, + EC97F0272ACB006A001814A6 /* importTheme.swift */, ); path = utils; sourceTree = ""; @@ -1492,7 +1516,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1430; + LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; TargetAttributes = { EC593AD32A4673B2002D6454 = { @@ -1552,6 +1576,7 @@ EC593ADC2A4673B3002D6454 /* Assets.xcassets in Resources */, EC6CBE332A7D6BF80089EAA0 /* README.md in Resources */, ECA30A172A5FA08500BB19D1 /* WhatToTest.en-US.txt in Resources */, + EC051C472ACAF2DC00E9D9ED /* themeFileIcon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1780,6 +1805,7 @@ 650ACAFC2A87A36900A7C600 /* calcVotes.swift in Sources */, ECC2C7B42AAB4A64000E196C /* ThemeEditPanel.swift in Sources */, ECF064EB2A7F2AC50088C2C0 /* Router.swift in Sources */, + EC97F0282ACB006A001814A6 /* importTheme.swift in Sources */, ECE2859B2A5010E2009CF9DD /* DataBlock.swift in Sources */, EC75D7432AC96D0200594250 /* getEnabledTheme.swift in Sources */, ECF974432AA170D300C3BC75 /* TipJar.swift in Sources */, @@ -2033,6 +2059,7 @@ INFOPLIST_FILE = winston/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Winston; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSDocumentsFolderUsageDescription = "Winston needs access to your documents to save/import themes."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Winston needs access to your library to be able to download images."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -2072,6 +2099,7 @@ INFOPLIST_FILE = winston/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Winston; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSDocumentsFolderUsageDescription = "Winston needs access to your documents to save/import themes."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Winston needs access to your library to be able to download images."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; diff --git a/winston.xcodeproj/xcuserdata/igormarcossi.xcuserdatad/xcschemes/xcschememanagement.plist b/winston.xcodeproj/xcuserdata/igormarcossi.xcuserdatad/xcschemes/xcschememanagement.plist index b1681fba..24d0b9e2 100644 --- a/winston.xcodeproj/xcuserdata/igormarcossi.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/winston.xcodeproj/xcuserdata/igormarcossi.xcuserdatad/xcschemes/xcschememanagement.plist @@ -114,21 +114,21 @@ isShown orderHint - 11 + 9 SwiftDate (Playground) 2.xcscheme isShown orderHint - 12 + 10 SwiftDate (Playground).xcscheme isShown orderHint - 10 + 8 winston.xcscheme_^#shared#^_ diff --git a/winston/Info.plist b/winston/Info.plist index cd13063a..0cabc762 100644 --- a/winston/Info.plist +++ b/winston/Info.plist @@ -6,6 +6,35 @@ $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + winston + + CFBundleTypeIconFile + themeFileIcon + CFBundleTypeName + Winston theme file + LSHandlerRank + Owner + LSItemContentTypes + + lo.cafe.winston.theme-file + + + + CFBundleTypeName + Winston theme zip file + LSHandlerRank + Default + LSItemContentTypes + + public.zip-archive + + + CFBundleURLTypes @@ -35,5 +64,31 @@ processing remote-notification + UTExportedTypeDeclarations + + + UTTypeConformsTo + + public.zip-archive + public.data + public.content + + UTTypeDescription + Winston theme file + UTTypeIconFiles + + themeFileIcon + + UTTypeIdentifier + lo.cafe.winston.theme-file + UTTypeTagSpecification + + public.filename-extension + + winston + + + + diff --git a/winston/Tabber.swift b/winston/Tabber.swift index 126e4b06..45177263 100644 --- a/winston/Tabber.swift +++ b/winston/Tabber.swift @@ -49,6 +49,7 @@ struct Tabber: View { @State var activeTab = TabIdentifier.posts @State var credModalOpen = false + @State var importedThemeAlert = false // @State var tabBarHeight: CGFloat? @StateObject private var inboxPayload = TabPayload("inboxRouter") @@ -207,6 +208,13 @@ struct Tabber: View { } message: { Text("Something went wrong, but winston's is a fast cat, got the bug in his fangs and brought it to you. What do you wanna do?") } + .alert("Success!", isPresented: $importedThemeAlert) { + Button("Nice!", role: .cancel) { + importedThemeAlert = false + } + } message: { + Text("The theme was imported successfully. Enable it in \"Themes\" section in the Settings tab.") + } .onAppear { if showTestersCelebrationModal { showTipJarModal = false @@ -240,6 +248,15 @@ struct Tabber: View { } } .onOpenURL { url in + if url.absoluteString.hasSuffix(".winston") || url.absoluteString.hasSuffix(".zip") { + TempGlobalState.shared.globalLoader.enable("Importing...") + let result = importTheme(at: url) + TempGlobalState.shared.globalLoader.dismiss() + if result { + importedThemeAlert = true + } + return + } let parsed = parseRedditURL(url.absoluteString) withAnimation { switch parsed { diff --git a/winston/assets/themeFileIcon.png b/winston/assets/themeFileIcon.png new file mode 100644 index 00000000..e97da33d Binary files /dev/null and b/winston/assets/themeFileIcon.png differ diff --git a/winston/components/Links/PostLink/PostLinkCompact.swift b/winston/components/Links/PostLink/PostLinkCompact.swift index 0d029340..6c547064 100644 --- a/winston/components/Links/PostLink/PostLinkCompact.swift +++ b/winston/components/Links/PostLink/PostLinkCompact.swift @@ -69,7 +69,10 @@ struct PostLinkCompact: View { .zIndex(1) .frame(maxWidth: .infinity, alignment: .topLeading) - SubsNStuffLine(showSub: showSub, feedsAndSuch: feedsAndSuch, post: post, sub: sub, routerProxy: routerProxy, over18: over18) + if !showSubsAtTop { + SubsNStuffLine(showSub: showSub, feedsAndSuch: feedsAndSuch, post: post, sub: sub, routerProxy: routerProxy, over18: over18) + } + } } } diff --git a/winston/models/WinstonTheme/WinstonTheme.swift b/winston/models/WinstonTheme/WinstonTheme.swift index 0fa7cd8c..11a243f0 100644 --- a/winston/models/WinstonTheme/WinstonTheme.swift +++ b/winston/models/WinstonTheme/WinstonTheme.swift @@ -25,7 +25,7 @@ struct WinstonTheme: Codable, Identifiable, Hashable, Equatable, Defaults.Serial func duplicate() -> WinstonTheme { var copy = self copy.id = UUID().uuidString - copy.metadata.name = randomWord().capitalized + if copy.metadata.name == "Default" { copy.metadata.name = randomWord().capitalized } return copy } diff --git a/winston/utils/importTheme.swift b/winston/utils/importTheme.swift new file mode 100644 index 00000000..14aed09d --- /dev/null +++ b/winston/utils/importTheme.swift @@ -0,0 +1,51 @@ +// +// importTheme.swift +// winston +// +// Created by Igor Marcossi on 02/10/23. +// + +import Foundation +import Zip +import Defaults + +func importTheme(at rawFileURL: URL) -> Bool { + do { + let fileManager = FileManager.default + let docUrls = fileManager.urls(for: .documentDirectory, in: .userDomainMask) + guard let documentDirectory: URL = docUrls.first else { return false } + let fileURL = documentDirectory.appendingPathComponent("\(UUID().uuidString).zip") + + if fileManager.fileExists(atPath: fileURL.path()) { + try? fileManager.removeItem(at: fileURL) + } + + let gotAccess = rawFileURL.startAccessingSecurityScopedResource() + if !gotAccess { return false } + try? fileManager.copyItem(at: rawFileURL, to: fileURL) + rawFileURL.stopAccessingSecurityScopedResource() + let unzipDirectory = try Zip.quickUnzipFile(fileURL) + let themeJsonURL = unzipDirectory.appendingPathComponent("theme.json") + let themeData = try Data(contentsOf: themeJsonURL) + let theme = try JSONDecoder().decode(WinstonTheme.self, from: themeData) + + let urls = try fileManager.contentsOfDirectory(at: unzipDirectory, includingPropertiesForKeys: nil) + let destinationURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + for url in urls { + if url.lastPathComponent != "theme.json" { + let destinationFileURL = destinationURL.appendingPathComponent(url.lastPathComponent) + try? fileManager.removeItem(at: destinationFileURL) + try fileManager.moveItem(at: url, to: destinationFileURL) + } + } + + DispatchQueue.main.async { + Defaults[.themesPresets].append(theme) + } + + return true + } catch { + print("Failed to unzip file with error: \(error)") + return false + } +} diff --git a/winston/utils/redditURLParser.swift b/winston/utils/redditURLParser.swift index f387c630..8dcbe6dd 100644 --- a/winston/utils/redditURLParser.swift +++ b/winston/utils/redditURLParser.swift @@ -17,7 +17,7 @@ enum RedditURLType: Equatable, Hashable { } func parseRedditURL(_ rawUrlString: String) -> RedditURLType { - let urlString = rawUrlString.replacingOccurrences(of: "winstonapp://", with: "https://app.winston.lo.cafe/") + let urlString = rawUrlString.replacingOccurrences(of: "winstonapp://", with: "https://app.winston.lo.cafe/").replacingOccurrences(of: "https://reddit.com/", with: "https://app.winston.lo.cafe/") guard let urlComponents = URLComponents(string: urlString) else { return .other(link: urlString) } diff --git a/winston/utils/saveAndLoadImages.swift b/winston/utils/saveAndLoadImages.swift index 5369c8d6..15d0f79d 100644 --- a/winston/utils/saveAndLoadImages.swift +++ b/winston/utils/saveAndLoadImages.swift @@ -110,16 +110,23 @@ func createZip(images: [String], theme: WinstonTheme) throws -> URL { let jsonData = try JSONEncoder().encode(theme) try jsonData.write(to: jsonURL) - let zipName = "\(theme.metadata.name) Theme - Winston" + let zipName = UUID().uuidString let zipURL = documentDirectory.appendingPathComponent("\(zipName).zip") if fileManager.fileExists(atPath: zipURL.path()) { try fileManager.removeItem(at: zipURL) } + + try Zip.zipFiles(paths: imgURLs + [jsonURL], zipFilePath: zipURL, password: nil, progress: nil) + + let winstonFileName = "\(theme.metadata.name)-Theme" + let winstonFileNameURL = documentDirectory.appendingPathComponent("\(winstonFileName).winston") + + if fileManager.fileExists(atPath: winstonFileNameURL.path()) { + try fileManager.removeItem(at: winstonFileNameURL) + } - let zipFilePath = try Zip.quickZipFiles(imgURLs + [jsonURL], fileName: zipName) - // replace with your ZipArchive class and its usage - // make sure theme.json and images are added to the zip file + try fileManager.moveItem(at: zipURL, to: winstonFileNameURL) - return zipFilePath + return winstonFileNameURL } diff --git a/winston/views/Settings/views/BehaviorPanel.swift b/winston/views/Settings/views/BehaviorPanel.swift index 92fd6adb..f45e7fe1 100644 --- a/winston/views/Settings/views/BehaviorPanel.swift +++ b/winston/views/Settings/views/BehaviorPanel.swift @@ -54,9 +54,11 @@ struct BehaviorPanel: View { Section { LabeledSlider(label: "Loading limit", value: Binding(get: { CGFloat(feedPostsLoadLimit) }, set: { val in feedPostsLoadLimit = Int(val) }), range: 15...100) + .themedListRowBG(enablePadding: true) } footer: { Text("Sets how many posts to load per chunk (loads more on scroll)") } + .themedListDividers() Section { Toggle("Navigation everywhere", isOn: $enableSwipeAnywhere) diff --git a/winston/views/Settings/views/theming/ThemesPanel.swift b/winston/views/Settings/views/theming/ThemesPanel.swift index b02a0d64..16206522 100644 --- a/winston/views/Settings/views/theming/ThemesPanel.swift +++ b/winston/views/Settings/views/theming/ThemesPanel.swift @@ -43,7 +43,7 @@ struct ThemesPanel: View { do { switch res { case .success(let file): - unzipTheme(at: file[0]) + importTheme(at: file[0]) case .failure(let error): print(error.localizedDescription) } @@ -85,35 +85,7 @@ struct ThemesPanel: View { } } - func unzipTheme(at fileURL: URL) { - do { - let gotAccess = fileURL.startAccessingSecurityScopedResource() - if !gotAccess { return } - - let unzipDirectory = try Zip.quickUnzipFile(fileURL) - fileURL.stopAccessingSecurityScopedResource() - let fileManager = FileManager.default - let themeJsonURL = unzipDirectory.appendingPathComponent("theme.json") - let themeData = try Data(contentsOf: themeJsonURL) - let theme = try JSONDecoder().decode(WinstonTheme.self, from: themeData) - - let urls = try fileManager.contentsOfDirectory(at: unzipDirectory, includingPropertiesForKeys: nil) - let destinationURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! - for url in urls { - if url.lastPathComponent != "theme.json" { - let destinationFileURL = destinationURL.appendingPathComponent(url.lastPathComponent) - try? fileManager.removeItem(at: destinationFileURL) - try fileManager.moveItem(at: url, to: destinationFileURL) - } - } - - DispatchQueue.main.async { - themesPresets.append(theme) - } - } catch { - print("Failed to unzip file with error: \(error)") - } - } + } struct ThemeNavLink: View { @@ -125,6 +97,28 @@ struct ThemeNavLink: View { @State private var isMoving = false @State private var zipUrl: URL? = nil var theme: WinstonTheme + + + func zipFiles() { + var imgNames: [String] = [] + if case .img(let schemesStr) = theme.postLinks.bg { + imgNames.append(schemesStr.light) + imgNames.append(schemesStr.dark) + } + if case .img(let schemesStr) = theme.posts.bg { + imgNames.append(schemesStr.light) + imgNames.append(schemesStr.dark) + } + if case .img(let schemesStr) = theme.lists.bg { + imgNames.append(schemesStr.light) + imgNames.append(schemesStr.dark) + } + createZipFile(with: imgNames, theme: theme.duplicate()) { url in + self.zipUrl = url + self.isMoving = true + } + } + var body: some View { let isDefault = theme.id == "default" HStack(spacing: 8) { @@ -146,10 +140,10 @@ struct ThemeNavLink: View { Text(theme.metadata.name) .fontSize(16, .semibold) .fixedSize(horizontal: true, vertical: false) - Text("Created by \(theme.metadata.author)") + Text("by \(theme.metadata.author)") .fontSize(14, .medium) .opacity(0.75) - .fixedSize(horizontal: true, vertical: false) + .fixedSize(horizontal: false, vertical: true) } Spacer() @@ -167,25 +161,8 @@ struct ThemeNavLink: View { } label: { Label("Duplicate", systemImage: "plus.square.on.square") } - Button { - var imgNames: [String] = [] - if case .img(let schemesStr) = theme.postLinks.bg { - imgNames.append(schemesStr.light) - imgNames.append(schemesStr.dark) - } - if case .img(let schemesStr) = theme.posts.bg { - imgNames.append(schemesStr.light) - imgNames.append(schemesStr.dark) - } - if case .img(let schemesStr) = theme.lists.bg { - imgNames.append(schemesStr.light) - imgNames.append(schemesStr.dark) - } - createZipFile(with: imgNames, theme: theme.duplicate()) { url in - self.zipUrl = url - self.isMoving = true - } - } label: { + + Button(action: zipFiles) { Label("Export", systemImage: "doc.zipper") } }