Impressia/Vernissage/Views/MainView.swift

364 lines
12 KiB
Swift
Raw Normal View History

2022-12-29 08:06:21 +01:00
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
2023-03-28 10:35:38 +02:00
// Licensed under the Apache License 2.0.
2022-12-29 08:06:21 +01:00
//
import SwiftUI
import UIKit
import CoreData
2023-02-19 10:32:38 +01:00
import PixelfedKit
2022-12-29 08:06:21 +01:00
struct MainView: View {
2022-12-29 08:06:21 +01:00
@Environment(\.managedObjectContext) private var viewContext
2023-02-03 15:16:30 +01:00
2022-12-30 18:20:54 +01:00
@EnvironmentObject var applicationState: ApplicationState
2023-02-03 15:16:30 +01:00
@EnvironmentObject var client: Client
2023-01-23 18:01:27 +01:00
@EnvironmentObject var routerPath: RouterPath
2023-02-14 07:32:00 +01:00
@EnvironmentObject var tipsStore: TipsStore
2023-03-13 13:53:36 +01:00
@State private var navBarTitle: LocalizedStringKey = "mainview.tab.homeTimeline"
2022-12-30 18:20:54 +01:00
@State private var viewMode: ViewMode = .home {
didSet {
2022-12-31 16:31:05 +01:00
self.navBarTitle = self.getViewTitle(viewMode: viewMode)
2022-12-30 18:20:54 +01:00
}
}
2023-01-11 13:16:43 +01:00
@FetchRequest(sortDescriptors: [SortDescriptor(\.acct, order: .forward)]) var dbAccounts: FetchedResults<AccountData>
2022-12-30 18:20:54 +01:00
private enum ViewMode {
2023-03-06 16:00:53 +01:00
case home, local, federated, profile, notifications, trendingPhotos, trendingTags, trendingAccounts, search
2022-12-30 18:20:54 +01:00
}
2022-12-29 08:06:21 +01:00
var body: some View {
2022-12-30 18:20:54 +01:00
self.getMainView()
2023-04-06 13:19:55 +02:00
.navigationMenu(menuPosition: $applicationState.menuPosition) {
self.navigationMenuContent()
}
.navigationTitle(navBarTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
self.getLeadingToolbar()
if self.applicationState.menuPosition == .top {
self.getPrincipalToolbar()
}
self.getTrailingToolbar()
}
.onChange(of: tipsStore.status) { status in
if status == .successful {
withAnimation(.spring()) {
self.routerPath.presentedOverlay = .successPayment
self.tipsStore.reset()
}
2023-02-13 21:10:07 +01:00
}
}
2022-12-30 18:20:54 +01:00
}
2022-12-30 18:20:54 +01:00
@ViewBuilder
private func getMainView() -> some View {
switch self.viewMode {
case .home:
HomeFeedView(accountId: applicationState.account?.id ?? String.empty())
.id(applicationState.account?.id ?? String.empty())
case .trendingPhotos:
TrendStatusesView(accountId: applicationState.account?.id ?? String.empty())
.id(applicationState.account?.id ?? String.empty())
case .trendingTags:
2023-03-07 16:45:44 +01:00
HashtagsView(listType: .trending)
.id(applicationState.account?.id ?? String.empty())
case .trendingAccounts:
2023-03-08 12:07:43 +01:00
AccountsPhotoView(listType: .trending)
.id(applicationState.account?.id ?? String.empty())
2022-12-30 18:20:54 +01:00
case .local:
2023-01-23 18:01:27 +01:00
StatusesView(listType: .local)
.id(applicationState.account?.id ?? String.empty())
2022-12-30 18:20:54 +01:00
case .federated:
2023-01-23 18:01:27 +01:00
StatusesView(listType: .federated)
.id(applicationState.account?.id ?? String.empty())
2023-01-05 21:08:19 +01:00
case .profile:
if let accountData = self.applicationState.account {
2023-01-05 21:08:19 +01:00
UserProfileView(accountId: accountData.id,
accountDisplayName: accountData.displayName,
2023-01-06 13:05:21 +01:00
accountUserName: accountData.acct)
.id(applicationState.account?.id ?? String.empty())
2023-01-05 21:08:19 +01:00
}
2022-12-30 18:20:54 +01:00
case .notifications:
if let accountData = self.applicationState.account {
2023-01-18 18:41:42 +01:00
NotificationsView(accountId: accountData.id)
.id(applicationState.account?.id ?? String.empty())
2023-01-18 18:41:42 +01:00
}
2023-03-06 16:00:53 +01:00
case .search:
SearchView()
.id(applicationState.account?.id ?? String.empty())
2022-12-30 18:20:54 +01:00
}
}
2022-12-30 18:20:54 +01:00
@ToolbarContentBuilder
private func getPrincipalToolbar() -> some ToolbarContent {
ToolbarItem(placement: .principal) {
Menu {
2023-04-06 13:19:55 +02:00
self.navigationMenuContent()
2022-12-30 18:20:54 +01:00
} label: {
HStack {
2023-03-13 13:53:36 +01:00
Text(navBarTitle, comment: "Navbar title")
2022-12-30 18:20:54 +01:00
.font(.headline)
Image(systemName: "chevron.down")
2023-02-19 10:16:01 +01:00
.fontWeight(.semibold)
2022-12-30 18:20:54 +01:00
.font(.subheadline)
}
.frame(width: 150)
2023-01-06 13:05:21 +01:00
.foregroundColor(.mainTextColor)
2022-12-29 08:06:21 +01:00
}
}
2022-12-30 18:20:54 +01:00
}
2022-12-30 18:20:54 +01:00
@ToolbarContentBuilder
private func getLeadingToolbar() -> some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
2023-01-03 20:42:20 +01:00
Menu {
2023-01-11 13:16:43 +01:00
ForEach(self.dbAccounts) { account in
Button {
2023-03-08 12:07:43 +01:00
self.switchAccounts(account)
2023-01-11 13:16:43 +01:00
} label: {
2023-03-12 08:39:12 +01:00
HStack {
2023-01-11 13:16:43 +01:00
Text(account.displayName ?? account.acct)
2023-03-12 08:39:12 +01:00
self.getAvatarImage(avatarUrl: account.avatar, avatarData: account.avatarData)
2023-01-11 13:16:43 +01:00
}
2023-01-03 20:42:20 +01:00
}
2023-03-12 08:39:12 +01:00
.disabled(account.id == self.applicationState.account?.id)
2023-01-03 20:42:20 +01:00
}
Divider()
2023-01-03 20:42:20 +01:00
Button {
2023-02-19 12:49:44 +01:00
HapticService.shared.fireHaptic(of: .buttonPress)
2023-01-23 18:01:27 +01:00
self.routerPath.presentedSheet = .settings
2023-01-03 20:42:20 +01:00
} label: {
2023-03-13 13:53:36 +01:00
Label("mainview.menu.settings", systemImage: "gear")
2023-01-03 20:42:20 +01:00
}
2022-12-30 18:20:54 +01:00
} label: {
2023-03-12 08:39:12 +01:00
self.getAvatarImage(avatarUrl: self.applicationState.account?.avatar,
avatarData: self.applicationState.account?.avatarData)
2022-12-29 08:06:21 +01:00
}
}
}
2023-01-12 18:34:48 +01:00
@ToolbarContentBuilder
private func getTrailingToolbar() -> some ToolbarContent {
2023-03-06 16:00:53 +01:00
if viewMode == .local || viewMode == .home || viewMode == .federated || viewMode == .trendingPhotos || viewMode == .search {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
2023-02-19 12:49:44 +01:00
HapticService.shared.fireHaptic(of: .buttonPress)
2023-01-23 18:01:27 +01:00
self.routerPath.presentedSheet = .newStatusEditor
} label: {
Image(systemName: "square.and.pencil")
2023-03-19 08:09:44 +01:00
.foregroundColor(Color.mainTextColor)
2023-02-19 10:16:01 +01:00
.fontWeight(.semibold)
}
2023-01-12 18:34:48 +01:00
}
}
}
2023-04-06 13:19:55 +02:00
@ViewBuilder
private func navigationMenuContent() -> some View {
Button {
self.switchView(to: .home)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .home))
Image(systemName: "house")
}
}
Button {
self.switchView(to: .local)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .local))
Image(systemName: "building")
}
}
Button {
self.switchView(to: .federated)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .federated))
Image(systemName: "globe.europe.africa")
}
}
Button {
self.switchView(to: .search)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .search))
Image(systemName: "magnifyingglass")
}
}
Divider()
Menu {
Button {
self.switchView(to: .trendingPhotos)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .trendingPhotos))
Image(systemName: "photo.stack")
}
}
Button {
self.switchView(to: .trendingTags)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .trendingTags))
Image(systemName: "tag")
}
}
Button {
self.switchView(to: .trendingAccounts)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .trendingAccounts))
Image(systemName: "person.3")
}
}
} label: {
HStack {
Text("mainview.tab.trending", comment: "Trending menu section")
Image(systemName: "chart.line.uptrend.xyaxis")
}
}
Divider()
Button {
self.switchView(to: .profile)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .profile))
Image(systemName: "person.crop.circle")
}
}
Button {
self.switchView(to: .notifications)
} label: {
HStack {
Text(self.getViewTitle(viewMode: .notifications))
Image(systemName: "bell.badge")
}
}
}
2023-03-12 08:39:12 +01:00
@ViewBuilder
private func getAvatarImage(avatarUrl: URL?, avatarData: Data?) -> some View {
if let avatarData,
let uiImage = UIImage(data: avatarData)?.roundedAvatar(avatarShape: self.applicationState.avatarShape) {
Image(uiImage: uiImage)
.resizable()
.frame(width: 32.0, height: 32.0)
.clipShape(self.applicationState.avatarShape.shape())
} else if let avatarUrl {
AsyncImage(url: avatarUrl)
.frame(width: 32.0, height: 32.0)
.clipShape(self.applicationState.avatarShape.shape())
} else {
Image(systemName: "person")
.resizable()
.frame(width: 16, height: 16)
.foregroundColor(.white)
.padding(8)
.background(Color.lightGrayColor)
.clipShape(AvatarShape.circle.shape())
.background(
AvatarShape.circle.shape()
)
}
}
2023-03-13 13:53:36 +01:00
private func getViewTitle(viewMode: ViewMode) -> LocalizedStringKey {
2022-12-31 16:31:05 +01:00
switch viewMode {
case .home:
2023-03-13 13:53:36 +01:00
return "mainview.tab.homeTimeline"
case .trendingPhotos:
2023-03-13 13:53:36 +01:00
return "mainview.tab.trendingPhotos"
case .trendingTags:
2023-03-13 13:53:36 +01:00
return "mainview.tab.trendingTags"
case .trendingAccounts:
2023-03-13 13:53:36 +01:00
return "mainview.tab.trendingAccounts"
2022-12-31 16:31:05 +01:00
case .local:
2023-03-13 13:53:36 +01:00
return "mainview.tab.localTimeline"
2022-12-31 16:31:05 +01:00
case .federated:
2023-03-13 13:53:36 +01:00
return "mainview.tab.federatedTimeline"
2023-01-05 21:08:19 +01:00
case .profile:
2023-03-13 13:53:36 +01:00
return "mainview.tab.userProfile"
2022-12-31 16:31:05 +01:00
case .notifications:
2023-03-13 13:53:36 +01:00
return "mainview.tab.notifications"
2023-03-06 16:00:53 +01:00
case .search:
2023-03-13 13:53:36 +01:00
return "mainview.tab.search"
2022-12-29 08:06:21 +01:00
}
}
2023-03-06 16:29:37 +01:00
private func switchView(to newViewMode: ViewMode) {
HapticService.shared.fireHaptic(of: .tabSelection)
2023-03-06 16:29:37 +01:00
if viewMode == .search {
hideKeyboard()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.viewMode = newViewMode
}
} else {
self.viewMode = newViewMode
}
}
2023-03-08 12:07:43 +01:00
private func switchAccounts(_ account: AccountData) {
HapticService.shared.fireHaptic(of: .buttonPress)
2023-03-08 12:07:43 +01:00
if viewMode == .search {
hideKeyboard()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.tryToSwitch(account)
}
} else {
self.tryToSwitch(account)
}
}
2023-02-12 09:13:04 +01:00
private func tryToSwitch(_ account: AccountData) {
Task {
// Verify access token correctness.
let authorizationSession = AuthorizationSession()
2023-03-29 17:06:41 +02:00
let accountModel = AccountModel(accountData: account)
2023-03-29 17:06:41 +02:00
await AuthorizationService.shared.verifyAccount(session: authorizationSession, accountModel: accountModel) { signedInAccountModel in
guard let signedInAccountModel else {
2023-03-13 13:53:36 +01:00
ToastrService.shared.showError(subtitle: "mainview.error.switchAccounts")
2023-02-12 09:13:04 +01:00
return
}
Task { @MainActor in
2023-03-29 17:06:41 +02:00
let instance = try? await self.client.instances.instance(url: signedInAccountModel.serverUrl)
// Refresh client state.
2023-03-29 17:06:41 +02:00
self.client.setAccount(account: signedInAccountModel)
// Refresh application state.
2023-03-29 17:06:41 +02:00
self.applicationState.changeApplicationState(accountModel: signedInAccountModel,
instance: instance,
2023-03-29 17:06:41 +02:00
lastSeenStatusId: signedInAccountModel.lastSeenStatusId)
// Set account as default (application will open this account after restart).
2023-03-29 17:06:41 +02:00
ApplicationSettingsHandler.shared.set(accountId: signedInAccountModel.id)
2023-02-12 09:13:04 +01:00
}
}
}
}
2022-12-29 08:06:21 +01:00
}