Initial macOS Catalyst support

This commit is contained in:
Thomas Ricouard 2023-10-23 19:12:25 +02:00
parent b257bfc576
commit 07bfd8cd0e
42 changed files with 376 additions and 511 deletions

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View file

@ -75,7 +75,6 @@
9FAD85A0297456A100496AB1 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD859F297456A100496AB1 /* Models */; };
9FAD85A2297456A400496AB1 /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD85A1297456A400496AB1 /* Env */; };
9FAD85A4297456A800496AB1 /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD85A3297456A800496AB1 /* DesignSystem */; };
9FAD85A8297582F100496AB1 /* QuickLookRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD85A7297582F100496AB1 /* QuickLookRepresentable.swift */; };
9FAD85CF2975B68900496AB1 /* SideBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD85CE2975B68900496AB1 /* SideBarView.swift */; };
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4ACA293783B000772766 /* SettingsTab.swift */; };
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAE4ACD29379A5A00772766 /* KeychainSwift */; };
@ -103,7 +102,7 @@
E9DF41FC29830FEC0003AAD2 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */; };
E9DF420129830FEC0003AAD2 /* ActionRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */; };
E9DF420329830FEC0003AAD2 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = E9DF420229830FEC0003AAD2 /* Action.js */; };
E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
FA31A9AB2A66BF7C00D5F662 /* EditTagGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA31A9AA2A66BF7C00D5F662 /* EditTagGroupView.swift */; };
FAD203D02A66D8A80030A7FD /* Symbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD203CF2A66D8A80030A7FD /* Symbols.swift */; };
/* End PBXBuildFile section */
@ -212,13 +211,13 @@
9FAD858A29743F7400496AB1 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
9FAD858F29743F7400496AB1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9FAD859629743F7E00496AB1 /* IceCubesShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesShareExtension.entitlements; sourceTree = "<group>"; };
9FAD85A7297582F100496AB1 /* QuickLookRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLookRepresentable.swift; sourceTree = "<group>"; };
9FAD85CE2975B68900496AB1 /* SideBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarView.swift; sourceTree = "<group>"; };
9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = "<group>"; };
9FBFE639292A715500C250E9 /* IceCubesApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IceCubesApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
9FBFE63C292A715500C250E9 /* IceCubesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IceCubesApp.swift; sourceTree = "<group>"; };
9FBFE642292A715600C250E9 /* IceCubesApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesApp.entitlements; sourceTree = "<group>"; };
9FC60CB82AE6C2F600C6EAD2 /* IceCubesActionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesActionExtension.entitlements; sourceTree = "<group>"; };
9FCBB3D22984EFD5009B77EE /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
9FCBB3D429859615009B77EE /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/InfoPlist.strings; sourceTree = "<group>"; };
9FD34822293D06E800DB0EE9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -343,7 +342,6 @@
9FBFE63C292A715500C250E9 /* IceCubesApp.swift */,
9F398AA52935FE8A00A889F2 /* AppRegistry.swift */,
639CDF9B296AC82F00C35E58 /* SafariRouter.swift */,
9FAD85A7297582F100496AB1 /* QuickLookRepresentable.swift */,
9FAD85CE2975B68900496AB1 /* SideBarView.swift */,
);
path = App;
@ -517,6 +515,7 @@
E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */ = {
isa = PBXGroup;
children = (
9FC60CB82AE6C2F600C6EAD2 /* IceCubesActionExtension.entitlements */,
E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */,
E9DF420229830FEC0003AAD2 /* Action.js */,
E9DF420429830FEC0003AAD2 /* Info.plist */,
@ -818,7 +817,6 @@
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */,
9F1E8B47298EBCBB00609F80 /* HapticSettingsView.swift in Sources */,
9F2A5411296A1429009B2D7C /* PushNotificationsView.swift in Sources */,
9FAD85A8297582F100496AB1 /* QuickLookRepresentable.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -847,6 +845,7 @@
};
E9DF420629830FEC0003AAD2 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilter = ios;
target = E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */;
targetProxy = E9DF420529830FEC0003AAD2 /* PBXContainerItemProxy */;
};
@ -881,6 +880,7 @@
CODE_SIGN_ENTITLEMENTS = IceCubesNotifications/IceCubesNotifications.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 730;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
@ -899,6 +899,9 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -911,6 +914,7 @@
CODE_SIGN_ENTITLEMENTS = IceCubesNotifications/IceCubesNotifications.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 730;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
@ -929,6 +933,9 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -942,6 +949,7 @@
CODE_SIGN_ENTITLEMENTS = IceCubesShareExtension/IceCubesShareExtension.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 730;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
@ -960,6 +968,9 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -972,6 +983,7 @@
CODE_SIGN_ENTITLEMENTS = IceCubesShareExtension/IceCubesShareExtension.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 730;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
@ -990,6 +1002,9 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -1147,6 +1162,7 @@
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
INFOPLIST_KEY_NSCameraUsageDescription = "Upload photos & videos to Mastodon";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Upload photos & videos to Mastodon";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@ -1166,8 +1182,8 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
@ -1200,6 +1216,7 @@
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
INFOPLIST_KEY_NSCameraUsageDescription = "Upload photos & videos to Mastodon";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Upload photos & videos to Mastodon";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@ -1219,8 +1236,8 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
@ -1232,6 +1249,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcon;
CODE_SIGN_ENTITLEMENTS = IceCubesActionExtension/IceCubesActionExtension.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
@ -1253,6 +1271,9 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -1263,6 +1284,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcon;
CODE_SIGN_ENTITLEMENTS = IceCubesActionExtension/IceCubesActionExtension.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
@ -1284,6 +1306,9 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";

View file

@ -180,3 +180,9 @@ struct ActivityView: UIViewControllerRepresentable {
func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}
}
extension URL: Identifiable {
public var id: String {
absoluteString
}
}

View file

@ -37,6 +37,11 @@ struct IceCubesApp: App {
}
var body: some Scene {
appScene
otherScenes
}
private var appScene: some Scene {
WindowGroup {
appView
.applyTheme(theme)
@ -60,14 +65,8 @@ struct IceCubesApp: App {
attachments: quickLook.mediaAttachments)
.presentationBackground(.ultraThinMaterial)
.presentationCornerRadius(16)
.environment(userPreferences)
.environment(theme)
.withEnvironments()
}
.fullScreenCover(item: $quickLook.url, content: { url in
QuickLookPreview(selectedURL: url, urls: quickLook.urls)
.edgesIgnoringSafeArea(.bottom)
.background(TransparentBackground())
})
.onChange(of: pushNotificationsService.handledNotification) { _, newValue in
if newValue != nil {
pushNotificationsService.handledNotification = nil
@ -99,6 +98,7 @@ struct IceCubesApp: App {
watcher.watch(streams: [.user, .direct])
}
}
}
@ViewBuilder
@ -208,6 +208,27 @@ struct IceCubesApp: App {
.id(appAccountsManager.currentClient.id)
}
private var otherScenes: some Scene {
WindowGroup(for: WindowDestination.self) { destination in
Group {
switch destination.wrappedValue {
case let .newStatusEditor(visibility):
StatusEditorView(mode: .new(visibility: visibility))
case let .mediaViewer(attachments, selectedAttachment):
MediaUIView(selectedAttachment: selectedAttachment,
attachments: attachments)
case .none:
EmptyView()
}
}
.withEnvironments()
.withModelContainer()
.applyTheme(theme)
}
.defaultSize(width: 600, height: 800)
.windowResizability(.automatic)
}
private func setNewClientsInEnv(client: Client) {
currentAccount.setClient(client: client)
currentInstance.setClient(client: client)

View file

@ -1,101 +0,0 @@
import QuickLook
import SwiftUI
import UIKit
extension URL: Identifiable {
public var id: String {
absoluteString
}
}
struct QuickLookPreview: UIViewControllerRepresentable {
let selectedURL: URL
let urls: [URL]
func makeUIViewController(context _: Context) -> UIViewController {
AppQLPreviewController(selectedURL: selectedURL, urls: urls)
}
func updateUIViewController(
_: UIViewController, context _: Context
) {}
}
@MainActor
class AppQLPreviewController: UIViewController {
let selectedURL: URL
let urls: [URL]
var qlController: QLPreviewController?
init(selectedURL: URL, urls: [URL]) {
self.selectedURL = selectedURL
self.urls = urls
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if qlController == nil {
qlController = QLPreviewController()
qlController?.dataSource = self
qlController?.delegate = self
qlController?.currentPreviewItemIndex = urls.firstIndex(of: selectedURL) ?? 0
present(qlController!, animated: true)
}
}
}
extension AppQLPreviewController: QLPreviewControllerDataSource {
nonisolated func numberOfPreviewItems(in _: QLPreviewController) -> Int {
urls.count
}
nonisolated func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
urls[index] as QLPreviewItem
}
}
extension AppQLPreviewController: QLPreviewControllerDelegate {
nonisolated func previewController(_: QLPreviewController, editingModeFor _: QLPreviewItem) -> QLPreviewItemEditingMode {
.createCopy
}
nonisolated func previewControllerWillDismiss(_: QLPreviewController) {
Task { @MainActor in
dismiss(animated: true)
}
}
nonisolated func previewControllerDidDismiss(_: QLPreviewController) {
Task { @MainActor in
dismiss(animated: true)
}
}
}
struct TransparentBackground: UIViewControllerRepresentable {
public func makeUIViewController(context _: Context) -> UIViewController {
TransparentController()
}
public func updateUIViewController(_: UIViewController, context _: Context) {}
class TransparentController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
}
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
parent?.view?.backgroundColor = .clear
parent?.modalPresentationStyle = .overCurrentContext
}
}
}

View file

@ -42,7 +42,7 @@ private struct SafariRouter: ViewModifier {
return .handled
}
}
guard preferences.preferredBrowser == .inAppSafari, !ProcessInfo.processInfo.isiOSAppOnMac else { return .systemAction }
guard preferences.preferredBrowser == .inAppSafari, !ProcessInfo.processInfo.isMacCatalystApp else { return .systemAction }
// SFSafariViewController only supports initial URLs with http:// or https:// schemes.
guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else {
return .systemAction

View file

@ -7,6 +7,8 @@ import SwiftUI
@MainActor
struct SideBarView<Content: View>: View {
@Environment(\.openWindow) private var openWindow
@Environment(AppAccountsManager.self) private var appAccounts
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Theme.self) private var theme
@ -55,7 +57,11 @@ struct SideBarView<Content: View>: View {
private var postButton: some View {
Button {
if ProcessInfo.processInfo.isMacCatalystApp {
openWindow(value: WindowDestination.newStatusEditor(visibility: userPreferences.postVisibility))
} else {
routerPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
}
} label: {
Image(systemName: "square.and.pencil")
.resizable()

View file

@ -169,7 +169,7 @@ struct SettingsTabs: View {
private var otherSections: some View {
@Bindable var preferences = preferences
Section("settings.section.other") {
if !ProcessInfo.processInfo.isiOSAppOnMac {
if !ProcessInfo.processInfo.isMacCatalystApp{
Picker(selection: $preferences.preferredBrowser) {
ForEach(PreferredBrowser.allCases, id: \.rawValue) { browser in
switch browser {
@ -202,7 +202,7 @@ struct SettingsTabs: View {
private var appSection: some View {
Section {
if !ProcessInfo.processInfo.isiOSAppOnMac {
if !ProcessInfo.processInfo.isMacCatalystApp {
NavigationLink(destination: IconSelectorView()) {
Label {
Text("settings.app.icon")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

0
IceCubesApp/Assets.xcassets/AppIcon.appiconset/60.png Executable file → Normal file
View file

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

View file

@ -1,284 +1,218 @@
{
"images" : [
{
"size": "60x60",
"expected-size": "180",
"filename": "180.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "3x"
},
{
"size": "40x40",
"expected-size": "80",
"filename": "80.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "2x"
},
{
"size": "40x40",
"expected-size": "120",
"filename": "120.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "3x"
},
{
"size": "60x60",
"expected-size": "120",
"filename": "120.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "2x"
},
{
"size": "57x57",
"expected-size": "57",
"filename": "57.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "1x"
},
{
"size": "29x29",
"expected-size": "58",
"filename": "58.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "2x"
},
{
"size": "29x29",
"expected-size": "29",
"filename": "29.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "1x"
},
{
"size": "29x29",
"expected-size": "87",
"filename": "87.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "3x"
},
{
"size": "57x57",
"expected-size": "114",
"filename": "114.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "iphone",
"scale": "2x"
},
{
"size": "20x20",
"expected-size": "40",
"filename" : "40.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom" : "iphone",
"scale": "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"size": "20x20",
"expected-size": "60",
"filename" : "60.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom" : "iphone",
"scale": "3x"
"scale" : "3x",
"size" : "20x20"
},
{
"size": "1024x1024",
"filename": "1024.png",
"expected-size": "1024",
"idiom": "ios-marketing",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"scale": "1x"
},
{
"size": "40x40",
"expected-size": "80",
"filename": "80.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "2x"
},
{
"size": "72x72",
"expected-size": "72",
"filename": "72.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "1x"
},
{
"size": "76x76",
"expected-size": "152",
"filename": "152.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "2x"
},
{
"size": "50x50",
"expected-size": "100",
"filename": "100.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "2x"
},
{
"size": "29x29",
"expected-size": "58",
"filename": "58.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "2x"
},
{
"size": "76x76",
"expected-size": "76",
"filename": "76.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "1x"
},
{
"size": "29x29",
"expected-size": "29",
"filename" : "29.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "1x"
"idiom" : "iphone",
"scale" : "1x",
"size" : "29x29"
},
{
"size": "50x50",
"expected-size": "50",
"filename": "50.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "1x"
"filename" : "58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"size": "72x72",
"expected-size": "144",
"filename": "144.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "2x"
"filename" : "87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"size": "40x40",
"expected-size": "40",
"filename": "40.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "1x"
"filename" : "80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"size": "83.5x83.5",
"expected-size": "167",
"filename": "167.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "ipad",
"scale": "2x"
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "57.png",
"idiom" : "iphone",
"scale" : "1x",
"size" : "57x57"
},
{
"filename" : "114.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "57x57"
},
{
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"size": "20x20",
"expected-size": "20",
"filename" : "20.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom" : "ipad",
"scale": "1x"
"scale" : "1x",
"size" : "20x20"
},
{
"size": "20x20",
"expected-size": "40",
"filename" : "40.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom" : "ipad",
"scale": "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"size": "128x128",
"expected-size": "128",
"filename": "128.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "1x"
"filename" : "29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"size": "256x256",
"expected-size": "256",
"filename": "256.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "1x"
"filename" : "58.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"size": "128x128",
"expected-size": "256",
"filename": "256.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "2x"
"filename" : "40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"size": "256x256",
"expected-size": "512",
"filename": "512.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "2x"
"filename" : "80.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"size": "32x32",
"expected-size": "32",
"filename": "32.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "1x"
"filename" : "50.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "50x50"
},
{
"size": "512x512",
"expected-size": "512",
"filename": "512.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "1x"
"filename" : "100.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "50x50"
},
{
"size": "16x16",
"expected-size": "16",
"filename": "16.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "1x"
"filename" : "72.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "72x72"
},
{
"size": "16x16",
"expected-size": "32",
"filename": "32.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "2x"
"filename" : "144.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "72x72"
},
{
"size": "32x32",
"expected-size": "64",
"filename": "64.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom": "mac",
"scale": "2x"
"filename" : "76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"size": "512x512",
"expected-size": "1024",
"filename" : "1024.png",
"folder": "Assets.xcassets/AppIcon.appiconset/",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "Icon-16.png",
"idiom" : "mac",
"scale": "2x"
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "Icon-32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "Icon-32 1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "Icon-64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "Icon-128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "Icon-256 1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "Icon-256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "Icon-512 1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "Icon-512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "Icon-1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -18,6 +18,8 @@
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(BUNDLE_ID_PREFIX).IceCubesApp</string>

View file

@ -2,10 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(BUNDLE_ID_PREFIX).IceCubesApp</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(BUNDLE_ID_PREFIX).IceCubesApp</string>

View file

@ -2,10 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(BUNDLE_ID_PREFIX).IceCubesApp</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(BUNDLE_ID_PREFIX).IceCubesApp</string>

View file

@ -7,6 +7,6 @@ public extension CGFloat {
static let scrollToViewHeight: CGFloat = 1
static let statusColumnsSpacing: CGFloat = 8
static let secondaryColumnWidth: CGFloat = 400
static let sidebarWidth: CGFloat = 80
static let sidebarWidth: CGFloat = 90
static let pollBarHeight: CGFloat = 30
}

View file

@ -12,7 +12,7 @@ public extension Font {
private static let subheadline = onMac ? 16.0 : 15.0
private static let footnote = onMac ? 15.0 : 13.0
private static let caption = onMac ? 14.0 : 12.0
private static let onMac = ProcessInfo.processInfo.isiOSAppOnMac
private static let onMac = ProcessInfo.processInfo.isMacCatalystApp
private static func customFont(size: CGFloat, relativeTo textStyle: TextStyle) -> Font {
if let chosenFont = Theme.shared.chosenFont {

View file

@ -14,5 +14,12 @@ import UIKit
{
guard let windowScene = scene as? UIWindowScene else { return }
window = windowScene.keyWindow
#if targetEnvironment(macCatalyst)
if let titlebar = windowScene.titlebar {
titlebar.titleVisibility = .hidden
titlebar.toolbar = nil
}
#endif
}
}

View file

@ -16,7 +16,7 @@ public struct AvatarView: View {
case .account:
return .init(width: 80, height: 80)
case .status:
if ProcessInfo.processInfo.isiOSAppOnMac {
if ProcessInfo.processInfo.isMacCatalystApp {
return .init(width: 48, height: 48)
}
return .init(width: 40, height: 40)

View file

@ -4,26 +4,15 @@ import SwiftUI
@MainActor
public extension View {
func statusEditorToolbarItem(routerPath: RouterPath, visibility: Models.Visibility) -> some ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
HapticManager.shared.fireHaptic(of: .buttonPress)
} label: {
Image(systemName: "square.and.pencil")
.accessibilityLabel("accessibility.tabs.timeline.new-post.label")
.accessibilityInputLabels([
LocalizedStringKey("accessibility.tabs.timeline.new-post.label"),
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel1"),
LocalizedStringKey("accessibility.tabs.timeline.new-post.inputLabel2"),
])
}
}
func statusEditorToolbarItem(routerPath: RouterPath,
visibility: Models.Visibility) -> some ToolbarContent {
StatusEditorToolbarItem(visibility: visibility)
}
}
@MainActor
public struct StatusEditorToolbarItem: ToolbarContent {
@Environment(\.openWindow) private var openWindow
@Environment(RouterPath.self) private var routerPath
let visibility: Models.Visibility
@ -36,9 +25,13 @@ public struct StatusEditorToolbarItem: ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
Task { @MainActor in
if ProcessInfo.processInfo.isMacCatalystApp {
openWindow(value: WindowDestination.newStatusEditor(visibility: visibility))
} else {
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
HapticManager.shared.fireHaptic(of: .buttonPress)
}
}
} label: {
Image(systemName: "square.and.pencil")
.accessibilityLabel("accessibility.tabs.timeline.new-post.label")

View file

@ -1,5 +1,4 @@
import Combine
@preconcurrency import SwiftUI
import Models
import QuickLook
@ -8,88 +7,10 @@ import QuickLook
public var selectedMediaAttachment: MediaAttachment?
public var mediaAttachments: [MediaAttachment] = []
public var url: URL? {
didSet {
if url == nil {
cleanup(urls: urls)
}
}
}
public private(set) var urls: [URL] = []
public init() {}
public func prepareFor(selectedMediaAttachment: MediaAttachment, mediaAttachments: [MediaAttachment]) {
if ProcessInfo.processInfo.isiOSAppOnMac, let selectedURL = selectedMediaAttachment.url {
let urls = mediaAttachments.compactMap{ $0.url }
Task {
await prepareFor(urls: urls, selectedURL: selectedURL)
}
} else {
self.selectedMediaAttachment = selectedMediaAttachment
self.mediaAttachments = mediaAttachments
}
}
private func prepareFor(urls: [URL], selectedURL: URL) async {
var transaction = Transaction(animation: .default)
transaction.disablesAnimations = true
do {
var order = 0
let pathOrderMap = urls.reduce(into: [String: Int]()) { result, url in
result[url.lastPathComponent] = order
order += 1
}
let paths: [URL] = try await withThrowingTaskGroup(of: URL.self, body: { group in
var paths: [URL] = []
for url in urls {
group.addTask {
try await self.localPathFor(url: url)
}
}
for try await path in group {
paths.append(path)
}
return paths.sorted { url1, url2 in
pathOrderMap[url1.lastPathComponent] ?? 0 < pathOrderMap[url2.lastPathComponent] ?? 0
}
})
withTransaction(transaction) {
self.urls = paths
url = paths.first(where: { $0.lastPathComponent == selectedURL.lastPathComponent })
}
} catch {
withTransaction(transaction) {
self.urls = []
url = nil
}
}
}
private var quickLookDir: URL {
try! FileManager.default.url(for: .cachesDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
.appending(component: "quicklook")
}
private func localPathFor(url: URL) async throws -> URL {
try? FileManager.default.createDirectory(at: quickLookDir, withIntermediateDirectories: true)
let path = quickLookDir.appendingPathComponent(url.lastPathComponent)
// Warning: Non-sendable type '(any URLSessionTaskDelegate)?' exiting main actor-isolated
// context in call to non-isolated instance method 'data(for:delegate:)' cannot cross actor
// boundary.
// This is on the defaulted-to-nil second parameter of `.data(from:delegate:)`.
// There is a Radar tracking this & others like it.
let data = try await URLSession.shared.data(from: url).0
try data.write(to: path)
return path
}
private func cleanup(urls _: [URL]) {
try? FileManager.default.removeItem(at: quickLookDir)
}
}

View file

@ -25,6 +25,20 @@ public enum RouterDestination: Hashable {
case tagsList(tags: [Tag])
}
public enum WindowDestination: Hashable, Codable {
case newStatusEditor(visibility: Models.Visibility)
case mediaViewer(attachments: [MediaAttachment], selectedAttachment: MediaAttachment)
var initialSize: CGSize {
switch self {
case .newStatusEditor:
return .init(width: 500, height: 700)
case .mediaViewer:
return .init(width: 800, height: 600)
}
}
}
public enum SheetDestination: Identifiable {
case newStatusEditor(visibility: Models.Visibility)
case editStatusEditor(status: Status)

View file

@ -71,6 +71,7 @@ public struct MediaUIView: View, @unchecked Sendable {
@ToolbarContentBuilder
private var toolbarView: some ToolbarContent {
#if !targetEnvironment(macCatalyst)
ToolbarItem(placement: .topBarLeading) {
Button {
dismiss()
@ -78,6 +79,7 @@ public struct MediaUIView: View, @unchecked Sendable {
Image(systemName: "xmark.circle")
}
}
#endif
ToolbarItem(placement: .topBarTrailing) {
if let url = attachments.first(where: { $0.id == scrollToId})?.url {
Button {

View file

@ -37,7 +37,7 @@ struct StatusEditorAccessoryView: View {
} label: {
Label("status.editor.photo-library", systemImage: "photo")
}
if !ProcessInfo.processInfo.isiOSAppOnMac {
if !ProcessInfo.processInfo.isMacCatalystApp {
Button {
isCameraPickerPresented = true
} label: {

View file

@ -215,7 +215,7 @@ public struct StatusEditorView: View {
SoundEffectManager.shared.playSound(of: .tootSent)
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
object: nil)
if !viewModel.mode.isInShareExtension, !preferences.requestedReview, !ProcessInfo.processInfo.isiOSAppOnMac {
if !viewModel.mode.isInShareExtension, !preferences.requestedReview, !ProcessInfo.processInfo.isMacCatalystApp {
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
SKStoreReviewController.requestReview(in: scene)
}

View file

@ -45,7 +45,7 @@ extension TextView.Representable {
textView.allowsEditingTextAttributes = false
textView.returnKeyType = .default
textView.allowsEditingTextAttributes = true
if ProcessInfo.processInfo.isiOSAppOnMac {
if ProcessInfo.processInfo.isMacCatalystApp {
textView.inlinePredictionType = .no
}

View file

@ -9,6 +9,7 @@ import SwiftUI
@MainActor
public struct StatusRowView: View {
@Environment(\.openWindow) private var openWindow
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@Environment(\.redactionReasons) private var reasons
@Environment(\.isCompact) private var isCompact: Bool
@ -208,9 +209,14 @@ public struct StatusRowView: View {
Button("accessibility.status.media-viewer-action.label") {
HapticManager.shared.fireHaptic(of: .notification(.success))
let attachments = viewModel.finalStatus.mediaAttachments
if ProcessInfo.processInfo.isMacCatalystApp {
openWindow(value: WindowDestination.mediaViewer(attachments: attachments,
selectedAttachment: attachments[0]))
} else {
quickLook.prepareFor(selectedMediaAttachment: attachments[0], mediaAttachments: attachments)
}
}
}
Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") {
withAnimation {

View file

@ -8,6 +8,7 @@ import MediaUI
@MainActor
public struct StatusRowMediaPreviewView: View {
@Environment(\.openWindow) private var openWindow
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
@Environment(\.extraLeadingInset) private var extraLeadingInset: CGFloat
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@ -88,8 +89,13 @@ public struct StatusRowMediaPreviewView: View {
if attachments.count == 1, let attachment = attachments.first {
makeFeaturedImagePreview(attachment: attachment)
.onTapGesture {
if ProcessInfo.processInfo.isMacCatalystApp {
openWindow(value: WindowDestination.mediaViewer(attachments: attachments,
selectedAttachment: attachment))
} else {
quickLook.prepareFor(selectedMediaAttachment: attachment, mediaAttachments: attachments)
}
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(Self.accessibilityLabel(for: attachment))
.accessibilityAddTraits([.isButton, .isImage])
@ -272,8 +278,13 @@ public struct StatusRowMediaPreviewView: View {
// #965: do not create overlapping tappable areas, when multiple images are shown
.contentShape(Rectangle())
.onTapGesture {
if ProcessInfo.processInfo.isMacCatalystApp {
openWindow(value: WindowDestination.mediaViewer(attachments: attachments,
selectedAttachment: attachment))
} else {
quickLook.prepareFor(selectedMediaAttachment: attachment, mediaAttachments: attachments)
}
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(Self.accessibilityLabel(for: attachment))
.accessibilityAddTraits(attachment.supportedType == .image ? [.isImage, .isButton] : .isButton)