Refactor iPad / macOS layout for medias in order to make the timeline smoother fix #282

This commit is contained in:
Thomas Ricouard 2023-01-23 18:43:48 +01:00
parent 5c7cc5803f
commit f718755120
3 changed files with 81 additions and 54 deletions

View File

@ -12,9 +12,9 @@ import Timeline
@main @main
struct IceCubesApp: App { struct IceCubesApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@StateObject private var appAccountsManager = AppAccountsManager.shared @StateObject private var appAccountsManager = AppAccountsManager.shared
@StateObject private var currentInstance = CurrentInstance.shared @StateObject private var currentInstance = CurrentInstance.shared
@StateObject private var currentAccount = CurrentAccount.shared @StateObject private var currentAccount = CurrentAccount.shared
@ -23,16 +23,16 @@ struct IceCubesApp: App {
@StateObject private var quickLook = QuickLook() @StateObject private var quickLook = QuickLook()
@StateObject private var theme = Theme.shared @StateObject private var theme = Theme.shared
@StateObject private var sidebarRouterPath = RouterPath() @StateObject private var sidebarRouterPath = RouterPath()
@State private var selectedTab: Tab = .timeline @State private var selectedTab: Tab = .timeline
@State private var selectSidebarItem: Tab? = .timeline @State private var selectSidebarItem: Tab? = .timeline
@State private var popToRootTab: Tab = .other @State private var popToRootTab: Tab = .other
@State private var sideBarLoadedTabs: Set<Tab> = Set() @State private var sideBarLoadedTabs: Set<Tab> = Set()
private var availableTabs: [Tab] { private var availableTabs: [Tab] {
appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab() appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab()
} }
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
appView appView
@ -69,7 +69,7 @@ struct IceCubesApp: App {
} }
} }
} }
@ViewBuilder @ViewBuilder
private var appView: some View { private var appView: some View {
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
@ -78,14 +78,14 @@ struct IceCubesApp: App {
tabBarView tabBarView
} }
} }
private func badgeFor(tab: Tab) -> Int { private func badgeFor(tab: Tab) -> Int {
if tab == .notifications && selectedTab != tab { if tab == .notifications && selectedTab != tab {
return watcher.unreadNotificationsCount + userPreferences.pushNotificationsCount return watcher.unreadNotificationsCount + userPreferences.pushNotificationsCount
} }
return 0 return 0
} }
private var sidebarView: some View { private var sidebarView: some View {
SideBarView(selectedTab: $selectedTab, SideBarView(selectedTab: $selectedTab,
popToRootTab: $popToRootTab, popToRootTab: $popToRootTab,
@ -114,7 +114,7 @@ struct IceCubesApp: App {
sideBarLoadedTabs.removeAll() sideBarLoadedTabs.removeAll()
} }
} }
private var tabBarView: some View { private var tabBarView: some View {
TabView(selection: .init(get: { TabView(selection: .init(get: {
selectedTab selectedTab
@ -139,14 +139,14 @@ struct IceCubesApp: App {
} }
} }
} }
private func setNewClientsInEnv(client: Client) { private func setNewClientsInEnv(client: Client) {
currentAccount.setClient(client: client) currentAccount.setClient(client: client)
currentInstance.setClient(client: client) currentInstance.setClient(client: client)
userPreferences.setClient(client: client) userPreferences.setClient(client: client)
watcher.setClient(client: client) watcher.setClient(client: client)
} }
private func handleScenePhase(scenePhase: ScenePhase) { private func handleScenePhase(scenePhase: ScenePhase) {
switch scenePhase { switch scenePhase {
case .background: case .background:
@ -163,16 +163,16 @@ struct IceCubesApp: App {
break break
} }
} }
private func setupRevenueCat() { private func setupRevenueCat() {
Purchases.logLevel = .error Purchases.logLevel = .error
Purchases.configure(withAPIKey: "appl_JXmiRckOzXXTsHKitQiicXCvMQi") Purchases.configure(withAPIKey: "appl_JXmiRckOzXXTsHKitQiicXCvMQi")
} }
private func refreshPushSubs() { private func refreshPushSubs() {
PushNotificationsService.shared.requestPushNotifications() PushNotificationsService.shared.requestPushNotifications()
} }
@CommandsBuilder @CommandsBuilder
private var appMenu: some Commands { private var appMenu: some Commands {
CommandGroup(replacing: .newItem) { CommandGroup(replacing: .newItem) {
@ -199,33 +199,41 @@ struct IceCubesApp: App {
class AppDelegate: NSObject, UIApplicationDelegate { class AppDelegate: NSObject, UIApplicationDelegate {
let themeObserver = ThemeObserverViewController(nibName: nil, bundle: nil) let themeObserver = ThemeObserverViewController(nibName: nil, bundle: nil)
func application(_: UIApplication, func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
{ {
try? AVAudioSession.sharedInstance().setCategory(.ambient, options: .mixWithOthers) try? AVAudioSession.sharedInstance().setCategory(.ambient, options: .mixWithOthers)
return true return true
} }
func application(_: UIApplication, func application(_: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
{ {
PushNotificationsService.shared.pushToken = deviceToken PushNotificationsService.shared.pushToken = deviceToken
#if !DEBUG #if !DEBUG
Task { Task {
await PushNotificationsService.shared.fetchSubscriptions(accounts: AppAccountsManager.shared.pushAccounts) await PushNotificationsService.shared.fetchSubscriptions(accounts: AppAccountsManager.shared.pushAccounts)
await PushNotificationsService.shared.updateSubscriptions(accounts: AppAccountsManager.shared.pushAccounts) await PushNotificationsService.shared.updateSubscriptions(accounts: AppAccountsManager.shared.pushAccounts)
} }
#endif #endif
} }
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {} 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 { class ThemeObserverViewController: UIViewController {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection) super.traitCollectionDidChange(previousTraitCollection)
print(traitCollection.userInterfaceStyle.rawValue) print(traitCollection.userInterfaceStyle.rawValue)
} }
} }

View File

@ -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
}
}

View File

@ -9,6 +9,7 @@ import SwiftUI
public struct StatusMediaPreviewView: View { public struct StatusMediaPreviewView: View {
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@EnvironmentObject var sceneDelegate: SceneDelegate
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var quickLook: QuickLook @EnvironmentObject private var quickLook: QuickLook
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -23,10 +24,26 @@ public struct StatusMediaPreviewView: View {
@State private var isAltAlertDisplayed: Bool = false @State private var isAltAlertDisplayed: Bool = false
@State private var isHidingMedia: 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 { private var imageMaxHeight: CGFloat {
if isNotifications { if isNotifications {
if UIDevice.current.userInterfaceIdiom == .pad { if UIDevice.current.userInterfaceIdiom == .pad {
return 150 return 100
} }
return 50 return 50
} }
@ -40,12 +57,6 @@ public struct StatusMediaPreviewView: View {
} }
private func size(for media: MediaAttachment) -> CGSize? { 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, if let width = media.meta?.original?.width,
let height = media.meta?.original?.height let height = media.meta?.original?.height
{ {
@ -55,8 +66,8 @@ public struct StatusMediaPreviewView: View {
} }
private func imageSize(from: CGSize, newWidth: CGFloat) -> CGSize { private func imageSize(from: CGSize, newWidth: CGFloat) -> CGSize {
if isNotifications { if isNotifications || theme.statusDisplayStyle == .compact {
return .init(width: 50, height: 50) return .init(width: imageMaxHeight, height: imageMaxHeight)
} }
let ratio = newWidth / from.width let ratio = newWidth / from.width
let newHeight = from.height * ratio let newHeight = from.height * ratio
@ -137,15 +148,9 @@ public struct StatusMediaPreviewView: View {
ZStack(alignment: .bottomTrailing) { ZStack(alignment: .bottomTrailing) {
switch attachment.supportedType { switch attachment.supportedType {
case .image: case .image:
if theme.statusDisplayStyle == .large, if let size = size(for: attachment) {
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
let newSize = imageSize(from: size, let newSize = imageSize(from: size,
newWidth: availableWidth) newWidth: availableWidth - appLayoutWidth)
LazyImage(url: attachment.url) { state in LazyImage(url: attachment.url) { state in
if let image = state.image { if let image = state.image {
@ -161,22 +166,19 @@ public struct StatusMediaPreviewView: View {
} }
} }
} else { } else {
AsyncImage( LazyImage(url: attachment.url) { state in
url: attachment.url, if let image = state.image {
content: { image in
image image
.resizable() .resizingMode(.aspectFit)
.aspectRatio(contentMode: .fit) .frame(maxHeight: imageMaxHeight)
.frame(maxHeight: isNotifications || theme.statusDisplayStyle == .compact ? imageMaxHeight : nil)
.cornerRadius(4) .cornerRadius(4)
}, } else {
placeholder: {
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 4)
.fill(Color.gray) .fill(Color.gray)
.frame(maxHeight: isNotifications || theme.statusDisplayStyle == .compact ? imageMaxHeight : nil) .frame(maxHeight: imageMaxHeight)
.shimmering() .shimmering()
} }
) }
} }
case .gifv, .video, .audio: case .gifv, .video, .audio:
if let url = attachment.url { if let url = attachment.url {
@ -195,13 +197,14 @@ public struct StatusMediaPreviewView: View {
altTextDisplayed = alt altTextDisplayed = alt
isAltAlertDisplayed = true isAltAlertDisplayed = true
} label: { } label: {
Text("ALT") Text("status.image.alt-text.abbreviation")
.font(theme.statusDisplayStyle == .compact ? .footnote : .body)
} }
.padding(8) .padding(4)
.background(.thinMaterial) .background(.thinMaterial)
.cornerRadius(4) .cornerRadius(4)
} }
.padding(10) .padding(theme.statusDisplayStyle == .compact ? 0 : 10)
} }
} }
} }