IceCubesApp/Packages/Status/Sources/Status/Editor/StatusEditorView.swift

251 lines
8.2 KiB
Swift
Raw Normal View History

2022-12-25 05:55:33 +00:00
import Accounts
2023-01-17 10:36:01 +00:00
import AppAccount
2022-12-25 05:55:33 +00:00
import DesignSystem
2023-01-17 10:36:01 +00:00
import EmojiText
import Env
2022-12-25 07:17:16 +00:00
import Models
import Network
2022-12-27 15:16:25 +00:00
import NukeUI
2023-01-17 10:36:01 +00:00
import PhotosUI
import SwiftUI
import TextView
2023-01-15 15:39:08 +00:00
import UIKit
public struct StatusEditorView: View {
2023-01-11 11:44:34 +00:00
@EnvironmentObject private var preferences: UserPreferences
2022-12-29 09:39:34 +00:00
@EnvironmentObject private var theme: Theme
2022-12-25 07:17:16 +00:00
@EnvironmentObject private var client: Client
2022-12-25 05:55:33 +00:00
@EnvironmentObject private var currentAccount: CurrentAccount
@Environment(\.dismiss) private var dismiss
2023-01-17 10:36:01 +00:00
2022-12-25 07:17:16 +00:00
@StateObject private var viewModel: StatusEditorViewModel
2022-12-28 09:45:05 +00:00
@FocusState private var isSpoilerTextFocused: Bool
2023-01-17 10:36:01 +00:00
2023-01-11 11:44:34 +00:00
@State private var isDismissAlertPresented: Bool = false
2023-01-13 17:43:02 +00:00
@State private var isLoadingAIRequest: Bool = false
2023-01-17 10:36:01 +00:00
2022-12-26 07:24:55 +00:00
public init(mode: StatusEditorViewModel.Mode) {
_viewModel = StateObject(wrappedValue: .init(mode: mode))
}
2023-01-17 10:36:01 +00:00
public var body: some View {
NavigationStack {
2022-12-25 07:17:16 +00:00
ZStack(alignment: .bottom) {
2022-12-27 12:38:10 +00:00
ScrollView {
2022-12-27 18:10:31 +00:00
Divider()
2022-12-28 09:45:05 +00:00
spoilerTextView
2022-12-27 12:38:10 +00:00
VStack(spacing: 12) {
accountHeaderView
.padding(.horizontal, .layoutPadding)
TextView($viewModel.statusText, $viewModel.selectedRange, $viewModel.markedTextRange)
.placeholder(String(localized: "status.editor.text.placeholder"))
2023-01-18 07:04:52 +00:00
.font(Font.scaledBodyUIFont)
2023-01-22 15:55:03 +00:00
.keyboardType(.twitter)
.padding(.horizontal, .layoutPadding)
2023-01-05 17:54:18 +00:00
StatusEditorMediaView(viewModel: viewModel)
if let status = viewModel.embeddedStatus {
StatusEmbeddedView(status: status)
.padding(.horizontal, .layoutPadding)
2023-01-05 17:54:18 +00:00
.disabled(true)
} else if let status = viewModel.replyToStatus {
Divider()
.padding(.top, 20)
StatusEmbeddedView(status: status)
2023-01-05 17:54:18 +00:00
.padding(.horizontal, .layoutPadding)
.disabled(true)
2022-12-27 12:38:10 +00:00
}
if viewModel.showPoll {
StatusEditorPollView(viewModel: viewModel, showPoll: $viewModel.showPoll)
.padding(.horizontal)
}
2022-12-27 12:38:10 +00:00
Spacer()
}
2022-12-27 18:10:31 +00:00
.padding(.top, 8)
.padding(.bottom, 40)
}
VStack(alignment: .leading, spacing: 0) {
StatusEditorAutoCompleteView(viewModel: viewModel)
StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused,
viewModel: viewModel)
2022-12-25 07:17:16 +00:00
}
}
.onDrop(of: StatusEditorUTTypeSupported.types(), delegate: viewModel)
2022-12-25 07:17:16 +00:00
.onAppear {
viewModel.client = client
2022-12-30 21:49:09 +00:00
viewModel.currentAccount = currentAccount.account
2022-12-31 11:11:42 +00:00
viewModel.theme = theme
2022-12-26 07:24:55 +00:00
viewModel.prepareStatusText()
2022-12-27 07:31:57 +00:00
if !client.isAuth {
dismiss()
2023-01-15 15:39:08 +00:00
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
object: nil)
2022-12-27 07:31:57 +00:00
}
2023-01-22 05:38:30 +00:00
Task {
await viewModel.fetchCustomEmojis()
}
}
.onChange(of: currentAccount.account?.id, perform: { _ in
viewModel.client = client
viewModel.currentAccount = currentAccount.account
})
2022-12-29 09:39:34 +00:00
.background(theme.primaryBackgroundColor)
2022-12-26 07:24:55 +00:00
.navigationTitle(viewModel.mode.title)
.navigationBarTitleDisplayMode(.inline)
.alert("Error while posting",
isPresented: $viewModel.showPostingErrorAlert,
actions: {
Button("Ok") { }
}, message: {
Text(viewModel.postingError ?? "")
})
.toolbar {
if preferences.isOpenAIEnabled {
ToolbarItem(placement: .navigationBarTrailing) {
AIMenu
.disabled(!viewModel.canPost)
}
2023-01-13 17:43:02 +00:00
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
2022-12-25 07:17:16 +00:00
Task {
2022-12-25 16:46:51 +00:00
let status = await viewModel.postStatus()
if status != nil {
dismiss()
2023-01-15 15:39:08 +00:00
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
object: nil)
2022-12-25 16:46:51 +00:00
}
2022-12-25 07:17:16 +00:00
}
} label: {
2022-12-25 16:46:51 +00:00
if viewModel.isPosting {
ProgressView()
} else {
Text("status.action.post")
2022-12-25 16:46:51 +00:00
}
}
.disabled(!viewModel.canPost)
2023-01-18 07:27:42 +00:00
.keyboardShortcut(.return, modifiers: .command)
}
ToolbarItem(placement: .navigationBarLeading) {
Button {
if viewModel.shouldDisplayDismissWarning {
2023-01-11 11:44:34 +00:00
isDismissAlertPresented = true
} else {
dismiss()
2023-01-15 15:39:08 +00:00
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
object: nil)
2023-01-11 11:44:34 +00:00
}
} label: {
Text("action.cancel")
}
2023-01-18 07:27:42 +00:00
.keyboardShortcut(.cancelAction)
.confirmationDialog("",
isPresented: $isDismissAlertPresented,
actions: {
Button("status.draft.delete", role: .destructive) {
2023-01-17 10:36:01 +00:00
dismiss()
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
object: nil)
}
Button("status.draft.save") {
2023-01-17 10:36:01 +00:00
preferences.draftsPosts.insert(viewModel.statusText.string, at: 0)
dismiss()
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
object: nil)
}
Button("action.cancel", role: .cancel) {}
2023-01-17 10:36:01 +00:00
})
}
}
}
2023-01-11 11:44:34 +00:00
.interactiveDismissDisabled(!viewModel.statusText.string.isEmpty)
}
2023-01-17 10:36:01 +00:00
2022-12-28 09:45:05 +00:00
@ViewBuilder
private var spoilerTextView: some View {
if viewModel.spoilerOn {
VStack {
TextField("status.editor.spoiler", text: $viewModel.spoilerText)
2022-12-28 09:45:05 +00:00
.focused($isSpoilerTextFocused)
.padding(.horizontal, .layoutPadding)
2022-12-28 09:45:05 +00:00
}
.frame(height: 35)
2022-12-29 09:39:34 +00:00
.background(theme.tintColor.opacity(0.20))
2022-12-28 09:45:05 +00:00
.offset(y: -8)
}
}
2023-01-17 10:36:01 +00:00
2022-12-25 18:15:35 +00:00
@ViewBuilder
private var accountHeaderView: some View {
2022-12-25 05:55:33 +00:00
if let account = currentAccount.account {
HStack {
AppAccountsSelectorView(routerPath: RouterPath(),
2023-01-16 12:39:35 +00:00
accountCreationEnabled: false,
avatarSize: .status)
2023-01-11 11:44:34 +00:00
VStack(alignment: .leading, spacing: 4) {
privacyMenu
2022-12-25 05:55:33 +00:00
Text("@\(account.acct)")
.font(.scaledFootnote)
2022-12-25 05:55:33 +00:00
.foregroundColor(.gray)
}
Spacer()
}
}
}
2023-01-17 10:36:01 +00:00
2023-01-11 11:44:34 +00:00
private var privacyMenu: some View {
Menu {
Section("status.editor.visibility") {
2023-01-11 11:44:34 +00:00
ForEach(Models.Visibility.allCases, id: \.self) { visibility in
Button {
viewModel.visibility = visibility
} label: {
Label(visibility.title, systemImage: visibility.iconName)
}
}
}
} label: {
HStack {
Label(viewModel.visibility.title, systemImage: viewModel.visibility.iconName)
Image(systemName: "chevron.down")
}
.font(.scaledFootnote)
2023-01-11 11:44:34 +00:00
.padding(4)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(theme.tintColor, lineWidth: 1)
)
}
}
2023-01-17 10:36:01 +00:00
2023-01-13 17:43:02 +00:00
private var AIMenu: some View {
Menu {
ForEach(StatusEditorAIPrompts.allCases, id: \.self) { prompt in
Button {
Task {
isLoadingAIRequest = true
await viewModel.runOpenAI(prompt: prompt.toRequestPrompt(text: viewModel.statusText.string))
isLoadingAIRequest = false
}
} label: {
prompt.label
}
}
if let backup = viewModel.backupStatusText {
2023-01-13 17:43:02 +00:00
Button {
viewModel.replaceTextWith(text: backup.string)
viewModel.backupStatusText = nil
2023-01-13 17:43:02 +00:00
} label: {
Label("status.editor.restore-previous", systemImage: "arrow.uturn.right")
2023-01-13 17:43:02 +00:00
}
}
} label: {
if isLoadingAIRequest {
ProgressView()
} else {
Image(systemName: "faxmachine")
}
}
}
}