diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index 3d62af3a..d628e4d7 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -67,7 +67,7 @@ 9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; }; 9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; }; 9FAD858E29743F7400496AB1 /* (null) in Resources */ = {isa = PBXBuildFile; }; - 9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */; platformFilters = (ios, maccatalyst, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 9FAD85982974405D00496AB1 /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD85972974405D00496AB1 /* Status */; }; 9FAD859A297440CB00496AB1 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD8599297440CB00496AB1 /* KeychainSwift */; }; 9FAD859C2974422700496AB1 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD859B2974422700496AB1 /* AppAccount */; }; @@ -90,7 +90,7 @@ 9FD542E72962D2FF0045321A /* Lists in Frameworks */ = {isa = PBXBuildFile; productRef = 9FD542E62962D2FF0045321A /* Lists */; }; 9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; }; 9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; }; - 9FF614472B2EDCE500F7B0E6 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FF614462B2EDCE500F7B0E6 /* GiphyUISDK */; }; + 9FF614472B2EDCE500F7B0E6 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 9FF614462B2EDCE500F7B0E6 /* GiphyUISDK */; }; 9FF614492B2EDCEC00F7B0E6 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FF614482B2EDCEC00F7B0E6 /* GiphyUISDK */; }; 9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; }; 9FFF677E299B7D2800FE700A /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677D299B7D2800FE700A /* Status */; }; @@ -107,7 +107,7 @@ E9DF41FC29830FEC0003AAD2 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */; }; E9DF420129830FEC0003AAD2 /* ActionRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */; }; E9DF420329830FEC0003AAD2 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = E9DF420229830FEC0003AAD2 /* Action.js */; }; - E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */; platformFilters = (ios, maccatalyst, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; FA31A9AB2A66BF7C00D5F662 /* EditTagGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA31A9AA2A66BF7C00D5F662 /* EditTagGroupView.swift */; }; /* End PBXBuildFile section */ @@ -862,11 +862,19 @@ }; 9FAD859129743F7400496AB1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; + platformFilters = ( + ios, + maccatalyst, + ); target = 9FAD858729743F7400496AB1 /* IceCubesShareExtension */; targetProxy = 9FAD859029743F7400496AB1 /* PBXContainerItemProxy */; }; E9DF420629830FEC0003AAD2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; + platformFilters = ( + ios, + maccatalyst, + ); target = E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */; targetProxy = E9DF420529830FEC0003AAD2 /* PBXContainerItemProxy */; }; @@ -920,12 +928,13 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,7"; }; name = Debug; }; @@ -954,12 +963,13 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,7"; VALIDATE_PRODUCT = YES; }; name = Release; @@ -1202,13 +1212,13 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp"; PRODUCT_NAME = "Ice Cubes"; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,7"; }; name = Debug; }; @@ -1256,13 +1266,13 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp"; PRODUCT_NAME = "Ice Cubes"; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,7"; }; name = Release; }; diff --git a/IceCubesApp/App/AppRegistry.swift b/IceCubesApp/App/AppRegistry.swift index c60ec3da..3c7f0bba 100644 --- a/IceCubesApp/App/AppRegistry.swift +++ b/IceCubesApp/App/AppRegistry.swift @@ -97,7 +97,7 @@ extension View { .preferredColorScheme(Theme.shared.selectedScheme == .dark ? .dark : .light) case .accountPushNotficationsSettings: if let subscription = PushNotificationsService.shared.subscriptions.first(where: { $0.account.token == AppAccountsManager.shared.currentAccount.oauthToken }) { - PushNotificationsView(subscription: subscription) + PushNotificationsViewWrapper(subscription: subscription) } else { EmptyView() } diff --git a/IceCubesApp/App/Report/ReportView.swift b/IceCubesApp/App/Report/ReportView.swift index 92d470b3..db7bab81 100644 --- a/IceCubesApp/App/Report/ReportView.swift +++ b/IceCubesApp/App/Report/ReportView.swift @@ -35,9 +35,11 @@ public struct ReportView: View { } .navigationTitle("report.title") .navigationBarTitleDisplayMode(.inline) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) .scrollDismissesKeyboard(.immediately) + #endif .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { diff --git a/IceCubesApp/App/SafariRouter.swift b/IceCubesApp/App/SafariRouter.swift index e487e5d2..7d41aafc 100644 --- a/IceCubesApp/App/SafariRouter.swift +++ b/IceCubesApp/App/SafariRouter.swift @@ -17,7 +17,9 @@ private struct SafariRouter: ViewModifier { @Environment(UserPreferences.self) private var preferences @Environment(RouterPath.self) private var routerPath + #if !os(visionOS) @State private var safariManager = InAppSafariManager() + #endif func body(content: Content) -> some View { content @@ -50,17 +52,24 @@ private struct SafariRouter: ViewModifier { guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else { return .systemAction } + #if os(visionOS) + return .systemAction + #else return safariManager.open(url) + #endif } } + #if !os(visionOS) .background { WindowReader { window in safariManager.windowScene = window.windowScene } } + #endif } } +#if !os(visionOS) @MainActor @Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate { var windowScene: UIWindowScene? @@ -113,6 +122,7 @@ private struct SafariRouter: ViewModifier { } } } +#endif private struct WindowReader: UIViewRepresentable { var onUpdate: (UIWindow) -> Void diff --git a/IceCubesApp/App/Tabs/Settings/AboutView.swift b/IceCubesApp/App/Tabs/Settings/AboutView.swift index 8fb17082..f1f63eca 100644 --- a/IceCubesApp/App/Tabs/Settings/AboutView.swift +++ b/IceCubesApp/App/Tabs/Settings/AboutView.swift @@ -57,7 +57,9 @@ struct AboutView: View { } footer: { Text("\(versionNumber)©2023 Thomas Ricouard") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif followAccountsSection @@ -94,14 +96,18 @@ struct AboutView: View { Text("settings.about.built-with") .textCase(nil) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .task { await fetchAccounts() } .listStyle(.insetGrouped) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif .navigationTitle(Text("settings.about.title")) .navigationBarTitleDisplayMode(.large) .environment(\.openURL, OpenURLAction { url in @@ -116,12 +122,16 @@ struct AboutView: View { AccountsListRow(viewModel: iceCubesAccount) AccountsListRow(viewModel: dimillianAccount) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } else { Section { ProgressView() } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } diff --git a/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift b/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift index bae5c102..d8e3a5b5 100644 --- a/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift +++ b/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift @@ -115,7 +115,9 @@ struct AccountSettingsView: View { cachedPostsCount = await timelineCache.cachedPostsCount(for: appAccountsManager.currentClient.id) } .navigationTitle(account.safeDisplayName) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif } } diff --git a/IceCubesApp/App/Tabs/Settings/AddAccountsView.swift b/IceCubesApp/App/Tabs/Settings/AddAccountsView.swift index f3064d6a..52ca0ada 100644 --- a/IceCubesApp/App/Tabs/Settings/AddAccountsView.swift +++ b/IceCubesApp/App/Tabs/Settings/AddAccountsView.swift @@ -53,7 +53,9 @@ struct AddAccountView: View { NavigationStack { Form { TextField("instance.url", text: $instanceName) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .keyboardType(.URL) .textContentType(.URL) .textInputAutocapitalization(.never) @@ -77,9 +79,11 @@ struct AddAccountView: View { .formStyle(.grouped) .navigationTitle("account.add.navigation-title") .navigationBarTitleDisplayMode(.inline) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) .scrollDismissesKeyboard(.immediately) + #endif .toolbar { if !appAccountsManager.availableAccounts.isEmpty { ToolbarItem(placement: .navigationBarLeading) { @@ -164,7 +168,9 @@ struct AddAccountView: View { } .buttonStyle(.borderedProminent) } + #if !os(visionOS) .listRowBackground(theme.tintColor) + #endif } private var instancesListView: some View { @@ -210,11 +216,13 @@ struct AddAccountView: View { .padding(10) } } + #if !os(visionOS) .background(theme.primaryBackgroundColor) .listRowBackground(Color.clear) .listRowInsets(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0)) .listRowSeparator(.hidden) .clipShape(RoundedRectangle(cornerRadius: 5)) + #endif .overlay { RoundedRectangle(cornerRadius: 5) .stroke(lineWidth: 1) @@ -249,7 +257,9 @@ struct AddAccountView: View { .redacted(reason: .placeholder) .allowsHitTesting(false) .shimmering() + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private func signIn() async { diff --git a/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift b/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift index fe8a5749..57caf5f9 100644 --- a/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift @@ -19,7 +19,10 @@ struct ContentSettingsView: View { Toggle(isOn: $userPreferences.suppressDupeReblogs) { Text("settings.content.hide-repeated-boosts") } - }.listRowBackground(theme.primaryBackgroundColor) + } + #if !os(visionOS) + .listRowBackground(theme.primaryBackgroundColor) + #endif Section("settings.content.media") { Toggle(isOn: $userPreferences.autoPlayVideo) { @@ -28,7 +31,10 @@ struct ContentSettingsView: View { Toggle(isOn: $userPreferences.showAltTextForMedia) { Text("settings.content.media.show.alt") } - }.listRowBackground(theme.primaryBackgroundColor) + } + #if !os(visionOS) + .listRowBackground(theme.primaryBackgroundColor) + #endif Section("settings.content.sharing") { Picker("settings.content.sharing.share-button-behavior", selection: $userPreferences.shareButtonBehavior) { @@ -38,14 +44,18 @@ struct ContentSettingsView: View { } } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif Section("settings.content.instance-settings") { Toggle(isOn: $userPreferences.useInstanceContentSettings) { Text("settings.content.use-instance-settings") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .onChange(of: userPreferences.useInstanceContentSettings) { _, newVal in if newVal { userPreferences.appAutoExpandSpoilers = userPreferences.autoExpandSpoilers @@ -76,7 +86,9 @@ struct ContentSettingsView: View { } footer: { Text("settings.content.collapse-long-posts-hint") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif Section("settings.content.posting") { Picker("settings.content.default-visibility", selection: $userPreferences.appDefaultPostVisibility) { @@ -104,11 +116,14 @@ struct ContentSettingsView: View { } .disabled(userPreferences.useInstanceContentSettings) } - + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .navigationTitle("settings.content.navigation-title") + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif } } diff --git a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift index 4250e5a1..c3e0f185 100644 --- a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift @@ -46,8 +46,10 @@ struct DisplaySettingsView: View { resetSection } .navigationTitle("settings.display.navigation-title") + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif .task(id: localValues.tintColor) { do { try await Task.sleep(for: .microseconds(500)) } catch {} theme.tintColor = localValues.tintColor @@ -121,7 +123,9 @@ struct DisplaySettingsView: View { Text("settings.display.section.theme.footer") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var fontSection: some View { @@ -173,7 +177,9 @@ struct DisplaySettingsView: View { d[.leading] } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } @ViewBuilder @@ -222,7 +228,9 @@ struct DisplaySettingsView: View { } Toggle("settings.display.show-account-popover", isOn: $userPreferences.showAccountPopover) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } @ViewBuilder @@ -232,14 +240,18 @@ struct DisplaySettingsView: View { Section("iPhone") { Toggle("settings.display.show-tab-label", isOn: $userPreferences.showiPhoneTabLabel) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } if UIDevice.current.userInterfaceIdiom == .pad { Section("iPad") { Toggle("settings.display.show-ipad-column", isOn: $userPreferences.showiPadSecondaryColumn) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } @@ -261,7 +273,9 @@ struct DisplaySettingsView: View { Text("settings.display.restore") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var themeSelectorButton: some View { diff --git a/IceCubesApp/App/Tabs/Settings/HapticSettingsView.swift b/IceCubesApp/App/Tabs/Settings/HapticSettingsView.swift index 91b371eb..13a826f5 100644 --- a/IceCubesApp/App/Tabs/Settings/HapticSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/HapticSettingsView.swift @@ -17,10 +17,14 @@ struct HapticSettingsView: View { Toggle("settings.haptic.tab-selection", isOn: $userPreferences.hapticTabSelectionEnabled) Toggle("settings.haptic.buttons", isOn: $userPreferences.hapticButtonPressEnabled) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .navigationTitle("settings.haptic.navigation-title") + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif } } diff --git a/IceCubesApp/App/Tabs/Settings/IconSelectorView.swift b/IceCubesApp/App/Tabs/Settings/IconSelectorView.swift index 757ca64c..c3804dec 100644 --- a/IceCubesApp/App/Tabs/Settings/IconSelectorView.swift +++ b/IceCubesApp/App/Tabs/Settings/IconSelectorView.swift @@ -85,7 +85,9 @@ struct IconSelectorView: View { .padding(6) .navigationTitle("settings.app.icon.navigation-title") } + #if !os(visionOS) .background(theme.primaryBackgroundColor) + #endif } private func makeIconGridView(icons: [Icon]) -> some View { @@ -116,6 +118,7 @@ struct IconSelectorView: View { } } } + .buttonStyle(.plain) } } } diff --git a/IceCubesApp/App/Tabs/Settings/InstanceInfoView.swift b/IceCubesApp/App/Tabs/Settings/InstanceInfoView.swift index 68f5f698..1118ae3f 100644 --- a/IceCubesApp/App/Tabs/Settings/InstanceInfoView.swift +++ b/IceCubesApp/App/Tabs/Settings/InstanceInfoView.swift @@ -13,8 +13,10 @@ struct InstanceInfoView: View { InstanceInfoSection(instance: instance) } .navigationTitle("instance.info.navigation-title") + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif } } @@ -35,7 +37,9 @@ public struct InstanceInfoSection: View { LabeledContent("instance.info.posts", value: format(instance.stats.statusCount)) LabeledContent("instance.info.domains", value: format(instance.stats.domainCount)) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif if let rules = instance.rules { Section("instance.info.section.rules") { @@ -43,7 +47,9 @@ public struct InstanceInfoSection: View { Text(rule.text.trimmingCharacters(in: .whitespacesAndNewlines)) } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } diff --git a/IceCubesApp/App/Tabs/Settings/PushNotificationsView.swift b/IceCubesApp/App/Tabs/Settings/PushNotificationsView.swift index 78b27aec..3fea4dd2 100644 --- a/IceCubesApp/App/Tabs/Settings/PushNotificationsView.swift +++ b/IceCubesApp/App/Tabs/Settings/PushNotificationsView.swift @@ -7,6 +7,27 @@ import NukeUI import SwiftUI import UserNotifications +struct PushNotificationsViewWrapper: View { + @Environment(\.dismiss) private var dismiss + + public let subscription: PushNotificationSubscriptionSettings + + var body: some View { + NavigationStack { + PushNotificationsView(subscription: subscription) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button { + dismiss() + } label: { + Image(systemName: "xmark.circle") + } + } + } + } + } +} + @MainActor struct PushNotificationsView: View { @Environment(Theme.self) private var theme @@ -33,7 +54,9 @@ struct PushNotificationsView: View { } footer: { Text("settings.push.main-toggle.description") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif if subscription.isEnabled { Section { @@ -86,7 +109,9 @@ struct PushNotificationsView: View { Label("settings.push.new-posts", systemImage: "bubble.right") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } Section { @@ -101,11 +126,15 @@ struct PushNotificationsView: View { } footer: { Text("settings.push.duplicate.footer") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .navigationTitle("settings.push.navigation-title") + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif .task { await subscription.fetchSubscription() } diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index 91938e61..2cdab426 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -45,7 +45,9 @@ struct SettingsTabs: View { cacheSection } .scrollContentBackground(.hidden) + #if !os(visionOS) .background(theme.secondaryBackgroundColor) + #endif .navigationTitle(Text("settings.title")) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar) @@ -114,7 +116,9 @@ struct SettingsTabs: View { } addAccountButton } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private func logoutAccount(account: AppAccount) async { @@ -166,7 +170,9 @@ struct SettingsTabs: View { .tint(theme.labelColor) #endif } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } @ViewBuilder @@ -204,12 +210,14 @@ struct SettingsTabs: View { Label("settings.other.fast-refresh", systemImage: "arrow.clockwise") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var appSection: some View { Section { - #if !targetEnvironment(macCatalyst) + #if !targetEnvironment(macCatalyst) && !os(visionOS) NavigationLink(destination: IconSelectorView()) { Label { Text("settings.app.icon") @@ -252,7 +260,9 @@ struct SettingsTabs: View { Text("settings.section.app.footer \(appVersion)").frame(maxWidth: .infinity, alignment: .center) } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var addAccountButton: some View { @@ -293,18 +303,24 @@ struct SettingsTabs: View { context.delete(tagGroups[index]) } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif Button { routerPath.presentedSheet = .addTagGroup } label: { Label("timeline.filter.add-tag-groups", systemImage: "plus") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .navigationTitle("timeline.filter.tag-groups") .scrollContentBackground(.hidden) + #if !os(visionOS) .background(theme.secondaryBackgroundColor) + #endif .toolbar { EditButton() } @@ -319,17 +335,23 @@ struct SettingsTabs: View { context.delete(localTimelines[index]) } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif Button { routerPath.presentedSheet = .addRemoteLocalTimeline } label: { Label("settings.timeline.add", systemImage: "badge.plus.radiowaves.right") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .navigationTitle("settings.general.remote-timelines") .scrollContentBackground(.hidden) + #if !os(visionOS) .background(theme.secondaryBackgroundColor) + #endif .toolbar { EditButton() } @@ -349,6 +371,8 @@ struct SettingsTabs: View { } } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } diff --git a/IceCubesApp/App/Tabs/Settings/SupportAppView.swift b/IceCubesApp/App/Tabs/Settings/SupportAppView.swift index 6b42c46c..6114ef18 100644 --- a/IceCubesApp/App/Tabs/Settings/SupportAppView.swift +++ b/IceCubesApp/App/Tabs/Settings/SupportAppView.swift @@ -69,8 +69,10 @@ struct SupportAppView: View { linksSection } .navigationTitle("settings.support.navigation-title") + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif .alert("settings.support.alert.title", isPresented: $purchaseSuccessDisplayed, actions: { Button { purchaseSuccessDisplayed = false } label: { Text("alert.button.ok") } }, message: { @@ -151,7 +153,9 @@ struct SupportAppView: View { Text("settings.support.message-from-dev") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var subscriptionSection: some View { @@ -188,7 +192,9 @@ struct SupportAppView: View { Text("settings.support.supporter.subscription-info") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var tipsSection: some View { @@ -213,7 +219,9 @@ struct SupportAppView: View { } } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var restorePurchase: some View { @@ -232,7 +240,9 @@ struct SupportAppView: View { } footer: { Text("settings.support.restore-purchase.explanation") } + #if !os(visionOS) .listRowBackground(theme.secondaryBackgroundColor) + #endif } private var linksSection: some View { @@ -252,7 +262,9 @@ struct SupportAppView: View { .buttonStyle(.borderless) } } + #if !os(visionOS) .listRowBackground(theme.secondaryBackgroundColor) + #endif } private var loadingPlaceholder: some View { diff --git a/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift b/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift index 5ef7ec6b..ef54893f 100644 --- a/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift @@ -46,8 +46,10 @@ struct SwipeActionsSettingsView: View { } footer: { Text("settings.swipeactions.status.explanation") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) - + #endif + Section { Picker(selection: $userPreferences.swipeActionsIconStyle, label: Text("settings.swipeactions.icon-style")) { ForEach(UserPreferences.SwipeActionsIconStyle.allCases, id: \.rawValue) { style in @@ -62,11 +64,15 @@ struct SwipeActionsSettingsView: View { } footer: { Text("settings.swipeactions.use-theme-colors-explanation") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .navigationTitle("settings.swipeactions.navigation-title") + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif } private func createStatusActionPicker(selection: Binding, label: LocalizedStringKey) -> some View { diff --git a/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift b/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift index befaf40e..838fa558 100644 --- a/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift @@ -21,8 +21,10 @@ struct TranslationSettingsView: View { .onAppear { readValue() } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) - + #endif + if apiKey.isEmpty { Section { Link(destination: URL(string: "https://www.deepl.com/pro-api")!) { @@ -30,14 +32,18 @@ struct TranslationSettingsView: View { .foregroundColor(.red) } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } autoDetectSection } .navigationTitle("settings.translation.navigation-title") + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif .onChange(of: apiKey) { writeNewValue() } @@ -50,7 +56,9 @@ struct TranslationSettingsView: View { Toggle(isOn: $preferences.alwaysUseDeepl) { Text("settings.translation.always-deepl") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } @ViewBuilder diff --git a/IceCubesApp/App/Tabs/Tabs.swift b/IceCubesApp/App/Tabs/Tabs.swift index 4a9022b8..c1f4b9b2 100644 --- a/IceCubesApp/App/Tabs/Tabs.swift +++ b/IceCubesApp/App/Tabs/Tabs.swift @@ -20,8 +20,11 @@ enum Tab: Int, Identifiable, Hashable { } static func loggedInTabs() -> [Tab] { - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + if UIDevice.current.userInterfaceIdiom == .pad || + UIDevice.current.userInterfaceIdiom == .mac { [.timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings] + } else if UIDevice.current.userInterfaceIdiom == .vision { + [.profile, .timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings] } else { [.timeline, .notifications, .explore, .messages, .profile] } diff --git a/IceCubesApp/App/Tabs/TagGroup/EditTagGroupView.swift b/IceCubesApp/App/Tabs/TagGroup/EditTagGroupView.swift index 2b8edb40..030bdbd1 100644 --- a/IceCubesApp/App/Tabs/TagGroup/EditTagGroupView.swift +++ b/IceCubesApp/App/Tabs/TagGroup/EditTagGroupView.swift @@ -57,9 +57,11 @@ struct EditTagGroupView: View { : "timeline.filter.edit-tag-groups" ) .navigationBarTitleDisplayMode(.inline) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) .scrollDismissesKeyboard(.immediately) + #endif .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("action.cancel", action: { dismiss() }) diff --git a/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineView.swift b/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineView.swift index 9f640516..bc4bc23a 100644 --- a/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineView.swift +++ b/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineView.swift @@ -52,9 +52,11 @@ struct AddRemoteTimelineView: View { .formStyle(.grouped) .navigationTitle("timeline.add-remote.title") .navigationBarTitleDisplayMode(.inline) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) .scrollDismissesKeyboard(.immediately) + #endif .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("action.cancel", action: { dismiss() }) diff --git a/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/1024.png b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/1024.png new file mode 100644 index 00000000..0a870c8b Binary files /dev/null and b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/1024.png differ diff --git a/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json new file mode 100644 index 00000000..9d5e8793 --- /dev/null +++ b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "1024.png", + "idiom" : "vision", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Contents.json b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Contents.json new file mode 100644 index 00000000..bb816dab --- /dev/null +++ b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Contents.json @@ -0,0 +1,14 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.solidimagestacklayer" + }, + { + "filename" : "Back.solidimagestacklayer" + } + ] +} diff --git a/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/1024.png b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/1024.png new file mode 100644 index 00000000..0a870c8b Binary files /dev/null and b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/1024.png differ diff --git a/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json new file mode 100644 index 00000000..9d5e8793 --- /dev/null +++ b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "1024.png", + "idiom" : "vision", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/IceCubesApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Packages/Account/Package.swift b/Packages/Account/Package.swift index 0df43eb6..fe1b989b 100644 --- a/Packages/Account/Package.swift +++ b/Packages/Account/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Account/Sources/Account/AccountDetailHeaderView.swift b/Packages/Account/Sources/Account/AccountDetailHeaderView.swift index 0240d107..dc8ef312 100644 --- a/Packages/Account/Sources/Account/AccountDetailHeaderView.swift +++ b/Packages/Account/Sources/Account/AccountDetailHeaderView.swift @@ -74,7 +74,9 @@ struct AccountDetailHeaderView: View { .frame(height: Constants.headerHeight) } } + #if !os(visionOS) .background(theme.secondaryBackgroundColor) + #endif .frame(height: Constants.headerHeight) .onTapGesture { guard account.haveHeader else { @@ -320,7 +322,9 @@ struct AccountDetailHeaderView: View { Text(note) .frame(maxWidth: .infinity, alignment: .leading) .padding(8) + #if !os(visionOS) .background(theme.secondaryBackgroundColor) + #endif .cornerRadius(4) .overlay( RoundedRectangle(cornerRadius: 4) @@ -370,7 +374,11 @@ struct AccountDetailHeaderView: View { .padding(8) .accessibilityElement(children: .contain) .accessibilityLabel("accessibility.tabs.profile.fields.container.label") + #if os(visionOS) + .background(Material.thick) + #else .background(theme.secondaryBackgroundColor) + #endif .cornerRadius(4) .overlay( RoundedRectangle(cornerRadius: 4) diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift index 9aea29ca..96685bbe 100644 --- a/Packages/Account/Sources/Account/AccountDetailView.swift +++ b/Packages/Account/Sources/Account/AccountDetailView.swift @@ -84,8 +84,10 @@ public struct AccountDetailView: View { } .environment(\.defaultMinListRowHeight, 1) .listStyle(.plain) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) + #endif .onChange(of: scrollToTopSignal) { withAnimation { proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top) @@ -216,7 +218,9 @@ public struct AccountDetailView: View { .padding(.leading, -4) .accessibilityLabel(account.safeDisplayName) - }.accessibilityAddTraits(.isImage) + } + .accessibilityAddTraits(.isImage) + .buttonStyle(.plain) } } .padding(.leading, .layoutPadding + 4) @@ -235,7 +239,9 @@ public struct AccountDetailView: View { Spacer() Image(systemName: "chevron.right") } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } }.task { await currentAccount.fetchFollowedTags() @@ -250,7 +256,9 @@ public struct AccountDetailView: View { .font(.scaledHeadline) .foregroundColor(theme.labelColor) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .contextMenu { Button("account.list.delete", role: .destructive) { Task { @@ -264,7 +272,9 @@ public struct AccountDetailView: View { } .tint(theme.tintColor) .buttonStyle(.borderless) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .task { await currentAccount.fetchLists() @@ -284,7 +294,9 @@ public struct AccountDetailView: View { bottom: 0, trailing: .layoutPadding)) .listRowSeparator(.hidden) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif ForEach(viewModel.pinned) { status in StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath)) } @@ -403,7 +415,9 @@ extension View { func applyAccountDetailsRowStyle(theme: Theme) -> some View { listRowInsets(.init()) .listRowSeparator(.hidden) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } diff --git a/Packages/Account/Sources/Account/AccountsList/AccountsListView.swift b/Packages/Account/Sources/Account/AccountsList/AccountsListView.swift index 098a3a64..09b98dc5 100644 --- a/Packages/Account/Sources/Account/AccountsList/AccountsListView.swift +++ b/Packages/Account/Sources/Account/AccountsList/AccountsListView.swift @@ -26,7 +26,9 @@ public struct AccountsListView: View { .redacted(reason: .placeholder) .allowsHitTesting(false) .shimmering() + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } case let .display(accounts, relationships, nextPageState): if case .followers = viewModel.mode, @@ -49,7 +51,9 @@ public struct AccountsListView: View { } } ) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } } @@ -58,7 +62,9 @@ public struct AccountsListView: View { if let relationship = relationships.first(where: { $0.id == account.id }) { AccountsListRow(viewModel: .init(account: account, relationShip: relationship)) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } } @@ -66,7 +72,9 @@ public struct AccountsListView: View { switch nextPageState { case .hasNextPage: loadingRow + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .onAppear { Task { await viewModel.fetchNextPage() @@ -75,18 +83,24 @@ public struct AccountsListView: View { case .loadingNextPage: loadingRow + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif case .none: EmptyView() } case let .error(error): Text(error.localizedDescription) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) + #endif .listStyle(.plain) .navigationTitle(viewModel.mode.title) .navigationBarTitleDisplayMode(.inline) diff --git a/Packages/Account/Sources/Account/Edit/EditAccountView.swift b/Packages/Account/Sources/Account/Edit/EditAccountView.swift index 1f0c48c5..49161ac6 100644 --- a/Packages/Account/Sources/Account/Edit/EditAccountView.swift +++ b/Packages/Account/Sources/Account/Edit/EditAccountView.swift @@ -28,9 +28,11 @@ public struct EditAccountView: View { } } .environment(\.editMode, .constant(.active)) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) .scrollDismissesKeyboard(.immediately) + #endif .navigationTitle("account.edit.navigation-title") .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -56,7 +58,9 @@ public struct EditAccountView: View { Spacer() } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } @ViewBuilder @@ -69,7 +73,9 @@ public struct EditAccountView: View { TextField("account.edit.about", text: $viewModel.note, axis: .vertical) .frame(maxHeight: 150) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var postSettingsSection: some View { @@ -89,7 +95,9 @@ public struct EditAccountView: View { Label("account.edit.post-settings.sensitive", systemImage: "eye") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var accountSection: some View { @@ -104,7 +112,9 @@ public struct EditAccountView: View { Label("account.edit.account-settings.discoverable", systemImage: "magnifyingglass") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var fieldsSection: some View { @@ -138,7 +148,9 @@ public struct EditAccountView: View { } } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } @ToolbarContentBuilder diff --git a/Packages/Account/Sources/Account/Edit/EditRelationshipNoteView.swift b/Packages/Account/Sources/Account/Edit/EditRelationshipNoteView.swift index 629d9fd6..9c42e258 100644 --- a/Packages/Account/Sources/Account/Edit/EditRelationshipNoteView.swift +++ b/Packages/Account/Sources/Account/Edit/EditRelationshipNoteView.swift @@ -18,10 +18,14 @@ public struct EditRelationshipNoteView: View { TextField("account.relation.note.edit.placeholder", text: $viewModel.note, axis: .vertical) .frame(minHeight: 150, maxHeight: 150, alignment: .top) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif .navigationTitle("account.relation.note.edit") .navigationBarTitleDisplayMode(.inline) .toolbar { diff --git a/Packages/Account/Sources/Account/Filters/EditFilterView.swift b/Packages/Account/Sources/Account/Filters/EditFilterView.swift index 889b3805..ad56ae9b 100644 --- a/Packages/Account/Sources/Account/Filters/EditFilterView.swift +++ b/Packages/Account/Sources/Account/Filters/EditFilterView.swift @@ -70,9 +70,11 @@ struct EditFilterView: View { } .navigationTitle(filter?.title ?? NSLocalizedString("filter.new", comment: "")) .navigationBarTitleDisplayMode(.inline) + #if !os(visionOS) .scrollContentBackground(.hidden) .scrollDismissesKeyboard(.interactively) .background(theme.secondaryBackgroundColor) + #endif .onAppear { if filter == nil { focusedField = .title @@ -104,7 +106,9 @@ struct EditFilterView: View { .disabled(expirySelection != .custom) } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } @ViewBuilder @@ -118,7 +122,9 @@ struct EditFilterView: View { } } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif if filter == nil, !title.isEmpty { Section { @@ -138,7 +144,9 @@ struct EditFilterView: View { .buttonStyle(.borderedProminent) .transition(.opacity) } + #if !os(visionOS) .listRowBackground(theme.secondaryBackgroundColor) + #endif } } @@ -192,7 +200,9 @@ struct EditFilterView: View { } } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var contextsSection: some View { @@ -214,7 +224,9 @@ struct EditFilterView: View { } .disabled(isSavingFilter) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } @@ -235,7 +247,9 @@ struct EditFilterView: View { } .pickerStyle(.inline) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var saveButton: some View { diff --git a/Packages/Account/Sources/Account/Filters/FiltersListView.swift b/Packages/Account/Sources/Account/Filters/FiltersListView.swift index 5d7dd1a0..6f10bb03 100644 --- a/Packages/Account/Sources/Account/Filters/FiltersListView.swift +++ b/Packages/Account/Sources/Account/Filters/FiltersListView.swift @@ -54,7 +54,9 @@ public struct FiltersListView: View { } } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } Section { @@ -62,15 +64,19 @@ public struct FiltersListView: View { Label("filter.new", systemImage: "plus") } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } .toolbar { toolbarContent } .navigationTitle("filter.filters") .navigationBarTitleDisplayMode(.inline) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif .task { do { isLoading = true diff --git a/Packages/AppAccount/Package.swift b/Packages/AppAccount/Package.swift index a2e4e44d..9bc861b6 100644 --- a/Packages/AppAccount/Package.swift +++ b/Packages/AppAccount/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift b/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift index 721fb438..9fc0e8c2 100644 --- a/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift +++ b/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift @@ -96,7 +96,9 @@ public struct AppAccountsSelectorView: View { AppAccountView(viewModel: viewModel) } } - .listRowBackground(theme.primaryBackgroundColor) + #if !os(visionOS) + .listRowBackground(theme.primaryBackgroundColor) + #endif if accountCreationEnabled { Section { @@ -111,7 +113,9 @@ public struct AppAccountsSelectorView: View { } settingsButton } - .listRowBackground(theme.primaryBackgroundColor) + #if !os(visionOS) + .listRowBackground(theme.primaryBackgroundColor) + #endif } } .listStyle(.insetGrouped) diff --git a/Packages/Conversations/Package.swift b/Packages/Conversations/Package.swift index e113bb99..dce06f6d 100644 --- a/Packages/Conversations/Package.swift +++ b/Packages/Conversations/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift b/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift index fc25bdd8..df092647 100644 --- a/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift +++ b/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift @@ -46,7 +46,9 @@ public struct ConversationDetailView: View { } .padding(.horizontal, .layoutPadding) } + #if !os(visionOS) .scrollDismissesKeyboard(.interactively) + #endif .safeAreaInset(edge: .bottom) { inputTextView } @@ -68,8 +70,10 @@ public struct ConversationDetailView: View { } } .navigationBarTitleDisplayMode(.inline) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) + #endif .toolbar { ToolbarItem(placement: .principal) { if viewModel.conversation.accounts.count == 1, diff --git a/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift b/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift index 6724f2cd..8552618e 100644 --- a/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift +++ b/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift @@ -43,7 +43,11 @@ struct ConversationMessageView: View { routerPath.handleStatus(status: message, url: url) }) } + #if os(visionOS) + .background(isOwnMessage ? Material.ultraThick : Material.regular) + #else .background(isOwnMessage ? theme.tintColor.opacity(0.2) : theme.secondaryBackgroundColor) + #endif .cornerRadius(8) .padding(.leading, isOwnMessage ? 24 : 0) .padding(.trailing, isOwnMessage ? 0 : 24) diff --git a/Packages/Conversations/Sources/Conversations/List/ConversationsListRow.swift b/Packages/Conversations/Sources/Conversations/List/ConversationsListRow.swift index ac54dd8a..56927da8 100644 --- a/Packages/Conversations/Sources/Conversations/List/ConversationsListRow.swift +++ b/Packages/Conversations/Sources/Conversations/List/ConversationsListRow.swift @@ -1,4 +1,3 @@ -import Accounts import DesignSystem import Env import Models @@ -86,6 +85,8 @@ struct ConversationsListRow: View { } } } + .buttonStyle(.plain) + .hoverEffectDisabled() } private var actionsView: some View { diff --git a/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift b/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift index f9c8b3f4..58b65365 100644 --- a/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift +++ b/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift @@ -81,8 +81,10 @@ public struct ConversationsListView: View { } .padding(.top, .layoutPadding) } + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) + #endif .navigationTitle("conversations.navigation-title") .navigationBarTitleDisplayMode(.inline) .toolbar { diff --git a/Packages/DesignSystem/Package.swift b/Packages/DesignSystem/Package.swift index 770538af..bf17f11b 100644 --- a/Packages/DesignSystem/Package.swift +++ b/Packages/DesignSystem/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/DesignSystem/Sources/DesignSystem/SceneDelegate.swift b/Packages/DesignSystem/Sources/DesignSystem/SceneDelegate.swift index 689993ce..67edbd55 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/SceneDelegate.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/SceneDelegate.swift @@ -4,8 +4,13 @@ import UIKit @Observable public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable { public var window: UIWindow? + #if os(visionOS) + public private(set) var windowWidth: CGFloat = 0 + public private(set) var windowHeight: CGFloat = 0 + #else public private(set) var windowWidth: CGFloat = UIScreen.main.bounds.size.width public private(set) var windowHeight: CGFloat = UIScreen.main.bounds.size.height + #endif public func scene(_ scene: UIScene, willConnectTo _: UISceneSession, @@ -24,8 +29,13 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable { override public init() { super.init() + #if os(visionOS) + windowWidth = window?.bounds.size.width ?? 0 + windowHeight = window?.bounds.size.height ?? 0 + #else windowWidth = window?.bounds.size.width ?? UIScreen.main.bounds.size.width windowHeight = window?.bounds.size.height ?? UIScreen.main.bounds.size.height + #endif Self.observedSceneDelegate.insert(self) _ = Self.observer // just for activating the lazy static property } @@ -41,15 +51,26 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable { while true { try? await Task.sleep(for: .seconds(0.1)) for delegate in observedSceneDelegate { + #if os(visionOS) + let newWidth = delegate.window?.bounds.size.width ?? 0 + if delegate.windowWidth != newWidth { + delegate.windowWidth = newWidth + } + let newHeight = delegate.window?.bounds.size.height ?? 0 + if delegate.windowHeight != newHeight { + delegate.windowHeight = newHeight + } + #else let newWidth = delegate.window?.bounds.size.width ?? UIScreen.main.bounds.size.width if delegate.windowWidth != newWidth { delegate.windowWidth = newWidth } - let newHeight = delegate.window?.bounds.size.height ?? UIScreen.main.bounds.size.height if delegate.windowHeight != newHeight { delegate.windowHeight = newHeight } + #endif + } } } diff --git a/Packages/Env/Package.swift b/Packages/Env/Package.swift index cfa53709..b545f08c 100644 --- a/Packages/Env/Package.swift +++ b/Packages/Env/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Env/Sources/Env/HapticManager.swift b/Packages/Env/Sources/Env/HapticManager.swift index 4ffa6e24..bf98c32a 100644 --- a/Packages/Env/Sources/Env/HapticManager.swift +++ b/Packages/Env/Sources/Env/HapticManager.swift @@ -5,27 +5,42 @@ import UIKit public class HapticManager { public static let shared: HapticManager = .init() + #if os(visionOS) + public enum FeedbackType: Int { + case success, warning, error + } + #endif + public enum HapticType { case buttonPress case dataRefresh(intensity: CGFloat) + #if os(visionOS) + case notification(_ type: FeedbackType) + #else case notification(_ type: UINotificationFeedbackGenerator.FeedbackType) + #endif case tabSelection case timeline } + #if !os(visionOS) private let selectionGenerator = UISelectionFeedbackGenerator() private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy) private let notificationGenerator = UINotificationFeedbackGenerator() + #endif private let userPreferences = UserPreferences.shared private init() { + #if !os(visionOS) selectionGenerator.prepare() impactGenerator.prepare() + #endif } @MainActor public func fireHaptic(_ type: HapticType) { + #if !os(visionOS) guard supportsHaptics else { return } switch type { @@ -50,6 +65,7 @@ public class HapticManager { selectionGenerator.selectionChanged() } } + #endif } public var supportsHaptics: Bool { diff --git a/Packages/Explore/Package.swift b/Packages/Explore/Package.swift index 999db826..42af2919 100644 --- a/Packages/Explore/Package.swift +++ b/Packages/Explore/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Explore/Sources/Explore/ExploreView.swift b/Packages/Explore/Sources/Explore/ExploreView.swift index 675bfd2f..e1bc2de2 100644 --- a/Packages/Explore/Sources/Explore/ExploreView.swift +++ b/Packages/Explore/Sources/Explore/ExploreView.swift @@ -47,7 +47,9 @@ public struct ExploreView: View { ProgressView() Spacer() } + #if !os(visionOS) .listRowBackground(theme.secondaryBackgroundColor) + #endif .listRowSeparator(.hidden) .id(UUID()) } @@ -55,7 +57,9 @@ public struct ExploreView: View { EmptyView(iconName: "magnifyingglass", title: "explore.search.title", message: "explore.search.message-\(client.server)") + #if !os(visionOS) .listRowBackground(theme.secondaryBackgroundColor) + #endif .listRowSeparator(.hidden) } else { quickAccessView @@ -90,8 +94,10 @@ public struct ExploreView: View { } } .listStyle(.plain) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) + #endif .navigationTitle("explore.navigation-title") .searchable(text: $viewModel.searchQuery, isPresented: $viewModel.isSearchPresented, @@ -140,7 +146,9 @@ public struct ExploreView: View { } .scrollIndicators(.never) .listRowInsets(EdgeInsets()) + #if !os(visionOS) .listRowBackground(theme.secondaryBackgroundColor) + #endif .listRowSeparator(.hidden) } @@ -150,7 +158,9 @@ public struct ExploreView: View { .padding(.vertical, 8) .redacted(reason: .placeholder) .allowsHitTesting(false) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } @@ -161,7 +171,9 @@ public struct ExploreView: View { ForEach(results.accounts) { account in if let relationship = results.relationships.first(where: { $0.id == account.id }) { AccountsListRow(viewModel: .init(account: account, relationShip: relationship)) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } } @@ -170,7 +182,9 @@ public struct ExploreView: View { Section("explore.section.tags") { ForEach(results.hashtags) { tag in TagRowView(tag: tag) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .padding(.vertical, 4) } } @@ -179,7 +193,9 @@ public struct ExploreView: View { Section("explore.section.posts") { ForEach(results.statuses) { status in StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath)) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .padding(.vertical, 8) } } @@ -193,14 +209,18 @@ public struct ExploreView: View { { account in if let relationship = viewModel.suggestedAccountsRelationShips.first(where: { $0.id == account.id }) { AccountsListRow(viewModel: .init(account: account, relationShip: relationship)) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } NavigationLink(value: RouterDestination.accountsList(accounts: viewModel.suggestedAccounts)) { Text("see-more") .foregroundColor(theme.tintColor) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } @@ -210,14 +230,18 @@ public struct ExploreView: View { .prefix(upTo: viewModel.trendingTags.count > 5 ? 5 : viewModel.trendingTags.count)) { tag in TagRowView(tag: tag) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .padding(.vertical, 4) } NavigationLink(value: RouterDestination.tagsList(tags: viewModel.trendingTags)) { Text("see-more") .foregroundColor(theme.tintColor) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } @@ -227,7 +251,9 @@ public struct ExploreView: View { .prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath)) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .padding(.vertical, 8) } @@ -235,7 +261,9 @@ public struct ExploreView: View { Text("see-more") .foregroundColor(theme.tintColor) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } @@ -245,7 +273,9 @@ public struct ExploreView: View { .prefix(upTo: viewModel.trendingLinks.count > 3 ? 3 : viewModel.trendingLinks.count)) { card in StatusRowCardView(card: card) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .padding(.vertical, 8) } @@ -253,7 +283,9 @@ public struct ExploreView: View { Text("see-more") .foregroundColor(theme.tintColor) } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } } diff --git a/Packages/Lists/Package.swift b/Packages/Lists/Package.swift index 828c0ecc..403f7920 100644 --- a/Packages/Lists/Package.swift +++ b/Packages/Lists/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Lists/Sources/Lists/Edit/ListEditView.swift b/Packages/Lists/Sources/Lists/Edit/ListEditView.swift index 0d9acd6c..f45323ce 100644 --- a/Packages/Lists/Sources/Lists/Edit/ListEditView.swift +++ b/Packages/Lists/Sources/Lists/Edit/ListEditView.swift @@ -65,7 +65,9 @@ public struct ListEditView: View { .listRowBackground(theme.primaryBackgroundColor) .disabled(viewModel.isUpdating) } + #if !os(visionOS) .scrollDismissesKeyboard(.immediately) + #endif .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) .toolbar { diff --git a/Packages/MediaUI/Package.swift b/Packages/MediaUI/Package.swift index 73cb24ba..ba6c1e04 100644 --- a/Packages/MediaUI/Package.swift +++ b/Packages/MediaUI/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Models/Package.swift b/Packages/Models/Package.swift index 3de3587e..d8edd7e4 100644 --- a/Packages/Models/Package.swift +++ b/Packages/Models/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Network/Package.swift b/Packages/Network/Package.swift index b4702e64..0c8e01fe 100644 --- a/Packages/Network/Package.swift +++ b/Packages/Network/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Notifications/Package.swift b/Packages/Notifications/Package.swift index 03dae33f..116afea1 100644 --- a/Packages/Notifications/Package.swift +++ b/Packages/Notifications/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Notifications/Sources/Notifications/NotificationsListView.swift b/Packages/Notifications/Sources/Notifications/NotificationsListView.swift index fe4f8cca..4e77ef43 100644 --- a/Packages/Notifications/Sources/Notifications/NotificationsListView.swift +++ b/Packages/Notifications/Sources/Notifications/NotificationsListView.swift @@ -83,8 +83,10 @@ public struct NotificationsListView: View { } } .navigationBarTitleDisplayMode(.inline) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) + #endif .task { viewModel.client = client viewModel.currentAccount = account @@ -130,7 +132,12 @@ public struct NotificationsListView: View { leading: .layoutPadding + 4, bottom: 12, trailing: .layoutPadding)) - .listRowBackground(theme.primaryBackgroundColor) + #if os(visionOS) + .listRowBackground(RoundedRectangle(cornerRadius: 8) + .foregroundStyle(Material.regular)) + #else + .listRowBackground(theme.primaryBackgroundColor) + #endif .redacted(reason: .placeholder) .allowsHitTesting(false) } @@ -140,7 +147,9 @@ public struct NotificationsListView: View { EmptyView(iconName: "bell.slash", title: "notifications.empty.title", message: "notifications.empty.message") + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .listSectionSeparator(.hidden) } else { ForEach(notifications) { notification in @@ -152,8 +161,13 @@ public struct NotificationsListView: View { leading: .layoutPadding + 4, bottom: 12, trailing: .layoutPadding)) + #if os(visionOS) + .listRowBackground(RoundedRectangle(cornerRadius: 8) + .foregroundStyle(notification.type == .mention && lockedType != .mention ? Material.thick : Material.regular)) + #else .listRowBackground(notification.type == .mention && lockedType != .mention ? theme.secondaryBackgroundColor : theme.primaryBackgroundColor) + #endif .id(notification.id) } } @@ -181,7 +195,9 @@ public struct NotificationsListView: View { await viewModel.fetchNotifications() } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .listSectionSeparator(.hidden) } } @@ -196,7 +212,9 @@ public struct NotificationsListView: View { leading: .layoutPadding + 4, bottom: .layoutPadding, trailing: .layoutPadding)) + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif } private var topPaddingView: some View { diff --git a/Packages/Status/Package.swift b/Packages/Status/Package.swift index fa572d43..8d8fe915 100644 --- a/Packages/Status/Package.swift +++ b/Packages/Status/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift index 0ac28b70..3e2ca2ef 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift @@ -59,7 +59,9 @@ public struct StatusDetailView: View { .foregroundColor(theme.secondaryBackgroundColor) .frame(minHeight: reader.frame(in: .local).size.height - statusHeight) .listRowSeparator(.hidden) + #if !os(visionOS) .listRowBackground(theme.secondaryBackgroundColor) + #endif .listRowInsets(.init()) .accessibilityHidden(true) @@ -69,8 +71,10 @@ public struct StatusDetailView: View { } .environment(\.defaultMinListRowHeight, 1) .listStyle(.plain) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) + #endif .onChange(of: viewModel.scrollToId) { _, newValue in if let newValue { viewModel.scrollToId = nil @@ -132,7 +136,9 @@ public struct StatusDetailView: View { } } .id(status.id) + #if !os(visionOS) .listRowBackground(viewModel.highlightRowColor) + #endif .listRowInsets(.init(top: 12, leading: .layoutPadding, bottom: 12, @@ -149,7 +155,9 @@ public struct StatusDetailView: View { await viewModel.fetch() } } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .listRowSeparator(.hidden) } @@ -169,13 +177,17 @@ public struct StatusDetailView: View { } .frame(height: 50) .listRowSeparator(.hidden) + #if !os(visionOS) .listRowBackground(theme.secondaryBackgroundColor) + #endif .listRowInsets(.init()) } private var topPaddingView: some View { HStack { EmptyView() } + #if !os(visionOS) .listRowBackground(theme.primaryBackgroundColor) + #endif .listRowSeparator(.hidden) .listRowInsets(.init()) .frame(height: .layoutPadding) diff --git a/Packages/Status/Sources/Status/Editor/Components/GIF/GIFPickerView.swift b/Packages/Status/Sources/Status/Editor/Components/GIF/GIFPickerView.swift index 5fb562d6..14b00b76 100644 --- a/Packages/Status/Sources/Status/Editor/Components/GIF/GIFPickerView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/GIF/GIFPickerView.swift @@ -1,3 +1,4 @@ +#if !os(visionOS) import DesignSystem import GiphyUISDK import SwiftUI @@ -49,3 +50,4 @@ struct GifPickerView: UIViewControllerRepresentable { } } } +#endif diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift index 0a7cae1b..203f9818 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift @@ -1,6 +1,8 @@ import DesignSystem import Env +#if !os(visionOS) import GiphyUISDK +#endif import Models import NukeUI import PhotosUI @@ -29,186 +31,220 @@ struct StatusEditorAccessoryView: View { var body: some View { @Bindable var viewModel = focusedSEVM - VStack(spacing: 0) { + #if os(visionOS) + HStack { + contentView + } + .frame(height: 24) + .padding(16) + .background(.ultraThinMaterial) + .cornerRadius(8) + #else Divider() HStack { - ScrollView(.horizontal) { - HStack(alignment: .center, spacing: 16) { - Menu { - Button { - isPhotosPickerPresented = true - } label: { - Label("status.editor.photo-library", systemImage: "photo") - } - #if !targetEnvironment(macCatalyst) - Button { - isCameraPickerPresented = true - } label: { - Label("status.editor.camera-picker", systemImage: "camera") - } - #endif - Button { - isFileImporterPresented = true - } label: { - Label("status.editor.browse-file", systemImage: "folder") - } - - Button { - isGIFPickerPresented = true - } label: { - Label("GIPHY", systemImage: "party.popper") - } - } label: { - if viewModel.isMediasLoading { - ProgressView() - } else { - Image(systemName: "photo.on.rectangle.angled") - } - } - .photosPicker(isPresented: $isPhotosPickerPresented, - selection: $viewModel.mediaPickers, - maxSelectionCount: 4, - matching: .any(of: [.images, .videos]), - photoLibrary: .shared()) - .fileImporter(isPresented: $isFileImporterPresented, - allowedContentTypes: [.image, .video], - allowsMultipleSelection: true) - { result in - if let urls = try? result.get() { - viewModel.processURLs(urls: urls) - } - } - .fullScreenCover(isPresented: $isCameraPickerPresented, content: { - StatusEditorCameraPickerView(selectedImage: .init(get: { - nil - }, set: { image in - if let image { - viewModel.processCameraPhoto(image: image) - } - })) - .background(.black) - }) - .sheet(isPresented: $isGIFPickerPresented, content: { - GifPickerView { url in - GPHCache.shared.downloadAssetData(url) { data, _ in - guard let data else { return } - viewModel.processGIFData(data: data) - } - isGIFPickerPresented = false - } onShouldDismissGifPicker: { - isGIFPickerPresented = false - } - .presentationDetents([.medium, .large]) - }) - .accessibilityLabel("accessibility.editor.button.attach-photo") - .disabled(viewModel.showPoll) - - Button { - // all SEVM have the same visibility value - followUpSEVMs.append(StatusEditorViewModel(mode: .new(visibility: focusedSEVM.visibility))) - } label: { - Image(systemName: "arrowshape.turn.up.left.circle.fill") - } - .disabled(!canAddNewSEVM) - - Button { - withAnimation { - viewModel.showPoll.toggle() - viewModel.resetPollDefaults() - } - } label: { - Image(systemName: "chart.bar") - } - .accessibilityLabel("accessibility.editor.button.poll") - .disabled(viewModel.shouldDisablePollButton) - - Button { - withAnimation { - viewModel.spoilerOn.toggle() - } - isSpoilerTextFocused = viewModel.id - } label: { - Image(systemName: viewModel.spoilerOn ? "exclamationmark.triangle.fill" : "exclamationmark.triangle") - } - .accessibilityLabel("accessibility.editor.button.spoiler") - - if !viewModel.mode.isInShareExtension { - Button { - isDraftsSheetDisplayed = true - } label: { - Image(systemName: "archivebox") - } - .accessibilityLabel("accessibility.editor.button.drafts") - .popover(isPresented: $isDraftsSheetDisplayed) { - if UIDevice.current.userInterfaceIdiom == .phone { - draftsListView - .presentationDetents([.medium]) - } else { - draftsListView - .frame(width: 400, height: 500) - } - } - } - - if !viewModel.customEmojiContainer.isEmpty { - Button { - isCustomEmojisSheetDisplay = true - } label: { - // This is a workaround for an apparent bug in the `face.smiling` SF Symbol. - // See https://github.com/Dimillian/IceCubesApp/issues/1193 - let customEmojiSheetIconName = colorScheme == .light ? "face.smiling" : "face.smiling.inverse" - Image(systemName: customEmojiSheetIconName) - } - .accessibilityLabel("accessibility.editor.button.custom-emojis") - .popover(isPresented: $isCustomEmojisSheetDisplay) { - if UIDevice.current.userInterfaceIdiom == .phone { - customEmojisSheet - } else { - customEmojisSheet - .frame(width: 400, height: 500) - } - } - } - - Button { - isLanguageSheetDisplayed.toggle() - } label: { - if let language = viewModel.selectedLanguage { - Text(language.uppercased()) - } else { - Image(systemName: "globe") - } - } - .accessibilityLabel("accessibility.editor.button.language") - .popover(isPresented: $isLanguageSheetDisplayed) { - if UIDevice.current.userInterfaceIdiom == .phone { - languageSheetView - } else { - languageSheetView - .frame(width: 400, height: 500) - } - } - - if preferences.isOpenAIEnabled { - AIMenu.disabled(!viewModel.canPost) - } - } - .padding(.horizontal, .layoutPadding) - } - Spacer() - characterCountView - .padding(.trailing, .layoutPadding) + contentView } .frame(height: 20) .padding(.vertical, 12) .background(.ultraThinMaterial) + #endif } .onAppear { viewModel.setInitialLanguageSelection(preference: preferences.recentlyUsedLanguages.first ?? preferences.serverPreferences?.postLanguage) } } + @ViewBuilder + private var contentView: some View { + #if os(visionOS) + HStack(spacing: 8) { + actionsView + characterCountView + .padding(.leading, 16) + } + #else + ScrollView(.horizontal) { + HStack(alignment: .center, spacing: 16) { + actionsView + } + .padding(.horizontal, .layoutPadding) + } + Spacer() + characterCountView + .padding(.trailing, .layoutPadding) + #endif + } + + @ViewBuilder + private var actionsView: some View { + @Bindable var viewModel = focusedSEVM + Menu { + Button { + isPhotosPickerPresented = true + } label: { + Label("status.editor.photo-library", systemImage: "photo") + } + #if !targetEnvironment(macCatalyst) + Button { + isCameraPickerPresented = true + } label: { + Label("status.editor.camera-picker", systemImage: "camera") + } + #endif + Button { + isFileImporterPresented = true + } label: { + Label("status.editor.browse-file", systemImage: "folder") + } + + #if !os(visionOS) + Button { + isGIFPickerPresented = true + } label: { + Label("GIPHY", systemImage: "party.popper") + } + #endif + } label: { + if viewModel.isMediasLoading { + ProgressView() + } else { + Image(systemName: "photo.on.rectangle.angled") + } + } + .photosPicker(isPresented: $isPhotosPickerPresented, + selection: $viewModel.mediaPickers, + maxSelectionCount: 4, + matching: .any(of: [.images, .videos]), + photoLibrary: .shared()) + .fileImporter(isPresented: $isFileImporterPresented, + allowedContentTypes: [.image, .video], + allowsMultipleSelection: true) + { result in + if let urls = try? result.get() { + viewModel.processURLs(urls: urls) + } + } + .fullScreenCover(isPresented: $isCameraPickerPresented, content: { + StatusEditorCameraPickerView(selectedImage: .init(get: { + nil + }, set: { image in + if let image { + viewModel.processCameraPhoto(image: image) + } + })) + .background(.black) + }) + .sheet(isPresented: $isGIFPickerPresented, content: { + #if !os(visionOS) + GifPickerView { url in + GPHCache.shared.downloadAssetData(url) { data, _ in + guard let data else { return } + viewModel.processGIFData(data: data) + } + isGIFPickerPresented = false + } onShouldDismissGifPicker: { + isGIFPickerPresented = false + } + .presentationDetents([.medium, .large]) + #else + EmptyView() + #endif + }) + .accessibilityLabel("accessibility.editor.button.attach-photo") + .disabled(viewModel.showPoll) + + Button { + // all SEVM have the same visibility value + followUpSEVMs.append(StatusEditorViewModel(mode: .new(visibility: focusedSEVM.visibility))) + } label: { + Image(systemName: "arrowshape.turn.up.left.circle.fill") + } + .disabled(!canAddNewSEVM) + + Button { + withAnimation { + viewModel.showPoll.toggle() + viewModel.resetPollDefaults() + } + } label: { + Image(systemName: "chart.bar") + } + .accessibilityLabel("accessibility.editor.button.poll") + .disabled(viewModel.shouldDisablePollButton) + + Button { + withAnimation { + viewModel.spoilerOn.toggle() + } + isSpoilerTextFocused = viewModel.id + } label: { + Image(systemName: viewModel.spoilerOn ? "exclamationmark.triangle.fill" : "exclamationmark.triangle") + } + .accessibilityLabel("accessibility.editor.button.spoiler") + + if !viewModel.mode.isInShareExtension { + Button { + isDraftsSheetDisplayed = true + } label: { + Image(systemName: "archivebox") + } + .accessibilityLabel("accessibility.editor.button.drafts") + .popover(isPresented: $isDraftsSheetDisplayed) { + if UIDevice.current.userInterfaceIdiom == .phone { + draftsListView + .presentationDetents([.medium]) + } else { + draftsListView + .frame(width: 400, height: 500) + } + } + } + + if !viewModel.customEmojiContainer.isEmpty { + Button { + isCustomEmojisSheetDisplay = true + } label: { + // This is a workaround for an apparent bug in the `face.smiling` SF Symbol. + // See https://github.com/Dimillian/IceCubesApp/issues/1193 + let customEmojiSheetIconName = colorScheme == .light ? "face.smiling" : "face.smiling.inverse" + Image(systemName: customEmojiSheetIconName) + } + .accessibilityLabel("accessibility.editor.button.custom-emojis") + .popover(isPresented: $isCustomEmojisSheetDisplay) { + if UIDevice.current.userInterfaceIdiom == .phone { + customEmojisSheet + } else { + customEmojisSheet + .frame(width: 400, height: 500) + } + } + } + + Button { + isLanguageSheetDisplayed.toggle() + } label: { + if let language = viewModel.selectedLanguage { + Text(language.uppercased()) + } else { + Image(systemName: "globe") + } + } + .accessibilityLabel("accessibility.editor.button.language") + .popover(isPresented: $isLanguageSheetDisplayed) { + if UIDevice.current.userInterfaceIdiom == .phone { + languageSheetView + } else { + languageSheetView + .frame(width: 400, height: 500) + } + } + + if preferences.isOpenAIEnabled { + AIMenu.disabled(!viewModel.canPost) + } + } + private var canAddNewSEVM: Bool { guard followUpSEVMs.count < 5 else { return false } diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorCameraPickerView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorCameraPickerView.swift index 80e511f0..b3ca568f 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorCameraPickerView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorCameraPickerView.swift @@ -21,7 +21,9 @@ struct StatusEditorCameraPickerView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIImagePickerController { let imagePicker = UIImagePickerController() + #if !os(visionOS) imagePicker.sourceType = .camera + #endif imagePicker.delegate = context.coordinator return imagePicker } diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorCoreView.swift b/Packages/Status/Sources/Status/Editor/StatusEditorCoreView.swift index 716694aa..8192d159 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorCoreView.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorCoreView.swift @@ -1,4 +1,3 @@ -import Accounts import AppAccount import DesignSystem import Env @@ -53,7 +52,9 @@ struct StatusEditorCoreView: View { } .opacity(editorFocusState == assignedFocusState ? 1 : 0.6) } + #if !os(visionOS) .background(theme.primaryBackgroundColor) + #endif .focused($editorFocusState, equals: assignedFocusState) .onAppear { setupViewModel() } } diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift index ed484a43..dc89af79 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift @@ -1,4 +1,3 @@ -import Accounts import AppAccount import DesignSystem import EmojiText @@ -74,13 +73,21 @@ public struct StatusEditorView: View { .scrollPosition(id: $scrollID, anchor: .top) .animation(.bouncy(duration: 0.3), value: editorFocusState) .animation(.bouncy(duration: 0.3), value: followUpSEVMs) + #if !os(visionOS) .background(theme.primaryBackgroundColor) + #endif .safeAreaInset(edge: .bottom) { StatusEditorAutoCompleteView(viewModel: focusedSEVM) } + #if os(visionOS) + .ornament(attachmentAnchor: .scene(.bottom)) { + StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused, focusedSEVM: focusedSEVM, followUpSEVMs: $followUpSEVMs) + } + #else .safeAreaInset(edge: .bottom) { StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused, focusedSEVM: focusedSEVM, followUpSEVMs: $followUpSEVMs) } + #endif .accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views .navigationTitle(focusedSEVM.mode.title) .navigationBarTitleDisplayMode(.inline) diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index d7329628..947b4d21 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -149,7 +149,12 @@ public struct StatusRowView: View { StatusRowSwipeView(viewModel: viewModel, mode: .leading) } } - .listRowBackground(viewModel.highlightRowColor) + #if os(visionOS) + .listRowBackground(RoundedRectangle(cornerRadius: 8) + .foregroundStyle(Material.regular)) + #else + .listRowBackground(viewModel.highlightRowColor) + #endif .listRowInsets(.init(top: 12, leading: .layoutPadding, bottom: 12, diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift index 3426e9ef..b1406f2f 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift @@ -22,11 +22,13 @@ public struct StatusRowMediaPreviewView: View { @State private var isQuickLookLoading: Bool = false var availableWidth: CGFloat { + #if !os(visionOS) if UIDevice.current.userInterfaceIdiom == .phone && (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) || theme.statusDisplayStyle == .medium { return sceneDelegate.windowWidth * 0.80 } + #endif return sceneDelegate.windowWidth } diff --git a/Packages/Timeline/Package.swift b/Packages/Timeline/Package.swift index 5b6c904f..0d161b53 100644 --- a/Packages/Timeline/Package.swift +++ b/Packages/Timeline/Package.swift @@ -8,6 +8,7 @@ let package = Package( defaultLocalization: "en", platforms: [ .iOS(.v17), + .visionOS(.v1), ], products: [ .library( diff --git a/Packages/Timeline/Sources/Timeline/TimelineView.swift b/Packages/Timeline/Sources/Timeline/TimelineView.swift index e0f5e493..836678eb 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineView.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineView.swift @@ -60,8 +60,10 @@ public struct TimelineView: View { .id(client.id) .environment(\.defaultMinListRowHeight, 1) .listStyle(.plain) + #if !os(visionOS) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) + #endif .introspect(.list, on: .iOS(.v17)) { (collectionView: UICollectionView) in DispatchQueue.main.async { self.collectionView = collectionView