diff --git a/App Icons/AppIconBrutalist@2x.png b/App Icons/AppIconBrutalist@2x.png new file mode 100644 index 0000000..8203440 Binary files /dev/null and b/App Icons/AppIconBrutalist@2x.png differ diff --git a/App Icons/AppIconBrutalist@3x.png b/App Icons/AppIconBrutalist@3x.png new file mode 100644 index 0000000..bc233da Binary files /dev/null and b/App Icons/AppIconBrutalist@3x.png differ diff --git a/App Icons/AppIconClassic@2x.png b/App Icons/AppIconClassic@2x.png new file mode 100644 index 0000000..6e212aa Binary files /dev/null and b/App Icons/AppIconClassic@2x.png differ diff --git a/App Icons/AppIconClassic@3x.png b/App Icons/AppIconClassic@3x.png new file mode 100644 index 0000000..7fc6369 Binary files /dev/null and b/App Icons/AppIconClassic@3x.png differ diff --git a/App Icons/AppIconLight@2x.png b/App Icons/AppIconLight@2x.png new file mode 100644 index 0000000..9800c3c Binary files /dev/null and b/App Icons/AppIconLight@2x.png differ diff --git a/App Icons/AppIconLight@3x.png b/App Icons/AppIconLight@3x.png new file mode 100644 index 0000000..6185e84 Binary files /dev/null and b/App Icons/AppIconLight@3x.png differ diff --git a/App Icons/AppIconRainbow@2x.png b/App Icons/AppIconRainbow@2x.png new file mode 100644 index 0000000..d49bb34 Binary files /dev/null and b/App Icons/AppIconRainbow@2x.png differ diff --git a/App Icons/AppIconRainbow@3x.png b/App Icons/AppIconRainbow@3x.png new file mode 100644 index 0000000..33a98a3 Binary files /dev/null and b/App Icons/AppIconRainbow@3x.png differ diff --git a/App Icons/AppIconRainbowBrutalist@2x.png b/App Icons/AppIconRainbowBrutalist@2x.png new file mode 100644 index 0000000..24def34 Binary files /dev/null and b/App Icons/AppIconRainbowBrutalist@2x.png differ diff --git a/App Icons/AppIconRainbowBrutalist@3x.png b/App Icons/AppIconRainbowBrutalist@3x.png new file mode 100644 index 0000000..6d1ca9a Binary files /dev/null and b/App Icons/AppIconRainbowBrutalist@3x.png differ diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index bfe7396..e58b451 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -60,6 +60,10 @@ "activity.open-in-default-browser" = "Open in default browser"; "add" = "Add"; "apns-default-message" = "New notification"; +"app-icon.brutalist" = "Brutalist"; +"app-icon.rainbow-brutalist" = "Rainbow Brutalist"; +"app-icon.classic" = "Classic"; +"app-icon.rainbow" = "Rainbow"; "add-identity.instance-url" = "Instance URL"; "add-identity.log-in" = "Log in"; "add-identity.browse" = "Browse"; @@ -182,6 +186,7 @@ "post" = "Post"; "preferences" = "Preferences"; "preferences.app" = "App Preferences"; +"preferences.app-icon" = "App Icon"; "preferences.blocked-domains" = "Blocked Domains"; "preferences.blocked-users" = "Blocked Users"; "preferences.media" = "Media"; diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index d5cc6fe..0653d01 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -185,6 +185,17 @@ D0D2AC5325BCD2BA003D5DF2 /* TagContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC5225BCD2BA003D5DF2 /* TagContentConfiguration.swift */; }; D0D2AC6725BD0484003D5DF2 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC6625BD0484003D5DF2 /* LineChartView.swift */; }; D0D4301425F067D600BE5504 /* BlurHash in Frameworks */ = {isa = PBXBuildFile; productRef = D0D4301325F067D600BE5504 /* BlurHash */; }; + D0D4301C25F08CFD00BE5504 /* AppIconLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4301A25F08CFD00BE5504 /* AppIconLight@2x.png */; }; + D0D4301D25F08CFD00BE5504 /* AppIconLight@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4301B25F08CFD00BE5504 /* AppIconLight@3x.png */; }; + D0D4302D25F0904D00BE5504 /* AppIconPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4302C25F0904D00BE5504 /* AppIconPreferencesView.swift */; }; + D0D4303C25F0A30600BE5504 /* AppIconClassic@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4303A25F0A30600BE5504 /* AppIconClassic@3x.png */; }; + D0D4303D25F0A30600BE5504 /* AppIconClassic@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4303B25F0A30600BE5504 /* AppIconClassic@2x.png */; }; + D0D4304825F0AEE600BE5504 /* AppIconRainbow@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4304625F0AEE600BE5504 /* AppIconRainbow@3x.png */; }; + D0D4304925F0AEE600BE5504 /* AppIconRainbow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4304725F0AEE600BE5504 /* AppIconRainbow@2x.png */; }; + D0D4306425F0B93700BE5504 /* AppIconBrutalist@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4306225F0B93700BE5504 /* AppIconBrutalist@2x.png */; }; + D0D4306525F0B93700BE5504 /* AppIconBrutalist@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4306325F0B93700BE5504 /* AppIconBrutalist@3x.png */; }; + D0D4307025F0BBA900BE5504 /* AppIconRainbowBrutalist@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4306E25F0BBA900BE5504 /* AppIconRainbowBrutalist@2x.png */; }; + D0D4307125F0BBA900BE5504 /* AppIconRainbowBrutalist@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0D4306F25F0BBA900BE5504 /* AppIconRainbowBrutalist@3x.png */; }; D0D93EBA25D9C70400C622ED /* AutocompleteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93EB925D9C70400C622ED /* AutocompleteItemView.swift */; }; D0D93EC025D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93EBF25D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift */; }; D0D93EC525D9C75E00C622ED /* AutocompleteItemContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93EBF25D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift */; }; @@ -406,6 +417,17 @@ D0D2AC4C25BCD2A9003D5DF2 /* TagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagTableViewCell.swift; sourceTree = ""; }; D0D2AC5225BCD2BA003D5DF2 /* TagContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagContentConfiguration.swift; sourceTree = ""; }; D0D2AC6625BD0484003D5DF2 /* LineChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartView.swift; sourceTree = ""; }; + D0D4301A25F08CFD00BE5504 /* AppIconLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconLight@2x.png"; sourceTree = ""; }; + D0D4301B25F08CFD00BE5504 /* AppIconLight@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconLight@3x.png"; sourceTree = ""; }; + D0D4302C25F0904D00BE5504 /* AppIconPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconPreferencesView.swift; sourceTree = ""; }; + D0D4303A25F0A30600BE5504 /* AppIconClassic@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconClassic@3x.png"; sourceTree = ""; }; + D0D4303B25F0A30600BE5504 /* AppIconClassic@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconClassic@2x.png"; sourceTree = ""; }; + D0D4304625F0AEE600BE5504 /* AppIconRainbow@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconRainbow@3x.png"; sourceTree = ""; }; + D0D4304725F0AEE600BE5504 /* AppIconRainbow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconRainbow@2x.png"; sourceTree = ""; }; + D0D4306225F0B93700BE5504 /* AppIconBrutalist@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBrutalist@2x.png"; sourceTree = ""; }; + D0D4306325F0B93700BE5504 /* AppIconBrutalist@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBrutalist@3x.png"; sourceTree = ""; }; + D0D4306E25F0BBA900BE5504 /* AppIconRainbowBrutalist@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconRainbowBrutalist@2x.png"; sourceTree = ""; }; + D0D4306F25F0BBA900BE5504 /* AppIconRainbowBrutalist@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconRainbowBrutalist@3x.png"; sourceTree = ""; }; D0D93EB925D9C70400C622ED /* AutocompleteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemView.swift; sourceTree = ""; }; D0D93EBF25D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemContentConfiguration.swift; sourceTree = ""; }; D0D93ECF25D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemCollectionViewCell.swift; sourceTree = ""; }; @@ -534,6 +556,7 @@ D021A62B25C38570008A0C0D /* AboutView.swift */, D021A63525C38ADB008A0C0D /* AcknowledgmentsView.swift */, D02D33EE25EE04CC000A35CC /* AddRemoveFromListsView.swift */, + D0D4302C25F0904D00BE5504 /* AppIconPreferencesView.swift */, D08E52602579D2E100FA2C5F /* DomainBlocksView.swift */, D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */, D0BEB20424FA1107001B0F04 /* FiltersView.swift */, @@ -643,6 +666,7 @@ D047FA7F24C3E21000AF17C5 = { isa = PBXGroup; children = ( + D0D4301925F08CE600BE5504 /* App Icons */, D0477F2A25C6EB90005C5368 /* Activities */, D0C7D45224F76169001EBDBB /* Assets.xcassets */, D0FE1C9625368A15003EF1EB /* Caches */, @@ -821,6 +845,23 @@ path = Extensions; sourceTree = ""; }; + D0D4301925F08CE600BE5504 /* App Icons */ = { + isa = PBXGroup; + children = ( + D0D4306225F0B93700BE5504 /* AppIconBrutalist@2x.png */, + D0D4306325F0B93700BE5504 /* AppIconBrutalist@3x.png */, + D0D4306E25F0BBA900BE5504 /* AppIconRainbowBrutalist@2x.png */, + D0D4306F25F0BBA900BE5504 /* AppIconRainbowBrutalist@3x.png */, + D0D4303B25F0A30600BE5504 /* AppIconClassic@2x.png */, + D0D4303A25F0A30600BE5504 /* AppIconClassic@3x.png */, + D0D4301A25F08CFD00BE5504 /* AppIconLight@2x.png */, + D0D4301B25F08CFD00BE5504 /* AppIconLight@3x.png */, + D0D4304625F0AEE600BE5504 /* AppIconRainbow@3x.png */, + D0D4304725F0AEE600BE5504 /* AppIconRainbow@2x.png */, + ); + path = "App Icons"; + sourceTree = ""; + }; D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */ = { isa = PBXGroup; children = ( @@ -991,8 +1032,18 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D0D4306425F0B93700BE5504 /* AppIconBrutalist@2x.png in Resources */, + D0D4304825F0AEE600BE5504 /* AppIconRainbow@3x.png in Resources */, D0C7D4C524F7616A001EBDBB /* Localizable.strings in Resources */, D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */, + D0D4304925F0AEE600BE5504 /* AppIconRainbow@2x.png in Resources */, + D0D4301C25F08CFD00BE5504 /* AppIconLight@2x.png in Resources */, + D0D4306525F0B93700BE5504 /* AppIconBrutalist@3x.png in Resources */, + D0D4303D25F0A30600BE5504 /* AppIconClassic@2x.png in Resources */, + D0D4301D25F08CFD00BE5504 /* AppIconLight@3x.png in Resources */, + D0D4307025F0BBA900BE5504 /* AppIconRainbowBrutalist@2x.png in Resources */, + D0D4307125F0BBA900BE5504 /* AppIconRainbowBrutalist@3x.png in Resources */, + D0D4303C25F0A30600BE5504 /* AppIconClassic@3x.png in Resources */, D0C7D4C624F7616A001EBDBB /* Localizable.stringsdict in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1110,6 +1161,7 @@ D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */, D0CEC0F725E3303200FEF5A6 /* AnimatingLayoutManager.swift in Sources */, D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */, + D0D4302D25F0904D00BE5504 /* AppIconPreferencesView.swift in Sources */, D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */, D07EC81125B232C2006DF726 /* SystemEmoji+Extensions.swift in Sources */, D035F87D25B7F61600DC75ED /* TimelinesViewController.swift in Sources */, diff --git a/Supporting Files/Info.plist b/Supporting Files/Info.plist index a480c31..433ef14 100644 --- a/Supporting Files/Info.plist +++ b/Supporting Files/Info.plist @@ -6,6 +6,90 @@ $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleAlternateIcons + + AppIconBrutalist + + CFBundleIconFiles + + AppIconBrutalist + + UIPrerenderedIcon + + + AppIconRainbow + + CFBundleIconFiles + + AppIconRainbow + + UIPrerenderedIcon + + + AppIconRainbowBrutalist + + CFBundleIconFiles + + AppIconRainbowBrutalist + + UIPrerenderedIcon + + + + CFBundlePrimaryIcon + + CFBundleIconFiles + + AppIcon + + UIPrerenderedIcon + + + + CFBundleIcons~ipad + + CFBundleAlternateIcons + + AppIconBrutalist + + CFBundleIconFiles + + AppIconBrutalist + + UIPrerenderedIcon + + + AppIconRainbow + + CFBundleIconFiles + + AppIconRainbow + + UIPrerenderedIcon + + + AppIconRainbowBrutalist + + CFBundleIconFiles + + AppIconRainbowBrutalist + + UIPrerenderedIcon + + + + CFBundlePrimaryIcon + + CFBundleIconFiles + + AppIcon + + UIPrerenderedIcon + + + CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion diff --git a/ViewModels/Sources/ViewModels/Entities/AlertItem.swift b/ViewModels/Sources/ViewModels/Entities/AlertItem.swift index 8a864b5..3c81d07 100644 --- a/ViewModels/Sources/ViewModels/Entities/AlertItem.swift +++ b/ViewModels/Sources/ViewModels/Entities/AlertItem.swift @@ -5,4 +5,8 @@ import Foundation public struct AlertItem: Identifiable { public let id = UUID() public let error: Error + + public init(error: Error) { + self.error = error + } } diff --git a/Views/SwiftUI/AppIconPreferencesView.swift b/Views/SwiftUI/AppIconPreferencesView.swift new file mode 100644 index 0000000..8ae7139 --- /dev/null +++ b/Views/SwiftUI/AppIconPreferencesView.swift @@ -0,0 +1,114 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import SwiftUI +import ViewModels + +struct AppIconPreferencesView: View { + @StateObject var viewModel: PreferencesViewModel + + @State var alertItem: AlertItem? + + var body: some View { + ScrollView { + LazyVGrid(columns: [ + GridItem(.flexible(minimum: .minimumButtonDimension)), + GridItem(.flexible(minimum: .minimumButtonDimension)), + GridItem(.flexible(minimum: .minimumButtonDimension)) + ]) { + ForEach(AppIcon.allCases) { + cell(appIcon: $0) + } + } + .padding() + } + .alertItem($alertItem) + .navigationTitle("preferences.app-icon") + } +} + +private extension AppIconPreferencesView { + @ViewBuilder func cell(appIcon: AppIcon) -> some View { + Button { + set(appIcon: appIcon) + } label: { + VStack { + if let image = appIcon.image { + image + .cornerRadius(.defaultCornerRadius) + .shadow(radius: .defaultShadowRadius) + .padding(.compactSpacing) + .background(appIcon == AppIcon.current ? Color.blue : Color.clear) + .cornerRadius(.defaultCornerRadius) + .padding(.top) + } + Text(appIcon.nameLocalizedStringKey) + .scaledToFill() + .minimumScaleFactor(0.5) + .foregroundColor(.primary) + } + } + } + + func set(appIcon: AppIcon) { + UIApplication.shared.setAlternateIconName(appIcon.alternateIconName) { error in + DispatchQueue.main.async { + if let error = error { + alertItem = AlertItem(error: error) + } else { + viewModel.objectWillChange.send() + } + } + } + } +} + +enum AppIcon: String, CaseIterable { + case classic = "AppIconClassic" + case rainbow = "AppIconRainbow" + case brutalist = "AppIconBrutalist" + case rainbowBrutalist = "AppIconRainbowBrutalist" +} + +extension AppIcon { + static var current: Self? { Self(rawValue: UIApplication.shared.alternateIconName ?? Self.classic.rawValue) } + + var nameLocalizedStringKey: LocalizedStringKey { + switch self { + case .classic: + return "app-icon.classic" + case .rainbow: + return "app-icon.rainbow" + case .brutalist: + return "app-icon.brutalist" + case .rainbowBrutalist: + return "app-icon.rainbow-brutalist" + } + } + + var alternateIconName: String? { + switch self { + case .classic: + return nil + default: + return rawValue + } + } + + var image: Image? { + guard let image = UIImage(named: rawValue) else { return nil } + + return Image(uiImage: image) + } +} + +extension AppIcon: Identifiable { + var id: Self { self } +} + +#if DEBUG +struct AppIconPreferencesView_Previews: PreviewProvider { + static var previews: some View { + AppIconPreferencesView(viewModel: .init(identityContext: .preview)) + } +} +#endif diff --git a/Views/SwiftUI/PreferencesView.swift b/Views/SwiftUI/PreferencesView.swift index aebaf6b..a667f28 100644 --- a/Views/SwiftUI/PreferencesView.swift +++ b/Views/SwiftUI/PreferencesView.swift @@ -66,29 +66,51 @@ struct PreferencesView: View { && viewModel.identityContext.identity.authenticated) } Section(header: Text("preferences.app")) { - NavigationLink("preferences.notifications", - destination: NotificationPreferencesView(viewModel: viewModel)) - Picker("preferences.status-word", - selection: $identityContext.appPreferences.statusWord) { - ForEach(AppPreferences.StatusWord.allCases) { option in - Text(option.localizedStringKey).tag(option) + Group { + if UIApplication.shared.supportsAlternateIcons { + NavigationLink(destination: AppIconPreferencesView(viewModel: viewModel)) { + HStack { + Text("preferences.app-icon") + Spacer() + if let appIcon = AppIcon.current { + if let image = appIcon.image { + image + .resizable() + .frame( + width: UIFont.preferredFont(forTextStyle: .body).lineHeight, + height: UIFont.preferredFont(forTextStyle: .body).lineHeight) + .cornerRadius(.defaultCornerRadius / 2) + } + Text(appIcon.nameLocalizedStringKey) + .foregroundColor(.secondary) + } + } + } + } + NavigationLink("preferences.notifications", + destination: NotificationPreferencesView(viewModel: viewModel)) + Picker("preferences.status-word", + selection: $identityContext.appPreferences.statusWord) { + ForEach(AppPreferences.StatusWord.allCases) { option in + Text(option.localizedStringKey).tag(option) + } + } + Toggle("preferences.show-reblog-and-favorite-counts", + isOn: $identityContext.appPreferences.showReblogAndFavoriteCounts) + Toggle("preferences.require-double-tap-to-reblog", + isOn: $identityContext.appPreferences.requireDoubleTapToReblog) + Toggle("preferences.require-double-tap-to-favorite", + isOn: $identityContext.appPreferences.requireDoubleTapToFavorite) + Toggle("preferences.links.open-in-default-browser", + isOn: $identityContext.appPreferences.openLinksInDefaultBrowser) + if !identityContext.appPreferences.openLinksInDefaultBrowser { + Toggle("preferences.links.use-universal-links", + isOn: $identityContext.appPreferences.useUniversalLinks) + } + if accessibilityReduceMotion { + Toggle("preferences.media.use-system-reduce-motion", + isOn: $identityContext.appPreferences.useSystemReduceMotionForMedia) } - } - Toggle("preferences.show-reblog-and-favorite-counts", - isOn: $identityContext.appPreferences.showReblogAndFavoriteCounts) - Toggle("preferences.require-double-tap-to-reblog", - isOn: $identityContext.appPreferences.requireDoubleTapToReblog) - Toggle("preferences.require-double-tap-to-favorite", - isOn: $identityContext.appPreferences.requireDoubleTapToFavorite) - Toggle("preferences.links.open-in-default-browser", - isOn: $identityContext.appPreferences.openLinksInDefaultBrowser) - if !identityContext.appPreferences.openLinksInDefaultBrowser { - Toggle("preferences.links.use-universal-links", - isOn: $identityContext.appPreferences.useUniversalLinks) - } - if accessibilityReduceMotion { - Toggle("preferences.media.use-system-reduce-motion", - isOn: $identityContext.appPreferences.useSystemReduceMotionForMedia) } Group { Picker("preferences.media.autoplay.gifs",