From f71875512083ddc91d7c940dbfdcec818771f298 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Mon, 23 Jan 2023 18:43:48 +0100 Subject: [PATCH] Refactor iPad / macOS layout for medias in order to make the timeline smoother fix #282 --- IceCubesApp/App/IceCubesApp.swift | 56 ++++++++++------- .../Sources/DesignSystem/SceneDelegate.swift | 16 +++++ .../Status/Row/StatusMediaPreviewView.swift | 63 ++++++++++--------- 3 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 Packages/DesignSystem/Sources/DesignSystem/SceneDelegate.swift diff --git a/IceCubesApp/App/IceCubesApp.swift b/IceCubesApp/App/IceCubesApp.swift index 9a1a8776..555026d7 100644 --- a/IceCubesApp/App/IceCubesApp.swift +++ b/IceCubesApp/App/IceCubesApp.swift @@ -12,9 +12,9 @@ import Timeline @main struct IceCubesApp: App { @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate - + @Environment(\.scenePhase) private var scenePhase - + @StateObject private var appAccountsManager = AppAccountsManager.shared @StateObject private var currentInstance = CurrentInstance.shared @StateObject private var currentAccount = CurrentAccount.shared @@ -23,16 +23,16 @@ struct IceCubesApp: App { @StateObject private var quickLook = QuickLook() @StateObject private var theme = Theme.shared @StateObject private var sidebarRouterPath = RouterPath() - + @State private var selectedTab: Tab = .timeline @State private var selectSidebarItem: Tab? = .timeline @State private var popToRootTab: Tab = .other @State private var sideBarLoadedTabs: Set = Set() - + private var availableTabs: [Tab] { appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab() } - + var body: some Scene { WindowGroup { appView @@ -69,7 +69,7 @@ struct IceCubesApp: App { } } } - + @ViewBuilder private var appView: some View { if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { @@ -78,14 +78,14 @@ struct IceCubesApp: App { tabBarView } } - + private func badgeFor(tab: Tab) -> Int { if tab == .notifications && selectedTab != tab { return watcher.unreadNotificationsCount + userPreferences.pushNotificationsCount } return 0 } - + private var sidebarView: some View { SideBarView(selectedTab: $selectedTab, popToRootTab: $popToRootTab, @@ -114,7 +114,7 @@ struct IceCubesApp: App { sideBarLoadedTabs.removeAll() } } - + private var tabBarView: some View { TabView(selection: .init(get: { selectedTab @@ -139,14 +139,14 @@ struct IceCubesApp: App { } } } - + private func setNewClientsInEnv(client: Client) { currentAccount.setClient(client: client) currentInstance.setClient(client: client) userPreferences.setClient(client: client) watcher.setClient(client: client) } - + private func handleScenePhase(scenePhase: ScenePhase) { switch scenePhase { case .background: @@ -163,16 +163,16 @@ struct IceCubesApp: App { break } } - + private func setupRevenueCat() { Purchases.logLevel = .error Purchases.configure(withAPIKey: "appl_JXmiRckOzXXTsHKitQiicXCvMQi") } - + private func refreshPushSubs() { PushNotificationsService.shared.requestPushNotifications() } - + @CommandsBuilder private var appMenu: some Commands { CommandGroup(replacing: .newItem) { @@ -199,33 +199,41 @@ struct IceCubesApp: App { class AppDelegate: NSObject, UIApplicationDelegate { let themeObserver = ThemeObserverViewController(nibName: nil, bundle: nil) - + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { try? AVAudioSession.sharedInstance().setCategory(.ambient, options: .mixWithOthers) return true } - + func application(_: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { PushNotificationsService.shared.pushToken = deviceToken - #if !DEBUG - Task { - await PushNotificationsService.shared.fetchSubscriptions(accounts: AppAccountsManager.shared.pushAccounts) - await PushNotificationsService.shared.updateSubscriptions(accounts: AppAccountsManager.shared.pushAccounts) - } - #endif +#if !DEBUG + Task { + await PushNotificationsService.shared.fetchSubscriptions(accounts: AppAccountsManager.shared.pushAccounts) + await PushNotificationsService.shared.updateSubscriptions(accounts: AppAccountsManager.shared.pushAccounts) + } +#endif } - + func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {} + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role) + if connectingSceneSession.role == .windowApplication { + configuration.delegateClass = SceneDelegate.self + } + return configuration + } } class ThemeObserverViewController: UIViewController { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - + print(traitCollection.userInterfaceStyle.rawValue) } } diff --git a/Packages/DesignSystem/Sources/DesignSystem/SceneDelegate.swift b/Packages/DesignSystem/Sources/DesignSystem/SceneDelegate.swift new file mode 100644 index 00000000..77e67d35 --- /dev/null +++ b/Packages/DesignSystem/Sources/DesignSystem/SceneDelegate.swift @@ -0,0 +1,16 @@ +import UIKit + +public class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate { + public var window: UIWindow? + + public var windowWidth: CGFloat { + window?.bounds.size.width ?? UIScreen.main.bounds.size.width + } + + public func scene(_ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = scene as? UIWindowScene else { return } + self.window = windowScene.keyWindow + } +} diff --git a/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift index c7c13bc7..4ab001b2 100644 --- a/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift @@ -9,6 +9,7 @@ import SwiftUI public struct StatusMediaPreviewView: View { @Environment(\.openURL) private var openURL + @EnvironmentObject var sceneDelegate: SceneDelegate @EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var quickLook: QuickLook @EnvironmentObject private var theme: Theme @@ -23,10 +24,26 @@ public struct StatusMediaPreviewView: View { @State private var isAltAlertDisplayed: Bool = false @State private var isHidingMedia: Bool = false + var availableWidth: CGFloat { + if sceneDelegate.windowWidth > .maxColumnWidth { + return .maxColumnWidth + } + return sceneDelegate.windowWidth + } + + var appLayoutWidth: CGFloat { + let avatarColumnWidth = theme.avatarPosition == .leading ? AvatarView.Size.status.size.width + .statusColumnsSpacing : 0 + var sidebarWidth: CGFloat = 0 + if UIDevice.current.userInterfaceIdiom == .pad && sceneDelegate.windowWidth < (.maxColumnWidth + .sidebarWidth) { + sidebarWidth = .sidebarWidth + } + return (.layoutPadding * 2) + avatarColumnWidth + sidebarWidth + } + private var imageMaxHeight: CGFloat { if isNotifications { if UIDevice.current.userInterfaceIdiom == .pad { - return 150 + return 100 } return 50 } @@ -40,12 +57,6 @@ public struct StatusMediaPreviewView: View { } private func size(for media: MediaAttachment) -> CGSize? { - if isNotifications { - return .init(width: 50, height: 50) - } - if theme.statusDisplayStyle == .compact { - return .init(width: 100, height: 100) - } if let width = media.meta?.original?.width, let height = media.meta?.original?.height { @@ -55,8 +66,8 @@ public struct StatusMediaPreviewView: View { } private func imageSize(from: CGSize, newWidth: CGFloat) -> CGSize { - if isNotifications { - return .init(width: 50, height: 50) + if isNotifications || theme.statusDisplayStyle == .compact { + return .init(width: imageMaxHeight, height: imageMaxHeight) } let ratio = newWidth / from.width let newHeight = from.height * ratio @@ -137,15 +148,9 @@ public struct StatusMediaPreviewView: View { ZStack(alignment: .bottomTrailing) { switch attachment.supportedType { case .image: - if theme.statusDisplayStyle == .large, - let size = size(for: attachment), - UIDevice.current.userInterfaceIdiom != .pad, - UIDevice.current.userInterfaceIdiom != .mac - { - let avatarColumnWidth = theme.avatarPosition == .leading ? AvatarView.Size.status.size.width + .statusColumnsSpacing : 0 - let availableWidth = UIScreen.main.bounds.width - (.layoutPadding * 2) - avatarColumnWidth + if let size = size(for: attachment) { let newSize = imageSize(from: size, - newWidth: availableWidth) + newWidth: availableWidth - appLayoutWidth) LazyImage(url: attachment.url) { state in if let image = state.image { @@ -161,22 +166,19 @@ public struct StatusMediaPreviewView: View { } } } else { - AsyncImage( - url: attachment.url, - content: { image in + LazyImage(url: attachment.url) { state in + if let image = state.image { image - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxHeight: isNotifications || theme.statusDisplayStyle == .compact ? imageMaxHeight : nil) + .resizingMode(.aspectFit) + .frame(maxHeight: imageMaxHeight) .cornerRadius(4) - }, - placeholder: { + } else { RoundedRectangle(cornerRadius: 4) .fill(Color.gray) - .frame(maxHeight: isNotifications || theme.statusDisplayStyle == .compact ? imageMaxHeight : nil) + .frame(maxHeight: imageMaxHeight) .shimmering() } - ) + } } case .gifv, .video, .audio: if let url = attachment.url { @@ -195,13 +197,14 @@ public struct StatusMediaPreviewView: View { altTextDisplayed = alt isAltAlertDisplayed = true } label: { - Text("ALT") + Text("status.image.alt-text.abbreviation") + .font(theme.statusDisplayStyle == .compact ? .footnote : .body) } - .padding(8) + .padding(4) .background(.thinMaterial) .cornerRadius(4) } - .padding(10) + .padding(theme.statusDisplayStyle == .compact ? 0 : 10) } } }