VisionOS native support (#1758)

* Initial support

* UI Adjustments

* WIP icons

* More UI
This commit is contained in:
Thomas Ricouard 2023-12-19 09:51:20 +01:00 committed by GitHub
parent ca13e61b53
commit 5a2478c791
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 677 additions and 195 deletions

View file

@ -67,7 +67,7 @@
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; }; 9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; };
9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; }; 9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; };
9FAD858E29743F7400496AB1 /* (null) in Resources */ = {isa = PBXBuildFile; }; 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 */; }; 9FAD85982974405D00496AB1 /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD85972974405D00496AB1 /* Status */; };
9FAD859A297440CB00496AB1 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD8599297440CB00496AB1 /* KeychainSwift */; }; 9FAD859A297440CB00496AB1 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD8599297440CB00496AB1 /* KeychainSwift */; };
9FAD859C2974422700496AB1 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD859B2974422700496AB1 /* AppAccount */; }; 9FAD859C2974422700496AB1 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD859B2974422700496AB1 /* AppAccount */; };
@ -90,7 +90,7 @@
9FD542E72962D2FF0045321A /* Lists in Frameworks */ = {isa = PBXBuildFile; productRef = 9FD542E62962D2FF0045321A /* Lists */; }; 9FD542E72962D2FF0045321A /* Lists in Frameworks */ = {isa = PBXBuildFile; productRef = 9FD542E62962D2FF0045321A /* Lists */; };
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; }; 9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; };
9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; }; 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 */; }; 9FF614492B2EDCEC00F7B0E6 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FF614482B2EDCEC00F7B0E6 /* GiphyUISDK */; };
9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; }; 9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; };
9FFF677E299B7D2800FE700A /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677D299B7D2800FE700A /* Status */; }; 9FFF677E299B7D2800FE700A /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677D299B7D2800FE700A /* Status */; };
@ -107,7 +107,7 @@
E9DF41FC29830FEC0003AAD2 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */; }; E9DF41FC29830FEC0003AAD2 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */; };
E9DF420129830FEC0003AAD2 /* ActionRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */; }; E9DF420129830FEC0003AAD2 /* ActionRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */; };
E9DF420329830FEC0003AAD2 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = E9DF420229830FEC0003AAD2 /* Action.js */; }; 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 */; }; FA31A9AB2A66BF7C00D5F662 /* EditTagGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA31A9AA2A66BF7C00D5F662 /* EditTagGroupView.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -862,11 +862,19 @@
}; };
9FAD859129743F7400496AB1 /* PBXTargetDependency */ = { 9FAD859129743F7400496AB1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
platformFilters = (
ios,
maccatalyst,
);
target = 9FAD858729743F7400496AB1 /* IceCubesShareExtension */; target = 9FAD858729743F7400496AB1 /* IceCubesShareExtension */;
targetProxy = 9FAD859029743F7400496AB1 /* PBXContainerItemProxy */; targetProxy = 9FAD859029743F7400496AB1 /* PBXContainerItemProxy */;
}; };
E9DF420629830FEC0003AAD2 /* PBXTargetDependency */ = { E9DF420629830FEC0003AAD2 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
platformFilters = (
ios,
maccatalyst,
);
target = E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */; target = E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */;
targetProxy = E9DF420529830FEC0003AAD2 /* PBXContainerItemProxy */; targetProxy = E9DF420529830FEC0003AAD2 /* PBXContainerItemProxy */;
}; };
@ -920,12 +928,13 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2,7";
}; };
name = Debug; name = Debug;
}; };
@ -954,12 +963,13 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2,7";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;
@ -1202,13 +1212,13 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
PRODUCT_NAME = "Ice Cubes"; PRODUCT_NAME = "Ice Cubes";
SDKROOT = auto; SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2,7";
}; };
name = Debug; name = Debug;
}; };
@ -1256,13 +1266,13 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
PRODUCT_NAME = "Ice Cubes"; PRODUCT_NAME = "Ice Cubes";
SDKROOT = auto; SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2,7";
}; };
name = Release; name = Release;
}; };

View file

@ -97,7 +97,7 @@ extension View {
.preferredColorScheme(Theme.shared.selectedScheme == .dark ? .dark : .light) .preferredColorScheme(Theme.shared.selectedScheme == .dark ? .dark : .light)
case .accountPushNotficationsSettings: case .accountPushNotficationsSettings:
if let subscription = PushNotificationsService.shared.subscriptions.first(where: { $0.account.token == AppAccountsManager.shared.currentAccount.oauthToken }) { if let subscription = PushNotificationsService.shared.subscriptions.first(where: { $0.account.token == AppAccountsManager.shared.currentAccount.oauthToken }) {
PushNotificationsView(subscription: subscription) PushNotificationsViewWrapper(subscription: subscription)
} else { } else {
EmptyView() EmptyView()
} }

View file

@ -35,9 +35,11 @@ public struct ReportView: View {
} }
.navigationTitle("report.title") .navigationTitle("report.title")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.scrollDismissesKeyboard(.immediately) .scrollDismissesKeyboard(.immediately)
#endif
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button { Button {

View file

@ -17,7 +17,9 @@ private struct SafariRouter: ViewModifier {
@Environment(UserPreferences.self) private var preferences @Environment(UserPreferences.self) private var preferences
@Environment(RouterPath.self) private var routerPath @Environment(RouterPath.self) private var routerPath
#if !os(visionOS)
@State private var safariManager = InAppSafariManager() @State private var safariManager = InAppSafariManager()
#endif
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
@ -50,17 +52,24 @@ private struct SafariRouter: ViewModifier {
guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else { guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else {
return .systemAction return .systemAction
} }
#if os(visionOS)
return .systemAction
#else
return safariManager.open(url) return safariManager.open(url)
#endif
} }
} }
#if !os(visionOS)
.background { .background {
WindowReader { window in WindowReader { window in
safariManager.windowScene = window.windowScene safariManager.windowScene = window.windowScene
} }
} }
#endif
} }
} }
#if !os(visionOS)
@MainActor @MainActor
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate { @Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
var windowScene: UIWindowScene? var windowScene: UIWindowScene?
@ -113,6 +122,7 @@ private struct SafariRouter: ViewModifier {
} }
} }
} }
#endif
private struct WindowReader: UIViewRepresentable { private struct WindowReader: UIViewRepresentable {
var onUpdate: (UIWindow) -> Void var onUpdate: (UIWindow) -> Void

View file

@ -57,7 +57,9 @@ struct AboutView: View {
} footer: { } footer: {
Text("\(versionNumber)©2023 Thomas Ricouard") Text("\(versionNumber)©2023 Thomas Ricouard")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
followAccountsSection followAccountsSection
@ -94,14 +96,18 @@ struct AboutView: View {
Text("settings.about.built-with") Text("settings.about.built-with")
.textCase(nil) .textCase(nil)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.task { .task {
await fetchAccounts() await fetchAccounts()
} }
.listStyle(.insetGrouped) .listStyle(.insetGrouped)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.navigationTitle(Text("settings.about.title")) .navigationTitle(Text("settings.about.title"))
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
.environment(\.openURL, OpenURLAction { url in .environment(\.openURL, OpenURLAction { url in
@ -116,12 +122,16 @@ struct AboutView: View {
AccountsListRow(viewModel: iceCubesAccount) AccountsListRow(viewModel: iceCubesAccount)
AccountsListRow(viewModel: dimillianAccount) AccountsListRow(viewModel: dimillianAccount)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} else { } else {
Section { Section {
ProgressView() ProgressView()
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }

View file

@ -115,7 +115,9 @@ struct AccountSettingsView: View {
cachedPostsCount = await timelineCache.cachedPostsCount(for: appAccountsManager.currentClient.id) cachedPostsCount = await timelineCache.cachedPostsCount(for: appAccountsManager.currentClient.id)
} }
.navigationTitle(account.safeDisplayName) .navigationTitle(account.safeDisplayName)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
} }
} }

View file

@ -53,7 +53,9 @@ struct AddAccountView: View {
NavigationStack { NavigationStack {
Form { Form {
TextField("instance.url", text: $instanceName) TextField("instance.url", text: $instanceName)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.keyboardType(.URL) .keyboardType(.URL)
.textContentType(.URL) .textContentType(.URL)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
@ -77,9 +79,11 @@ struct AddAccountView: View {
.formStyle(.grouped) .formStyle(.grouped)
.navigationTitle("account.add.navigation-title") .navigationTitle("account.add.navigation-title")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.scrollDismissesKeyboard(.immediately) .scrollDismissesKeyboard(.immediately)
#endif
.toolbar { .toolbar {
if !appAccountsManager.availableAccounts.isEmpty { if !appAccountsManager.availableAccounts.isEmpty {
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
@ -164,7 +168,9 @@ struct AddAccountView: View {
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
} }
#if !os(visionOS)
.listRowBackground(theme.tintColor) .listRowBackground(theme.tintColor)
#endif
} }
private var instancesListView: some View { private var instancesListView: some View {
@ -210,11 +216,13 @@ struct AddAccountView: View {
.padding(10) .padding(10)
} }
} }
#if !os(visionOS)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
.listRowBackground(Color.clear) .listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0)) .listRowInsets(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.clipShape(RoundedRectangle(cornerRadius: 5)) .clipShape(RoundedRectangle(cornerRadius: 5))
#endif
.overlay { .overlay {
RoundedRectangle(cornerRadius: 5) RoundedRectangle(cornerRadius: 5)
.stroke(lineWidth: 1) .stroke(lineWidth: 1)
@ -249,7 +257,9 @@ struct AddAccountView: View {
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.allowsHitTesting(false) .allowsHitTesting(false)
.shimmering() .shimmering()
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private func signIn() async { private func signIn() async {

View file

@ -19,7 +19,10 @@ struct ContentSettingsView: View {
Toggle(isOn: $userPreferences.suppressDupeReblogs) { Toggle(isOn: $userPreferences.suppressDupeReblogs) {
Text("settings.content.hide-repeated-boosts") Text("settings.content.hide-repeated-boosts")
} }
}.listRowBackground(theme.primaryBackgroundColor) }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
Section("settings.content.media") { Section("settings.content.media") {
Toggle(isOn: $userPreferences.autoPlayVideo) { Toggle(isOn: $userPreferences.autoPlayVideo) {
@ -28,7 +31,10 @@ struct ContentSettingsView: View {
Toggle(isOn: $userPreferences.showAltTextForMedia) { Toggle(isOn: $userPreferences.showAltTextForMedia) {
Text("settings.content.media.show.alt") Text("settings.content.media.show.alt")
} }
}.listRowBackground(theme.primaryBackgroundColor) }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
Section("settings.content.sharing") { Section("settings.content.sharing") {
Picker("settings.content.sharing.share-button-behavior", selection: $userPreferences.shareButtonBehavior) { Picker("settings.content.sharing.share-button-behavior", selection: $userPreferences.shareButtonBehavior) {
@ -38,14 +44,18 @@ struct ContentSettingsView: View {
} }
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
Section("settings.content.instance-settings") { Section("settings.content.instance-settings") {
Toggle(isOn: $userPreferences.useInstanceContentSettings) { Toggle(isOn: $userPreferences.useInstanceContentSettings) {
Text("settings.content.use-instance-settings") Text("settings.content.use-instance-settings")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.onChange(of: userPreferences.useInstanceContentSettings) { _, newVal in .onChange(of: userPreferences.useInstanceContentSettings) { _, newVal in
if newVal { if newVal {
userPreferences.appAutoExpandSpoilers = userPreferences.autoExpandSpoilers userPreferences.appAutoExpandSpoilers = userPreferences.autoExpandSpoilers
@ -76,7 +86,9 @@ struct ContentSettingsView: View {
} footer: { } footer: {
Text("settings.content.collapse-long-posts-hint") Text("settings.content.collapse-long-posts-hint")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
Section("settings.content.posting") { Section("settings.content.posting") {
Picker("settings.content.default-visibility", selection: $userPreferences.appDefaultPostVisibility) { Picker("settings.content.default-visibility", selection: $userPreferences.appDefaultPostVisibility) {
@ -104,11 +116,14 @@ struct ContentSettingsView: View {
} }
.disabled(userPreferences.useInstanceContentSettings) .disabled(userPreferences.useInstanceContentSettings)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.navigationTitle("settings.content.navigation-title") .navigationTitle("settings.content.navigation-title")
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
} }
} }

View file

@ -46,8 +46,10 @@ struct DisplaySettingsView: View {
resetSection resetSection
} }
.navigationTitle("settings.display.navigation-title") .navigationTitle("settings.display.navigation-title")
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.task(id: localValues.tintColor) { .task(id: localValues.tintColor) {
do { try await Task.sleep(for: .microseconds(500)) } catch {} do { try await Task.sleep(for: .microseconds(500)) } catch {}
theme.tintColor = localValues.tintColor theme.tintColor = localValues.tintColor
@ -121,7 +123,9 @@ struct DisplaySettingsView: View {
Text("settings.display.section.theme.footer") Text("settings.display.section.theme.footer")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var fontSection: some View { private var fontSection: some View {
@ -173,7 +177,9 @@ struct DisplaySettingsView: View {
d[.leading] d[.leading]
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
@ViewBuilder @ViewBuilder
@ -222,7 +228,9 @@ struct DisplaySettingsView: View {
} }
Toggle("settings.display.show-account-popover", isOn: $userPreferences.showAccountPopover) Toggle("settings.display.show-account-popover", isOn: $userPreferences.showAccountPopover)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
@ViewBuilder @ViewBuilder
@ -232,14 +240,18 @@ struct DisplaySettingsView: View {
Section("iPhone") { Section("iPhone") {
Toggle("settings.display.show-tab-label", isOn: $userPreferences.showiPhoneTabLabel) Toggle("settings.display.show-tab-label", isOn: $userPreferences.showiPhoneTabLabel)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
if UIDevice.current.userInterfaceIdiom == .pad { if UIDevice.current.userInterfaceIdiom == .pad {
Section("iPad") { Section("iPad") {
Toggle("settings.display.show-ipad-column", isOn: $userPreferences.showiPadSecondaryColumn) Toggle("settings.display.show-ipad-column", isOn: $userPreferences.showiPadSecondaryColumn)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
@ -261,7 +273,9 @@ struct DisplaySettingsView: View {
Text("settings.display.restore") Text("settings.display.restore")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var themeSelectorButton: some View { private var themeSelectorButton: some View {

View file

@ -17,10 +17,14 @@ struct HapticSettingsView: View {
Toggle("settings.haptic.tab-selection", isOn: $userPreferences.hapticTabSelectionEnabled) Toggle("settings.haptic.tab-selection", isOn: $userPreferences.hapticTabSelectionEnabled)
Toggle("settings.haptic.buttons", isOn: $userPreferences.hapticButtonPressEnabled) Toggle("settings.haptic.buttons", isOn: $userPreferences.hapticButtonPressEnabled)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.navigationTitle("settings.haptic.navigation-title") .navigationTitle("settings.haptic.navigation-title")
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
} }
} }

View file

@ -85,7 +85,9 @@ struct IconSelectorView: View {
.padding(6) .padding(6)
.navigationTitle("settings.app.icon.navigation-title") .navigationTitle("settings.app.icon.navigation-title")
} }
#if !os(visionOS)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
} }
private func makeIconGridView(icons: [Icon]) -> some View { private func makeIconGridView(icons: [Icon]) -> some View {
@ -116,6 +118,7 @@ struct IconSelectorView: View {
} }
} }
} }
.buttonStyle(.plain)
} }
} }
} }

View file

@ -13,8 +13,10 @@ struct InstanceInfoView: View {
InstanceInfoSection(instance: instance) InstanceInfoSection(instance: instance)
} }
.navigationTitle("instance.info.navigation-title") .navigationTitle("instance.info.navigation-title")
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
} }
} }
@ -35,7 +37,9 @@ public struct InstanceInfoSection: View {
LabeledContent("instance.info.posts", value: format(instance.stats.statusCount)) LabeledContent("instance.info.posts", value: format(instance.stats.statusCount))
LabeledContent("instance.info.domains", value: format(instance.stats.domainCount)) LabeledContent("instance.info.domains", value: format(instance.stats.domainCount))
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
if let rules = instance.rules { if let rules = instance.rules {
Section("instance.info.section.rules") { Section("instance.info.section.rules") {
@ -43,7 +47,9 @@ public struct InstanceInfoSection: View {
Text(rule.text.trimmingCharacters(in: .whitespacesAndNewlines)) Text(rule.text.trimmingCharacters(in: .whitespacesAndNewlines))
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }

View file

@ -7,6 +7,27 @@ import NukeUI
import SwiftUI import SwiftUI
import UserNotifications 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 @MainActor
struct PushNotificationsView: View { struct PushNotificationsView: View {
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme
@ -33,7 +54,9 @@ struct PushNotificationsView: View {
} footer: { } footer: {
Text("settings.push.main-toggle.description") Text("settings.push.main-toggle.description")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
if subscription.isEnabled { if subscription.isEnabled {
Section { Section {
@ -86,7 +109,9 @@ struct PushNotificationsView: View {
Label("settings.push.new-posts", systemImage: "bubble.right") Label("settings.push.new-posts", systemImage: "bubble.right")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
Section { Section {
@ -101,11 +126,15 @@ struct PushNotificationsView: View {
} footer: { } footer: {
Text("settings.push.duplicate.footer") Text("settings.push.duplicate.footer")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.navigationTitle("settings.push.navigation-title") .navigationTitle("settings.push.navigation-title")
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.task { .task {
await subscription.fetchSubscription() await subscription.fetchSubscription()
} }

View file

@ -45,7 +45,9 @@ struct SettingsTabs: View {
cacheSection cacheSection
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
#if !os(visionOS)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.navigationTitle(Text("settings.title")) .navigationTitle(Text("settings.title"))
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar) .toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .navigationBar)
@ -114,7 +116,9 @@ struct SettingsTabs: View {
} }
addAccountButton addAccountButton
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private func logoutAccount(account: AppAccount) async { private func logoutAccount(account: AppAccount) async {
@ -166,7 +170,9 @@ struct SettingsTabs: View {
.tint(theme.labelColor) .tint(theme.labelColor)
#endif #endif
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
@ViewBuilder @ViewBuilder
@ -204,12 +210,14 @@ struct SettingsTabs: View {
Label("settings.other.fast-refresh", systemImage: "arrow.clockwise") Label("settings.other.fast-refresh", systemImage: "arrow.clockwise")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var appSection: some View { private var appSection: some View {
Section { Section {
#if !targetEnvironment(macCatalyst) #if !targetEnvironment(macCatalyst) && !os(visionOS)
NavigationLink(destination: IconSelectorView()) { NavigationLink(destination: IconSelectorView()) {
Label { Label {
Text("settings.app.icon") Text("settings.app.icon")
@ -252,7 +260,9 @@ struct SettingsTabs: View {
Text("settings.section.app.footer \(appVersion)").frame(maxWidth: .infinity, alignment: .center) Text("settings.section.app.footer \(appVersion)").frame(maxWidth: .infinity, alignment: .center)
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var addAccountButton: some View { private var addAccountButton: some View {
@ -293,18 +303,24 @@ struct SettingsTabs: View {
context.delete(tagGroups[index]) context.delete(tagGroups[index])
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
Button { Button {
routerPath.presentedSheet = .addTagGroup routerPath.presentedSheet = .addTagGroup
} label: { } label: {
Label("timeline.filter.add-tag-groups", systemImage: "plus") Label("timeline.filter.add-tag-groups", systemImage: "plus")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.navigationTitle("timeline.filter.tag-groups") .navigationTitle("timeline.filter.tag-groups")
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
#if !os(visionOS)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.toolbar { .toolbar {
EditButton() EditButton()
} }
@ -319,17 +335,23 @@ struct SettingsTabs: View {
context.delete(localTimelines[index]) context.delete(localTimelines[index])
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
Button { Button {
routerPath.presentedSheet = .addRemoteLocalTimeline routerPath.presentedSheet = .addRemoteLocalTimeline
} label: { } label: {
Label("settings.timeline.add", systemImage: "badge.plus.radiowaves.right") Label("settings.timeline.add", systemImage: "badge.plus.radiowaves.right")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.navigationTitle("settings.general.remote-timelines") .navigationTitle("settings.general.remote-timelines")
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
#if !os(visionOS)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.toolbar { .toolbar {
EditButton() EditButton()
} }
@ -349,6 +371,8 @@ struct SettingsTabs: View {
} }
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }

View file

@ -69,8 +69,10 @@ struct SupportAppView: View {
linksSection linksSection
} }
.navigationTitle("settings.support.navigation-title") .navigationTitle("settings.support.navigation-title")
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.alert("settings.support.alert.title", isPresented: $purchaseSuccessDisplayed, actions: { .alert("settings.support.alert.title", isPresented: $purchaseSuccessDisplayed, actions: {
Button { purchaseSuccessDisplayed = false } label: { Text("alert.button.ok") } Button { purchaseSuccessDisplayed = false } label: { Text("alert.button.ok") }
}, message: { }, message: {
@ -151,7 +153,9 @@ struct SupportAppView: View {
Text("settings.support.message-from-dev") Text("settings.support.message-from-dev")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var subscriptionSection: some View { private var subscriptionSection: some View {
@ -188,7 +192,9 @@ struct SupportAppView: View {
Text("settings.support.supporter.subscription-info") Text("settings.support.supporter.subscription-info")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var tipsSection: some View { private var tipsSection: some View {
@ -213,7 +219,9 @@ struct SupportAppView: View {
} }
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var restorePurchase: some View { private var restorePurchase: some View {
@ -232,7 +240,9 @@ struct SupportAppView: View {
} footer: { } footer: {
Text("settings.support.restore-purchase.explanation") Text("settings.support.restore-purchase.explanation")
} }
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
} }
private var linksSection: some View { private var linksSection: some View {
@ -252,7 +262,9 @@ struct SupportAppView: View {
.buttonStyle(.borderless) .buttonStyle(.borderless)
} }
} }
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
} }
private var loadingPlaceholder: some View { private var loadingPlaceholder: some View {

View file

@ -46,8 +46,10 @@ struct SwipeActionsSettingsView: View {
} footer: { } footer: {
Text("settings.swipeactions.status.explanation") Text("settings.swipeactions.status.explanation")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
Section { Section {
Picker(selection: $userPreferences.swipeActionsIconStyle, label: Text("settings.swipeactions.icon-style")) { Picker(selection: $userPreferences.swipeActionsIconStyle, label: Text("settings.swipeactions.icon-style")) {
ForEach(UserPreferences.SwipeActionsIconStyle.allCases, id: \.rawValue) { style in ForEach(UserPreferences.SwipeActionsIconStyle.allCases, id: \.rawValue) { style in
@ -62,11 +64,15 @@ struct SwipeActionsSettingsView: View {
} footer: { } footer: {
Text("settings.swipeactions.use-theme-colors-explanation") Text("settings.swipeactions.use-theme-colors-explanation")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.navigationTitle("settings.swipeactions.navigation-title") .navigationTitle("settings.swipeactions.navigation-title")
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
} }
private func createStatusActionPicker(selection: Binding<StatusAction>, label: LocalizedStringKey) -> some View { private func createStatusActionPicker(selection: Binding<StatusAction>, label: LocalizedStringKey) -> some View {

View file

@ -21,8 +21,10 @@ struct TranslationSettingsView: View {
.onAppear { .onAppear {
readValue() readValue()
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
if apiKey.isEmpty { if apiKey.isEmpty {
Section { Section {
Link(destination: URL(string: "https://www.deepl.com/pro-api")!) { Link(destination: URL(string: "https://www.deepl.com/pro-api")!) {
@ -30,14 +32,18 @@ struct TranslationSettingsView: View {
.foregroundColor(.red) .foregroundColor(.red)
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
autoDetectSection autoDetectSection
} }
.navigationTitle("settings.translation.navigation-title") .navigationTitle("settings.translation.navigation-title")
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.onChange(of: apiKey) { .onChange(of: apiKey) {
writeNewValue() writeNewValue()
} }
@ -50,7 +56,9 @@ struct TranslationSettingsView: View {
Toggle(isOn: $preferences.alwaysUseDeepl) { Toggle(isOn: $preferences.alwaysUseDeepl) {
Text("settings.translation.always-deepl") Text("settings.translation.always-deepl")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
@ViewBuilder @ViewBuilder

View file

@ -20,8 +20,11 @@ enum Tab: Int, Identifiable, Hashable {
} }
static func loggedInTabs() -> [Tab] { 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] [.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 { } else {
[.timeline, .notifications, .explore, .messages, .profile] [.timeline, .notifications, .explore, .messages, .profile]
} }

View file

@ -57,9 +57,11 @@ struct EditTagGroupView: View {
: "timeline.filter.edit-tag-groups" : "timeline.filter.edit-tag-groups"
) )
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.scrollDismissesKeyboard(.immediately) .scrollDismissesKeyboard(.immediately)
#endif
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
Button("action.cancel", action: { dismiss() }) Button("action.cancel", action: { dismiss() })

View file

@ -52,9 +52,11 @@ struct AddRemoteTimelineView: View {
.formStyle(.grouped) .formStyle(.grouped)
.navigationTitle("timeline.add-remote.title") .navigationTitle("timeline.add-remote.title")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.scrollDismissesKeyboard(.immediately) .scrollDismissesKeyboard(.immediately)
#endif
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
Button("action.cancel", action: { dismiss() }) Button("action.cancel", action: { dismiss() })

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

View file

@ -0,0 +1,13 @@
{
"images" : [
{
"filename" : "1024.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,14 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.solidimagestacklayer"
},
{
"filename" : "Back.solidimagestacklayer"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

View file

@ -0,0 +1,13 @@
{
"images" : [
{
"filename" : "1024.png",
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -74,7 +74,9 @@ struct AccountDetailHeaderView: View {
.frame(height: Constants.headerHeight) .frame(height: Constants.headerHeight)
} }
} }
#if !os(visionOS)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.frame(height: Constants.headerHeight) .frame(height: Constants.headerHeight)
.onTapGesture { .onTapGesture {
guard account.haveHeader else { guard account.haveHeader else {
@ -320,7 +322,9 @@ struct AccountDetailHeaderView: View {
Text(note) Text(note)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(8) .padding(8)
#if !os(visionOS)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.cornerRadius(4) .cornerRadius(4)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 4)
@ -370,7 +374,11 @@ struct AccountDetailHeaderView: View {
.padding(8) .padding(8)
.accessibilityElement(children: .contain) .accessibilityElement(children: .contain)
.accessibilityLabel("accessibility.tabs.profile.fields.container.label") .accessibilityLabel("accessibility.tabs.profile.fields.container.label")
#if os(visionOS)
.background(Material.thick)
#else
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.cornerRadius(4) .cornerRadius(4)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 4)

View file

@ -84,8 +84,10 @@ public struct AccountDetailView: View {
} }
.environment(\.defaultMinListRowHeight, 1) .environment(\.defaultMinListRowHeight, 1)
.listStyle(.plain) .listStyle(.plain)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.onChange(of: scrollToTopSignal) { .onChange(of: scrollToTopSignal) {
withAnimation { withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top) proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
@ -216,7 +218,9 @@ public struct AccountDetailView: View {
.padding(.leading, -4) .padding(.leading, -4)
.accessibilityLabel(account.safeDisplayName) .accessibilityLabel(account.safeDisplayName)
}.accessibilityAddTraits(.isImage) }
.accessibilityAddTraits(.isImage)
.buttonStyle(.plain)
} }
} }
.padding(.leading, .layoutPadding + 4) .padding(.leading, .layoutPadding + 4)
@ -235,7 +239,9 @@ public struct AccountDetailView: View {
Spacer() Spacer()
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
}.task { }.task {
await currentAccount.fetchFollowedTags() await currentAccount.fetchFollowedTags()
@ -250,7 +256,9 @@ public struct AccountDetailView: View {
.font(.scaledHeadline) .font(.scaledHeadline)
.foregroundColor(theme.labelColor) .foregroundColor(theme.labelColor)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.contextMenu { .contextMenu {
Button("account.list.delete", role: .destructive) { Button("account.list.delete", role: .destructive) {
Task { Task {
@ -264,7 +272,9 @@ public struct AccountDetailView: View {
} }
.tint(theme.tintColor) .tint(theme.tintColor)
.buttonStyle(.borderless) .buttonStyle(.borderless)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.task { .task {
await currentAccount.fetchLists() await currentAccount.fetchLists()
@ -284,7 +294,9 @@ public struct AccountDetailView: View {
bottom: 0, bottom: 0,
trailing: .layoutPadding)) trailing: .layoutPadding))
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
ForEach(viewModel.pinned) { status in ForEach(viewModel.pinned) { status in
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath)) StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
} }
@ -403,7 +415,9 @@ extension View {
func applyAccountDetailsRowStyle(theme: Theme) -> some View { func applyAccountDetailsRowStyle(theme: Theme) -> some View {
listRowInsets(.init()) listRowInsets(.init())
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }

View file

@ -26,7 +26,9 @@ public struct AccountsListView: View {
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.allowsHitTesting(false) .allowsHitTesting(false)
.shimmering() .shimmering()
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
case let .display(accounts, relationships, nextPageState): case let .display(accounts, relationships, nextPageState):
if case .followers = viewModel.mode, if case .followers = viewModel.mode,
@ -49,7 +51,9 @@ public struct AccountsListView: View {
} }
} }
) )
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
} }
@ -58,7 +62,9 @@ public struct AccountsListView: View {
if let relationship = relationships.first(where: { $0.id == account.id }) { if let relationship = relationships.first(where: { $0.id == account.id }) {
AccountsListRow(viewModel: .init(account: account, AccountsListRow(viewModel: .init(account: account,
relationShip: relationship)) relationShip: relationship))
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
} }
@ -66,7 +72,9 @@ public struct AccountsListView: View {
switch nextPageState { switch nextPageState {
case .hasNextPage: case .hasNextPage:
loadingRow loadingRow
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.onAppear { .onAppear {
Task { Task {
await viewModel.fetchNextPage() await viewModel.fetchNextPage()
@ -75,18 +83,24 @@ public struct AccountsListView: View {
case .loadingNextPage: case .loadingNextPage:
loadingRow loadingRow
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
case .none: case .none:
EmptyView() EmptyView()
} }
case let .error(error): case let .error(error):
Text(error.localizedDescription) Text(error.localizedDescription)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.listStyle(.plain) .listStyle(.plain)
.navigationTitle(viewModel.mode.title) .navigationTitle(viewModel.mode.title)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)

View file

@ -28,9 +28,11 @@ public struct EditAccountView: View {
} }
} }
.environment(\.editMode, .constant(.active)) .environment(\.editMode, .constant(.active))
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.scrollDismissesKeyboard(.immediately) .scrollDismissesKeyboard(.immediately)
#endif
.navigationTitle("account.edit.navigation-title") .navigationTitle("account.edit.navigation-title")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
@ -56,7 +58,9 @@ public struct EditAccountView: View {
Spacer() Spacer()
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
@ViewBuilder @ViewBuilder
@ -69,7 +73,9 @@ public struct EditAccountView: View {
TextField("account.edit.about", text: $viewModel.note, axis: .vertical) TextField("account.edit.about", text: $viewModel.note, axis: .vertical)
.frame(maxHeight: 150) .frame(maxHeight: 150)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var postSettingsSection: some View { private var postSettingsSection: some View {
@ -89,7 +95,9 @@ public struct EditAccountView: View {
Label("account.edit.post-settings.sensitive", systemImage: "eye") Label("account.edit.post-settings.sensitive", systemImage: "eye")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var accountSection: some View { private var accountSection: some View {
@ -104,7 +112,9 @@ public struct EditAccountView: View {
Label("account.edit.account-settings.discoverable", systemImage: "magnifyingglass") Label("account.edit.account-settings.discoverable", systemImage: "magnifyingglass")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var fieldsSection: some View { private var fieldsSection: some View {
@ -138,7 +148,9 @@ public struct EditAccountView: View {
} }
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
@ToolbarContentBuilder @ToolbarContentBuilder

View file

@ -18,10 +18,14 @@ public struct EditRelationshipNoteView: View {
TextField("account.relation.note.edit.placeholder", text: $viewModel.note, axis: .vertical) TextField("account.relation.note.edit.placeholder", text: $viewModel.note, axis: .vertical)
.frame(minHeight: 150, maxHeight: 150, alignment: .top) .frame(minHeight: 150, maxHeight: 150, alignment: .top)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.navigationTitle("account.relation.note.edit") .navigationTitle("account.relation.note.edit")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {

View file

@ -70,9 +70,11 @@ struct EditFilterView: View {
} }
.navigationTitle(filter?.title ?? NSLocalizedString("filter.new", comment: "")) .navigationTitle(filter?.title ?? NSLocalizedString("filter.new", comment: ""))
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.scrollDismissesKeyboard(.interactively) .scrollDismissesKeyboard(.interactively)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.onAppear { .onAppear {
if filter == nil { if filter == nil {
focusedField = .title focusedField = .title
@ -104,7 +106,9 @@ struct EditFilterView: View {
.disabled(expirySelection != .custom) .disabled(expirySelection != .custom)
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
@ViewBuilder @ViewBuilder
@ -118,7 +122,9 @@ struct EditFilterView: View {
} }
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
if filter == nil, !title.isEmpty { if filter == nil, !title.isEmpty {
Section { Section {
@ -138,7 +144,9 @@ struct EditFilterView: View {
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.transition(.opacity) .transition(.opacity)
} }
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
} }
} }
@ -192,7 +200,9 @@ struct EditFilterView: View {
} }
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var contextsSection: some View { private var contextsSection: some View {
@ -214,7 +224,9 @@ struct EditFilterView: View {
} }
.disabled(isSavingFilter) .disabled(isSavingFilter)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
@ -235,7 +247,9 @@ struct EditFilterView: View {
} }
.pickerStyle(.inline) .pickerStyle(.inline)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var saveButton: some View { private var saveButton: some View {

View file

@ -54,7 +54,9 @@ public struct FiltersListView: View {
} }
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
Section { Section {
@ -62,15 +64,19 @@ public struct FiltersListView: View {
Label("filter.new", systemImage: "plus") Label("filter.new", systemImage: "plus")
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
.toolbar { .toolbar {
toolbarContent toolbarContent
} }
.navigationTitle("filter.filters") .navigationTitle("filter.filters")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.task { .task {
do { do {
isLoading = true isLoading = true

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -96,7 +96,9 @@ public struct AppAccountsSelectorView: View {
AppAccountView(viewModel: viewModel) AppAccountView(viewModel: viewModel)
} }
} }
.listRowBackground(theme.primaryBackgroundColor) #if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
if accountCreationEnabled { if accountCreationEnabled {
Section { Section {
@ -111,7 +113,9 @@ public struct AppAccountsSelectorView: View {
} }
settingsButton settingsButton
} }
.listRowBackground(theme.primaryBackgroundColor) #if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
.listStyle(.insetGrouped) .listStyle(.insetGrouped)

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -46,7 +46,9 @@ public struct ConversationDetailView: View {
} }
.padding(.horizontal, .layoutPadding) .padding(.horizontal, .layoutPadding)
} }
#if !os(visionOS)
.scrollDismissesKeyboard(.interactively) .scrollDismissesKeyboard(.interactively)
#endif
.safeAreaInset(edge: .bottom) { .safeAreaInset(edge: .bottom) {
inputTextView inputTextView
} }
@ -68,8 +70,10 @@ public struct ConversationDetailView: View {
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.toolbar { .toolbar {
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
if viewModel.conversation.accounts.count == 1, if viewModel.conversation.accounts.count == 1,

View file

@ -43,7 +43,11 @@ struct ConversationMessageView: View {
routerPath.handleStatus(status: message, url: url) 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) .background(isOwnMessage ? theme.tintColor.opacity(0.2) : theme.secondaryBackgroundColor)
#endif
.cornerRadius(8) .cornerRadius(8)
.padding(.leading, isOwnMessage ? 24 : 0) .padding(.leading, isOwnMessage ? 24 : 0)
.padding(.trailing, isOwnMessage ? 0 : 24) .padding(.trailing, isOwnMessage ? 0 : 24)

View file

@ -1,4 +1,3 @@
import Accounts
import DesignSystem import DesignSystem
import Env import Env
import Models import Models
@ -86,6 +85,8 @@ struct ConversationsListRow: View {
} }
} }
} }
.buttonStyle(.plain)
.hoverEffectDisabled()
} }
private var actionsView: some View { private var actionsView: some View {

View file

@ -81,8 +81,10 @@ public struct ConversationsListView: View {
} }
.padding(.top, .layoutPadding) .padding(.top, .layoutPadding)
} }
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.navigationTitle("conversations.navigation-title") .navigationTitle("conversations.navigation-title")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -4,8 +4,13 @@ import UIKit
@Observable @Observable
public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable { public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
public var window: UIWindow? 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 windowWidth: CGFloat = UIScreen.main.bounds.size.width
public private(set) var windowHeight: CGFloat = UIScreen.main.bounds.size.height public private(set) var windowHeight: CGFloat = UIScreen.main.bounds.size.height
#endif
public func scene(_ scene: UIScene, public func scene(_ scene: UIScene,
willConnectTo _: UISceneSession, willConnectTo _: UISceneSession,
@ -24,8 +29,13 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
override public init() { override public init() {
super.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 windowWidth = window?.bounds.size.width ?? UIScreen.main.bounds.size.width
windowHeight = window?.bounds.size.height ?? UIScreen.main.bounds.size.height windowHeight = window?.bounds.size.height ?? UIScreen.main.bounds.size.height
#endif
Self.observedSceneDelegate.insert(self) Self.observedSceneDelegate.insert(self)
_ = Self.observer // just for activating the lazy static property _ = Self.observer // just for activating the lazy static property
} }
@ -41,15 +51,26 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
while true { while true {
try? await Task.sleep(for: .seconds(0.1)) try? await Task.sleep(for: .seconds(0.1))
for delegate in observedSceneDelegate { 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 let newWidth = delegate.window?.bounds.size.width ?? UIScreen.main.bounds.size.width
if delegate.windowWidth != newWidth { if delegate.windowWidth != newWidth {
delegate.windowWidth = newWidth delegate.windowWidth = newWidth
} }
let newHeight = delegate.window?.bounds.size.height ?? UIScreen.main.bounds.size.height let newHeight = delegate.window?.bounds.size.height ?? UIScreen.main.bounds.size.height
if delegate.windowHeight != newHeight { if delegate.windowHeight != newHeight {
delegate.windowHeight = newHeight delegate.windowHeight = newHeight
} }
#endif
} }
} }
} }

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -5,27 +5,42 @@ import UIKit
public class HapticManager { public class HapticManager {
public static let shared: HapticManager = .init() public static let shared: HapticManager = .init()
#if os(visionOS)
public enum FeedbackType: Int {
case success, warning, error
}
#endif
public enum HapticType { public enum HapticType {
case buttonPress case buttonPress
case dataRefresh(intensity: CGFloat) case dataRefresh(intensity: CGFloat)
#if os(visionOS)
case notification(_ type: FeedbackType)
#else
case notification(_ type: UINotificationFeedbackGenerator.FeedbackType) case notification(_ type: UINotificationFeedbackGenerator.FeedbackType)
#endif
case tabSelection case tabSelection
case timeline case timeline
} }
#if !os(visionOS)
private let selectionGenerator = UISelectionFeedbackGenerator() private let selectionGenerator = UISelectionFeedbackGenerator()
private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy) private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy)
private let notificationGenerator = UINotificationFeedbackGenerator() private let notificationGenerator = UINotificationFeedbackGenerator()
#endif
private let userPreferences = UserPreferences.shared private let userPreferences = UserPreferences.shared
private init() { private init() {
#if !os(visionOS)
selectionGenerator.prepare() selectionGenerator.prepare()
impactGenerator.prepare() impactGenerator.prepare()
#endif
} }
@MainActor @MainActor
public func fireHaptic(_ type: HapticType) { public func fireHaptic(_ type: HapticType) {
#if !os(visionOS)
guard supportsHaptics else { return } guard supportsHaptics else { return }
switch type { switch type {
@ -50,6 +65,7 @@ public class HapticManager {
selectionGenerator.selectionChanged() selectionGenerator.selectionChanged()
} }
} }
#endif
} }
public var supportsHaptics: Bool { public var supportsHaptics: Bool {

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -47,7 +47,9 @@ public struct ExploreView: View {
ProgressView() ProgressView()
Spacer() Spacer()
} }
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.id(UUID()) .id(UUID())
} }
@ -55,7 +57,9 @@ public struct ExploreView: View {
EmptyView(iconName: "magnifyingglass", EmptyView(iconName: "magnifyingglass",
title: "explore.search.title", title: "explore.search.title",
message: "explore.search.message-\(client.server)") message: "explore.search.message-\(client.server)")
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} else { } else {
quickAccessView quickAccessView
@ -90,8 +94,10 @@ public struct ExploreView: View {
} }
} }
.listStyle(.plain) .listStyle(.plain)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
#endif
.navigationTitle("explore.navigation-title") .navigationTitle("explore.navigation-title")
.searchable(text: $viewModel.searchQuery, .searchable(text: $viewModel.searchQuery,
isPresented: $viewModel.isSearchPresented, isPresented: $viewModel.isSearchPresented,
@ -140,7 +146,9 @@ public struct ExploreView: View {
} }
.scrollIndicators(.never) .scrollIndicators(.never)
.listRowInsets(EdgeInsets()) .listRowInsets(EdgeInsets())
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} }
@ -150,7 +158,9 @@ public struct ExploreView: View {
.padding(.vertical, 8) .padding(.vertical, 8)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.allowsHitTesting(false) .allowsHitTesting(false)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
@ -161,7 +171,9 @@ public struct ExploreView: View {
ForEach(results.accounts) { account in ForEach(results.accounts) { account in
if let relationship = results.relationships.first(where: { $0.id == account.id }) { if let relationship = results.relationships.first(where: { $0.id == account.id }) {
AccountsListRow(viewModel: .init(account: account, relationShip: relationship)) AccountsListRow(viewModel: .init(account: account, relationShip: relationship))
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
} }
@ -170,7 +182,9 @@ public struct ExploreView: View {
Section("explore.section.tags") { Section("explore.section.tags") {
ForEach(results.hashtags) { tag in ForEach(results.hashtags) { tag in
TagRowView(tag: tag) TagRowView(tag: tag)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.padding(.vertical, 4) .padding(.vertical, 4)
} }
} }
@ -179,7 +193,9 @@ public struct ExploreView: View {
Section("explore.section.posts") { Section("explore.section.posts") {
ForEach(results.statuses) { status in ForEach(results.statuses) { status in
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath)) StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.padding(.vertical, 8) .padding(.vertical, 8)
} }
} }
@ -193,14 +209,18 @@ public struct ExploreView: View {
{ account in { account in
if let relationship = viewModel.suggestedAccountsRelationShips.first(where: { $0.id == account.id }) { if let relationship = viewModel.suggestedAccountsRelationShips.first(where: { $0.id == account.id }) {
AccountsListRow(viewModel: .init(account: account, relationShip: relationship)) AccountsListRow(viewModel: .init(account: account, relationShip: relationship))
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
NavigationLink(value: RouterDestination.accountsList(accounts: viewModel.suggestedAccounts)) { NavigationLink(value: RouterDestination.accountsList(accounts: viewModel.suggestedAccounts)) {
Text("see-more") Text("see-more")
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
@ -210,14 +230,18 @@ public struct ExploreView: View {
.prefix(upTo: viewModel.trendingTags.count > 5 ? 5 : viewModel.trendingTags.count)) .prefix(upTo: viewModel.trendingTags.count > 5 ? 5 : viewModel.trendingTags.count))
{ tag in { tag in
TagRowView(tag: tag) TagRowView(tag: tag)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.padding(.vertical, 4) .padding(.vertical, 4)
} }
NavigationLink(value: RouterDestination.tagsList(tags: viewModel.trendingTags)) { NavigationLink(value: RouterDestination.tagsList(tags: viewModel.trendingTags)) {
Text("see-more") Text("see-more")
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
@ -227,7 +251,9 @@ public struct ExploreView: View {
.prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) .prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count))
{ status in { status in
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath)) StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.padding(.vertical, 8) .padding(.vertical, 8)
} }
@ -235,7 +261,9 @@ public struct ExploreView: View {
Text("see-more") Text("see-more")
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
@ -245,7 +273,9 @@ public struct ExploreView: View {
.prefix(upTo: viewModel.trendingLinks.count > 3 ? 3 : viewModel.trendingLinks.count)) .prefix(upTo: viewModel.trendingLinks.count > 3 ? 3 : viewModel.trendingLinks.count))
{ card in { card in
StatusRowCardView(card: card) StatusRowCardView(card: card)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.padding(.vertical, 8) .padding(.vertical, 8)
} }
@ -253,7 +283,9 @@ public struct ExploreView: View {
Text("see-more") Text("see-more")
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -65,7 +65,9 @@ public struct ListEditView: View {
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
.disabled(viewModel.isUpdating) .disabled(viewModel.isUpdating)
} }
#if !os(visionOS)
.scrollDismissesKeyboard(.immediately) .scrollDismissesKeyboard(.immediately)
#endif
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.toolbar { .toolbar {

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -83,8 +83,10 @@ public struct NotificationsListView: View {
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.task { .task {
viewModel.client = client viewModel.client = client
viewModel.currentAccount = account viewModel.currentAccount = account
@ -130,7 +132,12 @@ public struct NotificationsListView: View {
leading: .layoutPadding + 4, leading: .layoutPadding + 4,
bottom: 12, bottom: 12,
trailing: .layoutPadding)) trailing: .layoutPadding))
.listRowBackground(theme.primaryBackgroundColor) #if os(visionOS)
.listRowBackground(RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Material.regular))
#else
.listRowBackground(theme.primaryBackgroundColor)
#endif
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.allowsHitTesting(false) .allowsHitTesting(false)
} }
@ -140,7 +147,9 @@ public struct NotificationsListView: View {
EmptyView(iconName: "bell.slash", EmptyView(iconName: "bell.slash",
title: "notifications.empty.title", title: "notifications.empty.title",
message: "notifications.empty.message") message: "notifications.empty.message")
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.listSectionSeparator(.hidden) .listSectionSeparator(.hidden)
} else { } else {
ForEach(notifications) { notification in ForEach(notifications) { notification in
@ -152,8 +161,13 @@ public struct NotificationsListView: View {
leading: .layoutPadding + 4, leading: .layoutPadding + 4,
bottom: 12, bottom: 12,
trailing: .layoutPadding)) 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 ? .listRowBackground(notification.type == .mention && lockedType != .mention ?
theme.secondaryBackgroundColor : theme.primaryBackgroundColor) theme.secondaryBackgroundColor : theme.primaryBackgroundColor)
#endif
.id(notification.id) .id(notification.id)
} }
} }
@ -181,7 +195,9 @@ public struct NotificationsListView: View {
await viewModel.fetchNotifications() await viewModel.fetchNotifications()
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.listSectionSeparator(.hidden) .listSectionSeparator(.hidden)
} }
} }
@ -196,7 +212,9 @@ public struct NotificationsListView: View {
leading: .layoutPadding + 4, leading: .layoutPadding + 4,
bottom: .layoutPadding, bottom: .layoutPadding,
trailing: .layoutPadding)) trailing: .layoutPadding))
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
} }
private var topPaddingView: some View { private var topPaddingView: some View {

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -59,7 +59,9 @@ public struct StatusDetailView: View {
.foregroundColor(theme.secondaryBackgroundColor) .foregroundColor(theme.secondaryBackgroundColor)
.frame(minHeight: reader.frame(in: .local).size.height - statusHeight) .frame(minHeight: reader.frame(in: .local).size.height - statusHeight)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
.listRowInsets(.init()) .listRowInsets(.init())
.accessibilityHidden(true) .accessibilityHidden(true)
@ -69,8 +71,10 @@ public struct StatusDetailView: View {
} }
.environment(\.defaultMinListRowHeight, 1) .environment(\.defaultMinListRowHeight, 1)
.listStyle(.plain) .listStyle(.plain)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.onChange(of: viewModel.scrollToId) { _, newValue in .onChange(of: viewModel.scrollToId) { _, newValue in
if let newValue { if let newValue {
viewModel.scrollToId = nil viewModel.scrollToId = nil
@ -132,7 +136,9 @@ public struct StatusDetailView: View {
} }
} }
.id(status.id) .id(status.id)
#if !os(visionOS)
.listRowBackground(viewModel.highlightRowColor) .listRowBackground(viewModel.highlightRowColor)
#endif
.listRowInsets(.init(top: 12, .listRowInsets(.init(top: 12,
leading: .layoutPadding, leading: .layoutPadding,
bottom: 12, bottom: 12,
@ -149,7 +155,9 @@ public struct StatusDetailView: View {
await viewModel.fetch() await viewModel.fetch()
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} }
@ -169,13 +177,17 @@ public struct StatusDetailView: View {
} }
.frame(height: 50) .frame(height: 50)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
.listRowInsets(.init()) .listRowInsets(.init())
} }
private var topPaddingView: some View { private var topPaddingView: some View {
HStack { EmptyView() } HStack { EmptyView() }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowInsets(.init()) .listRowInsets(.init())
.frame(height: .layoutPadding) .frame(height: .layoutPadding)

View file

@ -1,3 +1,4 @@
#if !os(visionOS)
import DesignSystem import DesignSystem
import GiphyUISDK import GiphyUISDK
import SwiftUI import SwiftUI
@ -49,3 +50,4 @@ struct GifPickerView: UIViewControllerRepresentable {
} }
} }
} }
#endif

View file

@ -1,6 +1,8 @@
import DesignSystem import DesignSystem
import Env import Env
#if !os(visionOS)
import GiphyUISDK import GiphyUISDK
#endif
import Models import Models
import NukeUI import NukeUI
import PhotosUI import PhotosUI
@ -29,186 +31,220 @@ struct StatusEditorAccessoryView: View {
var body: some View { var body: some View {
@Bindable var viewModel = focusedSEVM @Bindable var viewModel = focusedSEVM
VStack(spacing: 0) { VStack(spacing: 0) {
#if os(visionOS)
HStack {
contentView
}
.frame(height: 24)
.padding(16)
.background(.ultraThinMaterial)
.cornerRadius(8)
#else
Divider() Divider()
HStack { HStack {
ScrollView(.horizontal) { contentView
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)
} }
.frame(height: 20) .frame(height: 20)
.padding(.vertical, 12) .padding(.vertical, 12)
.background(.ultraThinMaterial) .background(.ultraThinMaterial)
#endif
} }
.onAppear { .onAppear {
viewModel.setInitialLanguageSelection(preference: preferences.recentlyUsedLanguages.first ?? preferences.serverPreferences?.postLanguage) 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 { private var canAddNewSEVM: Bool {
guard followUpSEVMs.count < 5 else { return false } guard followUpSEVMs.count < 5 else { return false }

View file

@ -21,7 +21,9 @@ struct StatusEditorCameraPickerView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIImagePickerController { func makeUIViewController(context: Context) -> UIImagePickerController {
let imagePicker = UIImagePickerController() let imagePicker = UIImagePickerController()
#if !os(visionOS)
imagePicker.sourceType = .camera imagePicker.sourceType = .camera
#endif
imagePicker.delegate = context.coordinator imagePicker.delegate = context.coordinator
return imagePicker return imagePicker
} }

View file

@ -1,4 +1,3 @@
import Accounts
import AppAccount import AppAccount
import DesignSystem import DesignSystem
import Env import Env
@ -53,7 +52,9 @@ struct StatusEditorCoreView: View {
} }
.opacity(editorFocusState == assignedFocusState ? 1 : 0.6) .opacity(editorFocusState == assignedFocusState ? 1 : 0.6)
} }
#if !os(visionOS)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.focused($editorFocusState, equals: assignedFocusState) .focused($editorFocusState, equals: assignedFocusState)
.onAppear { setupViewModel() } .onAppear { setupViewModel() }
} }

View file

@ -1,4 +1,3 @@
import Accounts
import AppAccount import AppAccount
import DesignSystem import DesignSystem
import EmojiText import EmojiText
@ -74,13 +73,21 @@ public struct StatusEditorView: View {
.scrollPosition(id: $scrollID, anchor: .top) .scrollPosition(id: $scrollID, anchor: .top)
.animation(.bouncy(duration: 0.3), value: editorFocusState) .animation(.bouncy(duration: 0.3), value: editorFocusState)
.animation(.bouncy(duration: 0.3), value: followUpSEVMs) .animation(.bouncy(duration: 0.3), value: followUpSEVMs)
#if !os(visionOS)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.safeAreaInset(edge: .bottom) { .safeAreaInset(edge: .bottom) {
StatusEditorAutoCompleteView(viewModel: focusedSEVM) StatusEditorAutoCompleteView(viewModel: focusedSEVM)
} }
#if os(visionOS)
.ornament(attachmentAnchor: .scene(.bottom)) {
StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused, focusedSEVM: focusedSEVM, followUpSEVMs: $followUpSEVMs)
}
#else
.safeAreaInset(edge: .bottom) { .safeAreaInset(edge: .bottom) {
StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused, focusedSEVM: focusedSEVM, followUpSEVMs: $followUpSEVMs) StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused, focusedSEVM: focusedSEVM, followUpSEVMs: $followUpSEVMs)
} }
#endif
.accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views .accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views
.navigationTitle(focusedSEVM.mode.title) .navigationTitle(focusedSEVM.mode.title)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)

View file

@ -149,7 +149,12 @@ public struct StatusRowView: View {
StatusRowSwipeView(viewModel: viewModel, mode: .leading) 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, .listRowInsets(.init(top: 12,
leading: .layoutPadding, leading: .layoutPadding,
bottom: 12, bottom: 12,

View file

@ -22,11 +22,13 @@ public struct StatusRowMediaPreviewView: View {
@State private var isQuickLookLoading: Bool = false @State private var isQuickLookLoading: Bool = false
var availableWidth: CGFloat { var availableWidth: CGFloat {
#if !os(visionOS)
if UIDevice.current.userInterfaceIdiom == .phone && if UIDevice.current.userInterfaceIdiom == .phone &&
(UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) || theme.statusDisplayStyle == .medium (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) || theme.statusDisplayStyle == .medium
{ {
return sceneDelegate.windowWidth * 0.80 return sceneDelegate.windowWidth * 0.80
} }
#endif
return sceneDelegate.windowWidth return sceneDelegate.windowWidth
} }

View file

@ -8,6 +8,7 @@ let package = Package(
defaultLocalization: "en", defaultLocalization: "en",
platforms: [ platforms: [
.iOS(.v17), .iOS(.v17),
.visionOS(.v1),
], ],
products: [ products: [
.library( .library(

View file

@ -60,8 +60,10 @@ public struct TimelineView: View {
.id(client.id) .id(client.id)
.environment(\.defaultMinListRowHeight, 1) .environment(\.defaultMinListRowHeight, 1)
.listStyle(.plain) .listStyle(.plain)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.introspect(.list, on: .iOS(.v17)) { (collectionView: UICollectionView) in .introspect(.list, on: .iOS(.v17)) { (collectionView: UICollectionView) in
DispatchQueue.main.async { DispatchQueue.main.async {
self.collectionView = collectionView self.collectionView = collectionView