Add secondary column when screen is wide enough

This commit is contained in:
Thomas Ricouard 2023-01-29 16:45:58 +01:00
parent 243cbcbc41
commit 497c2a1fe1
5 changed files with 57 additions and 42 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,18 +23,18 @@ 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 let feedbackGenerator = UISelectionFeedbackGenerator() private let feedbackGenerator = UISelectionFeedbackGenerator()
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
@ -72,7 +72,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 {
@ -81,35 +81,45 @@ 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,
tabs: availableTabs, tabs: availableTabs,
routerPath: sidebarRouterPath) { routerPath: sidebarRouterPath) {
ZStack { GeometryReader { proxy in
if selectedTab == .profile { HStack(spacing: 0) {
ProfileTab(popToRootTab: $popToRootTab) ZStack {
} if selectedTab == .profile {
ForEach(availableTabs) { tab in ProfileTab(popToRootTab: $popToRootTab)
if tab == selectedTab || sideBarLoadedTabs.contains(tab) { }
tab ForEach(availableTabs) { tab in
.makeContentView(popToRootTab: $popToRootTab) if tab == selectedTab || sideBarLoadedTabs.contains(tab) {
.opacity(tab == selectedTab ? 1 : 0) tab
.transition(.opacity) .makeContentView(popToRootTab: $popToRootTab)
.id("\(tab)\(appAccountsManager.currentAccount.id)") .opacity(tab == selectedTab ? 1 : 0)
.onAppear { .transition(.opacity)
sideBarLoadedTabs.insert(tab) .id("\(tab)\(appAccountsManager.currentAccount.id)")
.onAppear {
sideBarLoadedTabs.insert(tab)
}
} else {
EmptyView()
} }
} else { }
EmptyView() }
if proxy.frame(in: .global).width > (.maxColumnWidth + .secondaryColumnWidth),
currentAccount.account?.id != nil {
Divider().edgesIgnoringSafeArea(.all)
NotificationsTab(popToRootTab: $popToRootTab, lockedType: nil, isSecondaryColumn: true)
.frame(maxWidth: 360)
} }
} }
} }
@ -117,7 +127,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
@ -143,14 +153,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:
@ -167,16 +177,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) {
@ -203,14 +213,14 @@ 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)
{ {
@ -220,9 +230,9 @@ class AppDelegate: NSObject, UIApplicationDelegate {
await PushNotificationsService.shared.updateSubscriptions(forceCreate: false) await PushNotificationsService.shared.updateSubscriptions(forceCreate: false)
} }
} }
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {} func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {}
func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration { func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role) let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
if connectingSceneSession.role == .windowApplication { if connectingSceneSession.role == .windowApplication {
@ -235,7 +245,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
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

@ -18,6 +18,7 @@ struct NotificationsTab: View {
@Binding var popToRootTab: Tab @Binding var popToRootTab: Tab
let lockedType: Models.Notification.NotificationType? let lockedType: Models.Notification.NotificationType?
let isSecondaryColumn: Bool
var body: some View { var body: some View {
NavigationStack(path: $routerPath.path) { NavigationStack(path: $routerPath.path) {
@ -25,11 +26,13 @@ struct NotificationsTab: View {
.withAppRouter() .withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbar { .toolbar {
statusEditorToolbarItem(routerPath: routerPath, if !isSecondaryColumn {
visibility: userPreferences.postVisibility) statusEditorToolbarItem(routerPath: routerPath,
if UIDevice.current.userInterfaceIdiom != .pad { visibility: userPreferences.postVisibility)
ToolbarItem(placement: .navigationBarLeading) { if UIDevice.current.userInterfaceIdiom != .pad {
AppAccountsSelectorView(routerPath: routerPath) ToolbarItem(placement: .navigationBarLeading) {
AppAccountsSelectorView(routerPath: routerPath)
}
} }
} }
} }

View file

@ -37,9 +37,9 @@ enum Tab: Int, Identifiable, Hashable {
case .federated: case .federated:
TimelineTab(popToRootTab: popToRootTab, timeline: .federated) TimelineTab(popToRootTab: popToRootTab, timeline: .federated)
case .notifications: case .notifications:
NotificationsTab(popToRootTab: popToRootTab, lockedType: nil) NotificationsTab(popToRootTab: popToRootTab, lockedType: nil, isSecondaryColumn: false)
case .mentions: case .mentions:
NotificationsTab(popToRootTab: popToRootTab, lockedType: .mention) NotificationsTab(popToRootTab: popToRootTab, lockedType: .mention, isSecondaryColumn: false)
case .explore: case .explore:
ExploreTab(popToRootTab: popToRootTab) ExploreTab(popToRootTab: popToRootTab)
case .messages: case .messages:

View file

@ -5,6 +5,7 @@ public extension CGFloat {
static let dividerPadding: CGFloat = 2 static let dividerPadding: CGFloat = 2
static let statusColumnsSpacing: CGFloat = 8 static let statusColumnsSpacing: CGFloat = 8
static let maxColumnWidth: CGFloat = 650 static let maxColumnWidth: CGFloat = 650
static let secondaryColumnWidth: CGFloat = 360
static let sidebarWidth: CGFloat = 80 static let sidebarWidth: CGFloat = 80
static let pollBarHeight: CGFloat = 30 static let pollBarHeight: CGFloat = 30
} }

View file

@ -319,6 +319,7 @@ public struct StatusRowView: View {
Text("status.action.translate") Text("status.action.translate")
} }
} }
.buttonStyle(.borderless)
} }
if let translation = viewModel.translation, !viewModel.isLoadingTranslation { if let translation = viewModel.translation, !viewModel.isLoadingTranslation {