mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-24 22:11:07 +01:00
Refactor iPad / macOS layout for medias in order to make the timeline smoother fix #282
This commit is contained in:
parent
5c7cc5803f
commit
f718755120
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user