macOS / iPad app fixes + support drop in the editor + global new post button
This commit is contained in:
parent
7f6419ebae
commit
899ccd8ad7
|
@ -34,6 +34,7 @@
|
|||
9F398AA92935FFDB00A889F2 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AA82935FFDB00A889F2 /* Account */; };
|
||||
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AAA2935FFDB00A889F2 /* Models */; };
|
||||
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AB229360A4C00A889F2 /* TimelineTab.swift */; };
|
||||
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4A48182976B21900A1A038 /* ProfileTab.swift */; };
|
||||
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55C68C2955968700F94077 /* ExploreTab.swift */; };
|
||||
9F55C6902955993C00F94077 /* Explore in Frameworks */ = {isa = PBXBuildFile; productRef = 9F55C68F2955993C00F94077 /* Explore */; };
|
||||
9F5E581929545BE700A53960 /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5E581829545BE700A53960 /* Env */; };
|
||||
|
@ -125,6 +126,7 @@
|
|||
9F398AA52935FE8A00A889F2 /* AppRouteur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteur.swift; sourceTree = "<group>"; };
|
||||
9F398AAC2936005300A889F2 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Account; path = Packages/Account; sourceTree = "<group>"; };
|
||||
9F398AB229360A4C00A889F2 /* TimelineTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTab.swift; sourceTree = "<group>"; };
|
||||
9F4A48182976B21900A1A038 /* ProfileTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTab.swift; sourceTree = "<group>"; };
|
||||
9F55C68C2955968700F94077 /* ExploreTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreTab.swift; sourceTree = "<group>"; };
|
||||
9F55C68E295598F900F94077 /* Explore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Explore; path = Packages/Explore; sourceTree = "<group>"; };
|
||||
9F5E581729545B5500A53960 /* Env */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Env; path = Packages/Env; sourceTree = "<group>"; };
|
||||
|
@ -267,6 +269,7 @@
|
|||
9F35DB4B2952005C00B3281A /* MessagesTab.swift */,
|
||||
9F55C68C2955968700F94077 /* ExploreTab.swift */,
|
||||
9F2B92F5295AE04800DE16D0 /* Tabs.swift */,
|
||||
9F4A48182976B21900A1A038 /* ProfileTab.swift */,
|
||||
);
|
||||
path = Tabs;
|
||||
sourceTree = "<group>";
|
||||
|
@ -537,6 +540,7 @@
|
|||
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */,
|
||||
9F398AA62935FE8A00A889F2 /* AppRouteur.swift in Sources */,
|
||||
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */,
|
||||
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */,
|
||||
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */,
|
||||
639CDF9C296AC82F00C35E58 /* SafariRouteur.swift in Sources */,
|
||||
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */,
|
||||
|
|
|
@ -21,12 +21,13 @@ struct IceCubesApp: App {
|
|||
@StateObject private var watcher = StreamWatcher()
|
||||
@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: [Tab] = []
|
||||
|
||||
@State private var sideBarLoadedTabs: Set<Tab> = Set()
|
||||
|
||||
private var availableTabs: [Tab] {
|
||||
appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab()
|
||||
}
|
||||
|
@ -54,6 +55,14 @@ struct IceCubesApp: App {
|
|||
.edgesIgnoringSafeArea(.bottom)
|
||||
})
|
||||
}
|
||||
.commands {
|
||||
CommandGroup(replacing: CommandGroupPlacement.newItem) {
|
||||
Button("New post") {
|
||||
sidebarRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.serverPreferences?.postVisibility ?? .pub)
|
||||
}
|
||||
.keyboardShortcut("n", modifiers: .command)
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { scenePhase in
|
||||
handleScenePhase(scenePhase: scenePhase)
|
||||
}
|
||||
|
@ -84,10 +93,11 @@ struct IceCubesApp: App {
|
|||
private var sidebarView: some View {
|
||||
SideBarView(selectedTab: $selectedTab,
|
||||
popToRootTab: $popToRootTab,
|
||||
tabs: availableTabs) {
|
||||
tabs: availableTabs,
|
||||
routerPath: sidebarRouterPath) {
|
||||
ZStack {
|
||||
if let account = currentAccount.account, selectedTab == .profile {
|
||||
AccountDetailView(account: account)
|
||||
if selectedTab == .profile {
|
||||
ProfileTab(popToRootTab: $popToRootTab)
|
||||
}
|
||||
ForEach(availableTabs) { tab in
|
||||
if tab == selectedTab || sideBarLoadedTabs.contains(tab) {
|
||||
|
@ -96,9 +106,7 @@ struct IceCubesApp: App {
|
|||
.opacity(tab == selectedTab ? 1 : 0)
|
||||
.id(tab)
|
||||
.onAppear {
|
||||
if !sideBarLoadedTabs.contains(tab) {
|
||||
sideBarLoadedTabs.append(tab)
|
||||
}
|
||||
sideBarLoadedTabs.insert(tab)
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
|
@ -186,4 +194,5 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
}
|
||||
|
||||
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {}
|
||||
|
||||
}
|
||||
|
|
|
@ -56,12 +56,9 @@ class AppQLPreviewCpntroller: QLPreviewController {
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
navigationItem.rightBarButtonItem = closeButton
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
navigationItem.rightBarButtonItem = closeButton
|
||||
if UIDevice.current.userInterfaceIdiom != .pad {
|
||||
navigationItem.rightBarButtonItem = closeButton
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
|
|
|
@ -7,51 +7,106 @@ import SwiftUI
|
|||
struct SideBarView<Content: View>: View {
|
||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||
@EnvironmentObject private var theme: Theme
|
||||
|
||||
@EnvironmentObject private var watcher: StreamWatcher
|
||||
@EnvironmentObject private var userPreferences: UserPreferences
|
||||
|
||||
@Binding var selectedTab: Tab
|
||||
@Binding var popToRootTab: Tab
|
||||
var tabs: [Tab]
|
||||
@ObservedObject var routerPath = RouterPath()
|
||||
@ViewBuilder var content: () -> Content
|
||||
|
||||
private func badgeFor(tab: Tab) -> Int {
|
||||
if tab == .notifications && selectedTab != tab {
|
||||
return watcher.unreadNotificationsCount + userPreferences.pushNotificationsCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private var profileView: some View {
|
||||
Button {
|
||||
selectedTab = .profile
|
||||
} label: {
|
||||
AppAccountsSelectorView(routeurPath: RouterPath(),
|
||||
accountCreationEnabled: false,
|
||||
avatarSize: .status)
|
||||
}
|
||||
.frame(width: .sidebarWidth, height: 60)
|
||||
.background(selectedTab == .profile ? theme.secondaryBackgroundColor : .clear)
|
||||
}
|
||||
|
||||
private func makeIconForTab(tab: Tab) -> some View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Image(systemName: tab.iconName)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(tab == selectedTab ? theme.tintColor : .gray)
|
||||
if let badge = badgeFor(tab: tab), badge > 0 {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(.red)
|
||||
Text(String(badge))
|
||||
.foregroundColor(.white)
|
||||
.font(.footnote)
|
||||
}
|
||||
.frame(width: 20, height: 20)
|
||||
.offset(x: 10, y: -10)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.frame(width: .sidebarWidth, height: 50)
|
||||
}
|
||||
|
||||
private var postButton: some View {
|
||||
Button {
|
||||
routerPath.presentedSheet = .newStatusEditor(visibility: userPreferences.serverPreferences?.postVisibility ?? .pub)
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 20, height: 30)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.keyboardShortcut("n", modifiers: .command)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
VStack(alignment: .center) {
|
||||
Button {
|
||||
selectedTab = .profile
|
||||
} label: {
|
||||
AppAccountsSelectorView(routeurPath: RouterPath(),
|
||||
accountCreationEnabled: false,
|
||||
avatarSize: .status)
|
||||
}
|
||||
.frame(width: 80, height: 60)
|
||||
.background(selectedTab == .profile ? theme.secondaryBackgroundColor : .clear)
|
||||
ForEach(tabs) { tab in
|
||||
Button {
|
||||
if tab == selectedTab {
|
||||
popToRootTab = .other
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
popToRootTab = tab
|
||||
ScrollView {
|
||||
VStack(alignment: .center) {
|
||||
profileView
|
||||
ForEach(tabs) { tab in
|
||||
Button {
|
||||
if tab == selectedTab {
|
||||
popToRootTab = .other
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
popToRootTab = tab
|
||||
}
|
||||
}
|
||||
selectedTab = tab
|
||||
if tab == .notifications {
|
||||
watcher.unreadNotificationsCount = 0
|
||||
userPreferences.pushNotificationsCount = 0
|
||||
}
|
||||
} label: {
|
||||
makeIconForTab(tab: tab)
|
||||
}
|
||||
selectedTab = tab
|
||||
} label: {
|
||||
Image(systemName: tab.iconName)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(tab == selectedTab ? theme.tintColor : .gray)
|
||||
.background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear)
|
||||
}
|
||||
.frame(width: 80, height: 50)
|
||||
.background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear)
|
||||
postButton
|
||||
.padding(.top, 12)
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: 80)
|
||||
.background(.clear)
|
||||
.frame(width: .sidebarWidth)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(.thinMaterial)
|
||||
Divider()
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
content()
|
||||
}
|
||||
.background(.thinMaterial)
|
||||
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import Account
|
||||
import AppAccount
|
||||
import Conversations
|
||||
import Env
|
||||
import Models
|
||||
import Network
|
||||
import Shimmer
|
||||
import SwiftUI
|
||||
|
||||
struct ProfileTab: View {
|
||||
@EnvironmentObject private var client: Client
|
||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||
@StateObject private var routeurPath = RouterPath()
|
||||
@Binding var popToRootTab: Tab
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $routeurPath.path) {
|
||||
if let account = currentAccount.account {
|
||||
AccountDetailView(account: account)
|
||||
.withAppRouteur()
|
||||
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
||||
.toolbar {
|
||||
if UIDevice.current.userInterfaceIdiom != .pad {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
AppAccountsSelectorView(routeurPath: routeurPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
.id(currentAccount.account?.id)
|
||||
} else {
|
||||
AccountDetailView(account: .placeholder())
|
||||
.redacted(reason: .placeholder)
|
||||
.shimmering()
|
||||
}
|
||||
}
|
||||
.onChange(of: $popToRootTab.wrappedValue) { popToRootTab in
|
||||
if popToRootTab == .messages {
|
||||
routeurPath.path = []
|
||||
}
|
||||
}
|
||||
.onChange(of: currentAccount.account?.id) { _ in
|
||||
routeurPath.path = []
|
||||
}
|
||||
.onAppear {
|
||||
routeurPath.client = client
|
||||
}
|
||||
.withSafariRouteur()
|
||||
.environmentObject(routeurPath)
|
||||
}
|
||||
}
|
|
@ -5,4 +5,5 @@ public extension CGFloat {
|
|||
static let dividerPadding: CGFloat = 2
|
||||
static let statusColumnsSpacing: CGFloat = 8
|
||||
static let maxColumnWidth: CGFloat = 650
|
||||
static let sidebarWidth: CGFloat = 80
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
@MainActor
|
||||
enum StatusEditorUTTypeSupported: String, CaseIterable {
|
||||
|
@ -9,6 +10,10 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
|
|||
case image = "public.image"
|
||||
case jpeg = "public.jpeg"
|
||||
case png = "public.png"
|
||||
|
||||
static func types() -> [UTType] {
|
||||
[.url, .text, .plainText, .image, .jpeg, .png]
|
||||
}
|
||||
|
||||
func loadItemContent(item: NSItemProvider) async throws -> Any? {
|
||||
let result = try await item.loadItem(forTypeIdentifier: rawValue)
|
||||
|
|
|
@ -67,6 +67,7 @@ public struct StatusEditorView: View {
|
|||
viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
.onDrop(of: StatusEditorUTTypeSupported.types(), delegate: viewModel)
|
||||
.onAppear {
|
||||
viewModel.client = client
|
||||
viewModel.currentAccount = currentAccount.account
|
||||
|
|
|
@ -238,7 +238,7 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
|
||||
for range in urlRanges {
|
||||
statusText.addAttributes([.foregroundColor: UIColor(theme?.tintColor ?? .brand),
|
||||
.underlineStyle: NSUnderlineStyle.single,
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.underlineColor: UIColor(theme?.tintColor ?? .brand)],
|
||||
range: NSRange(location: range.location, length: range.length))
|
||||
}
|
||||
|
@ -451,3 +451,11 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
data: data)
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusEditorViewModel: DropDelegate {
|
||||
public func performDrop(info: DropInfo) -> Bool {
|
||||
let item = info.itemProviders(for: StatusEditorUTTypeSupported.types())
|
||||
processItemsProvider(items: item)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue