IceCubesApp/IceCubesApp/App/SideBarView.swift

246 lines
8 KiB
Swift
Raw Permalink Normal View History

2023-01-16 18:51:05 +00:00
import Account
2023-01-16 20:15:33 +00:00
import AppAccount
2023-01-17 10:36:01 +00:00
import DesignSystem
import Env
import Models
2023-01-27 19:36:40 +00:00
import SwiftUI
import SwiftUIIntrospect
2023-01-16 18:51:05 +00:00
2023-09-19 07:18:20 +00:00
@MainActor
2023-01-16 18:51:05 +00:00
struct SideBarView<Content: View>: View {
2023-10-23 17:12:25 +00:00
@Environment(\.openWindow) private var openWindow
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
2023-11-01 17:58:44 +00:00
@Environment(AppAccountsManager.self) private var appAccounts
@Environment(CurrentAccount.self) private var currentAccount
2023-09-18 19:03:52 +00:00
@Environment(Theme.self) private var theme
@Environment(StreamWatcher.self) private var watcher
2023-09-19 07:18:20 +00:00
@Environment(UserPreferences.self) private var userPreferences
@Environment(RouterPath.self) private var routerPath
2024-02-14 11:48:14 +00:00
2023-01-16 18:51:05 +00:00
@Binding var selectedTab: Tab
@Binding var popToRootTab: Tab
var tabs: [Tab]
2023-01-16 20:15:33 +00:00
@ViewBuilder var content: () -> Content
2024-02-14 11:48:14 +00:00
@State private var sidebarTabs = SidebarTabs.shared
2023-01-17 10:36:01 +00:00
private func badgeFor(tab: Tab) -> Int {
2023-09-16 12:15:03 +00:00
if tab == .notifications, selectedTab != tab,
2023-02-21 06:23:42 +00:00
let token = appAccounts.currentAccount.oauthToken
{
2023-09-19 06:44:11 +00:00
return watcher.unreadNotificationsCount + (userPreferences.notificationsCount[token] ?? 0)
}
return 0
}
2023-02-21 06:23:42 +00:00
private func makeIconForTab(tab: Tab) -> some View {
2024-05-08 09:51:28 +00:00
HStack {
ZStack(alignment: .topTrailing) {
SideBarIcon(systemIconName: tab.iconName,
isSelected: tab == selectedTab)
let badge = badgeFor(tab: tab)
if badge > 0 {
makeBadgeView(count: badge)
}
}
if userPreferences.isSidebarExpanded {
Text(tab.title)
.font(.headline)
.foregroundColor(tab == selectedTab ? theme.tintColor : theme.labelColor)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
2024-05-08 09:51:28 +00:00
.frame(width: (userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth) - 24, height: 50)
2024-01-16 17:43:09 +00:00
.background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear,
in: RoundedRectangle(cornerRadius: 8))
}
2023-02-21 06:23:42 +00:00
private func makeBadgeView(count: Int) -> some View {
ZStack {
Circle()
.fill(.red)
Text(count > 99 ? "99+" : String(count))
.foregroundColor(.white)
.font(.caption2)
}
.frame(width: 24, height: 24)
.offset(x: 14, y: -14)
}
2023-01-22 05:38:30 +00:00
private var postButton: some View {
Button {
2024-01-09 12:28:57 +00:00
#if targetEnvironment(macCatalyst) || os(visionOS)
2023-12-18 07:22:59 +00:00
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: userPreferences.postVisibility))
#else
routerPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
#endif
} label: {
Image(systemName: "square.and.pencil")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 30)
.offset(x: 2, y: -2)
}
.buttonStyle(.borderedProminent)
.help(Tab.post.title)
}
2023-01-22 05:38:30 +00:00
private func makeAccountButton(account: AppAccount, showBadge: Bool) -> some View {
Button {
if account.id == appAccounts.currentAccount.id {
selectedTab = .profile
SoundEffectManager.shared.playSound(.tabSelection)
} else {
var transation = Transaction()
transation.disablesAnimations = true
withTransaction(transation) {
appAccounts.currentAccount = account
}
}
} label: {
ZStack(alignment: .topTrailing) {
2024-05-08 09:51:28 +00:00
if userPreferences.isSidebarExpanded {
AppAccountView(viewModel: .init(appAccount: account,
isCompact: false,
isInSettings: false),
isParentPresented: .constant(false))
} else {
AppAccountView(viewModel: .init(appAccount: account,
isCompact: true,
isInSettings: false),
isParentPresented: .constant(false))
}
2024-05-13 07:27:24 +00:00
if !userPreferences.isSidebarExpanded,
showBadge,
2023-02-21 06:23:42 +00:00
let token = account.oauthToken,
2023-09-19 06:44:11 +00:00
let notificationsCount = userPreferences.notificationsCount[token],
notificationsCount > 0
2023-02-21 06:23:42 +00:00
{
2023-09-19 06:44:11 +00:00
makeBadgeView(count: notificationsCount)
}
}
2024-05-08 09:51:28 +00:00
.padding(.leading, userPreferences.isSidebarExpanded ? 16 : 0)
}
.help(accountButtonTitle(accountName: account.accountName))
2024-05-08 09:51:28 +00:00
.frame(width: userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth, height: 50)
.padding(.vertical, 8)
.background(selectedTab == .profile && account.id == appAccounts.currentAccount.id ?
2023-01-22 05:38:30 +00:00
theme.secondaryBackgroundColor : .clear)
}
2023-01-22 05:38:30 +00:00
private func accountButtonTitle(accountName: String?) -> LocalizedStringKey {
if let accountName {
"tab.profile-account-\(accountName)"
} else {
Tab.profile.title
}
}
private var tabsView: some View {
ForEach(tabs) { tab in
if tab != .profile && sidebarTabs.isEnabled(tab) {
2024-01-04 15:42:03 +00:00
Button {
// ensure keyboard is always dismissed when selecting a tab
hideKeyboard()
2023-07-19 05:46:25 +00:00
2024-01-04 15:42:03 +00:00
if tab == selectedTab {
popToRootTab = .other
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
popToRootTab = tab
}
}
2024-01-04 15:42:03 +00:00
selectedTab = tab
SoundEffectManager.shared.playSound(.tabSelection)
if tab == .notifications {
if let token = appAccounts.currentAccount.oauthToken {
userPreferences.notificationsCount[token] = 0
}
watcher.unreadNotificationsCount = 0
}
2024-01-04 15:42:03 +00:00
} label: {
makeIconForTab(tab: tab)
}
.help(tab.title)
}
}
}
2023-01-22 05:38:30 +00:00
2023-01-16 18:51:05 +00:00
var body: some View {
@Bindable var routerPath = routerPath
2023-01-16 18:51:05 +00:00
HStack(spacing: 0) {
if horizontalSizeClass == .regular {
ScrollView {
VStack(alignment: .center) {
if appAccounts.availableAccounts.isEmpty {
tabsView
} else {
ForEach(appAccounts.availableAccounts) { account in
makeAccountButton(account: account,
showBadge: account.id != appAccounts.currentAccount.id)
if account.id == appAccounts.currentAccount.id {
tabsView
}
2023-01-16 18:51:05 +00:00
}
}
}
}
2024-05-08 09:51:28 +00:00
.frame(width: userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth)
.scrollContentBackground(.hidden)
.background(.thinMaterial)
2024-01-16 17:43:09 +00:00
.safeAreaInset(edge: .bottom, content: {
2024-05-08 09:51:28 +00:00
HStack(spacing: 16) {
2024-01-16 17:43:09 +00:00
postButton
.padding(.vertical, 24)
2024-05-08 09:51:28 +00:00
.padding(.leading, userPreferences.isSidebarExpanded ? 18 : 0)
if userPreferences.isSidebarExpanded {
Text("menu.new-post")
.font(.subheadline)
.foregroundColor(theme.labelColor)
.frame(maxWidth: .infinity, alignment: .leading)
}
2024-01-16 17:43:09 +00:00
}
2024-05-08 09:51:28 +00:00
.frame(width: userPreferences.isSidebarExpanded ? .sidebarWidthExpanded : .sidebarWidth)
2024-01-16 17:43:09 +00:00
.background(.thinMaterial)
})
2024-02-14 11:48:14 +00:00
Divider().edgesIgnoringSafeArea(.all)
2023-01-16 18:51:05 +00:00
}
2023-01-16 20:15:33 +00:00
content()
2023-01-16 18:51:05 +00:00
}
.background(.thinMaterial)
2024-01-04 20:28:45 +00:00
.edgesIgnoringSafeArea(.bottom)
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
2023-01-16 18:51:05 +00:00
}
}
2023-01-19 10:59:25 +00:00
private struct SideBarIcon: View {
2023-09-18 19:03:52 +00:00
@Environment(Theme.self) private var theme
2023-01-22 05:38:30 +00:00
2023-01-19 10:59:25 +00:00
let systemIconName: String
let isSelected: Bool
2023-01-22 05:38:30 +00:00
2023-01-19 10:59:25 +00:00
@State private var isHovered: Bool = false
2023-01-22 05:38:30 +00:00
2023-01-19 10:59:25 +00:00
var body: some View {
Image(systemName: systemIconName)
2023-02-10 11:53:59 +00:00
.font(.title2)
.fontWeight(.medium)
2023-01-19 10:59:25 +00:00
.foregroundColor(isSelected ? theme.tintColor : theme.labelColor)
2024-01-10 07:56:35 +00:00
.symbolVariant(isSelected ? .fill : .none)
2023-01-19 10:59:25 +00:00
.scaleEffect(isHovered ? 0.8 : 1.0)
.onHover { isHovered in
withAnimation(.interpolatingSpring(stiffness: 300, damping: 15)) {
self.isHovered = isHovered
}
}
2024-05-08 09:51:28 +00:00
.frame(width: 50, height: 40)
2023-01-19 10:59:25 +00:00
}
}
2023-06-26 09:45:45 +00:00
extension View {
@MainActor func hideKeyboard() {
2023-07-19 05:46:25 +00:00
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
2023-06-26 09:45:45 +00:00
}