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
|
2023-01-15 15:39:08 +00:00
|
|
|
import UIKit
|
2023-02-16 11:14:57 +00:00
|
|
|
import StoreKit
|
2022-12-23 09:41:55 +00:00
|
|
|
|
|
|
|
public struct StatusEditorView: View {
|
2023-02-17 12:30:45 +00:00
|
|
|
@EnvironmentObject private var appAccounts: AppAccountsManager
|
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
|
2023-02-15 07:46:14 +00:00
|
|
|
@EnvironmentObject private var routerPath: RouterPath
|
2022-12-23 09:41:55 +00:00
|
|
|
@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-02-12 17:24:09 +00:00
|
|
|
@State private var isLanguageConfirmPresented = 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))
|
2022-12-23 09:41:55 +00:00
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-23 09:41:55 +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
|
2023-01-03 06:41:29 +00:00
|
|
|
.padding(.horizontal, .layoutPadding)
|
2023-02-06 11:24:57 +00:00
|
|
|
TextView($viewModel.statusText,
|
|
|
|
getTextView: { textView in
|
2023-02-12 15:29:41 +00:00
|
|
|
viewModel.textView = textView
|
|
|
|
})
|
|
|
|
.placeholder(String(localized: "status.editor.text.placeholder"))
|
|
|
|
.setKeyboardType(preferences.isSocialKeyboardEnabled ? .twitter : .default)
|
|
|
|
.padding(.horizontal, .layoutPadding)
|
2023-01-05 17:54:18 +00:00
|
|
|
StatusEditorMediaView(viewModel: viewModel)
|
2023-01-17 14:14:50 +00:00
|
|
|
if let status = viewModel.embeddedStatus {
|
2023-02-15 07:46:14 +00:00
|
|
|
StatusEmbeddedView(status: status, client: client, routerPath: routerPath)
|
2023-01-03 06:41:29 +00:00
|
|
|
.padding(.horizontal, .layoutPadding)
|
2023-01-05 17:54:18 +00:00
|
|
|
.disabled(true)
|
|
|
|
} else if let status = viewModel.replyToStatus {
|
|
|
|
Divider()
|
|
|
|
.padding(.top, 20)
|
2023-02-15 07:46:14 +00:00
|
|
|
StatusEmbeddedView(status: status, client: client, routerPath: routerPath)
|
2023-01-05 17:54:18 +00:00
|
|
|
.padding(.horizontal, .layoutPadding)
|
|
|
|
.disabled(true)
|
2022-12-27 12:38:10 +00:00
|
|
|
}
|
2023-01-13 06:30:15 +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)
|
2022-12-31 08:10:27 +00:00
|
|
|
.padding(.bottom, 40)
|
|
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
|
|
StatusEditorAutoCompleteView(viewModel: viewModel)
|
|
|
|
StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused,
|
|
|
|
viewModel: viewModel)
|
2022-12-25 07:17:16 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-17 12:02:05 +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
|
2023-01-24 20:34:16 +00:00
|
|
|
viewModel.preferences = preferences
|
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
|
|
|
|
2023-01-18 18:11:52 +00:00
|
|
|
Task {
|
|
|
|
await viewModel.fetchCustomEmojis()
|
|
|
|
}
|
2022-12-23 09:41:55 +00:00
|
|
|
}
|
2023-01-18 07:07:09 +00:00
|
|
|
.onChange(of: currentAccount.account?.id, perform: { _ in
|
|
|
|
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)
|
2022-12-23 09:41:55 +00:00
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
2023-01-22 08:09:35 +00:00
|
|
|
.alert("Error while posting",
|
|
|
|
isPresented: $viewModel.showPostingErrorAlert,
|
|
|
|
actions: {
|
2023-01-25 12:02:28 +00:00
|
|
|
Button("Ok") {}
|
|
|
|
}, message: {
|
|
|
|
Text(viewModel.postingError ?? "")
|
|
|
|
})
|
2022-12-23 09:41:55 +00:00
|
|
|
.toolbar {
|
|
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
|
|
Button {
|
2022-12-25 07:17:16 +00:00
|
|
|
Task {
|
2023-02-12 17:24:09 +00:00
|
|
|
viewModel.evaluateLanguages()
|
|
|
|
if let _ = viewModel.languageConfirmationDialogLanguages {
|
|
|
|
isLanguageConfirmPresented = true
|
|
|
|
} else {
|
|
|
|
await postStatus()
|
|
|
|
}
|
2022-12-25 07:17:16 +00:00
|
|
|
}
|
2022-12-23 09:41:55 +00:00
|
|
|
} label: {
|
2022-12-25 16:46:51 +00:00
|
|
|
if viewModel.isPosting {
|
|
|
|
ProgressView()
|
|
|
|
} else {
|
2023-01-19 17:14:08 +00:00
|
|
|
Text("status.action.post")
|
2022-12-25 16:46:51 +00:00
|
|
|
}
|
2022-12-23 09:41:55 +00:00
|
|
|
}
|
2023-01-10 13:44:11 +00:00
|
|
|
.disabled(!viewModel.canPost)
|
2023-01-18 07:27:42 +00:00
|
|
|
.keyboardShortcut(.return, modifiers: .command)
|
2023-02-12 17:23:29 +00:00
|
|
|
.confirmationDialog("", isPresented: $isLanguageConfirmPresented, actions: {
|
2023-02-12 17:24:09 +00:00
|
|
|
languageConfirmationDialog
|
2023-02-12 17:23:29 +00:00
|
|
|
})
|
2022-12-23 09:41:55 +00:00
|
|
|
}
|
|
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
|
|
Button {
|
2023-01-22 10:28:23 +00:00
|
|
|
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
|
|
|
}
|
2022-12-23 09:41:55 +00:00
|
|
|
} label: {
|
2023-01-19 17:14:08 +00:00
|
|
|
Text("action.cancel")
|
2022-12-23 09:41:55 +00:00
|
|
|
}
|
2023-01-18 07:27:42 +00:00
|
|
|
.keyboardShortcut(.cancelAction)
|
2023-01-16 21:03:04 +00:00
|
|
|
.confirmationDialog("",
|
|
|
|
isPresented: $isDismissAlertPresented,
|
|
|
|
actions: {
|
2023-01-19 17:14:08 +00:00
|
|
|
Button("status.draft.delete", role: .destructive) {
|
2023-01-17 10:36:01 +00:00
|
|
|
dismiss()
|
|
|
|
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
|
|
|
|
object: nil)
|
|
|
|
}
|
2023-01-19 17:14:08 +00:00
|
|
|
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)
|
|
|
|
}
|
2023-01-19 17:14:08 +00:00
|
|
|
Button("action.cancel", role: .cancel) {}
|
2023-01-17 10:36:01 +00:00
|
|
|
})
|
2022-12-23 09:41:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-17 07:09:26 +00:00
|
|
|
.interactiveDismissDisabled(!viewModel.shouldDisplayDismissWarning)
|
2023-02-17 12:30:45 +00:00
|
|
|
.onChange(of: appAccounts.currentClient) { newClient in
|
2023-02-17 07:09:26 +00:00
|
|
|
if viewModel.mode.isInShareExtension {
|
|
|
|
currentAccount.setClient(client: newClient)
|
2023-02-17 12:30:45 +00:00
|
|
|
viewModel.client = newClient
|
2023-02-17 07:09:26 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-23 09:41:55 +00:00
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2023-02-12 17:24:09 +00:00
|
|
|
@ViewBuilder
|
|
|
|
private var languageConfirmationDialog: some View {
|
|
|
|
if let dialogVals = viewModel.languageConfirmationDialogLanguages,
|
|
|
|
let detected = dialogVals["detected"],
|
|
|
|
let detectedLong = Locale.current.localizedString(forLanguageCode: detected),
|
|
|
|
let selected = dialogVals["selected"],
|
|
|
|
let selectedLong = Locale.current.localizedString(forLanguageCode: selected)
|
|
|
|
{
|
|
|
|
Button("status.editor.language-select.confirmation.detected-\(detectedLong)") {
|
|
|
|
viewModel.selectedLanguage = detected
|
|
|
|
Task {
|
|
|
|
await postStatus()
|
2023-02-12 17:23:29 +00:00
|
|
|
}
|
2023-02-12 17:24:09 +00:00
|
|
|
}
|
|
|
|
Button("status.editor.language-select.confirmation.selected-\(selectedLong)") {
|
|
|
|
viewModel.selectedLanguage = selected
|
|
|
|
Task {
|
|
|
|
await postStatus()
|
2023-02-12 17:23:29 +00:00
|
|
|
}
|
2023-02-12 17:24:09 +00:00
|
|
|
}
|
|
|
|
Button("action.cancel", role: .cancel) {
|
|
|
|
viewModel.languageConfirmationDialogLanguages = nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
EmptyView()
|
2023-02-12 17:23:29 +00:00
|
|
|
}
|
2023-02-12 17:24:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private func postStatus() async {
|
|
|
|
let status = await viewModel.postStatus()
|
|
|
|
if status != nil {
|
|
|
|
dismiss()
|
|
|
|
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
|
|
|
|
object: nil)
|
2023-02-16 11:14:57 +00:00
|
|
|
if !viewModel.mode.isInShareExtension && !preferences.requestedReview {
|
2023-02-16 12:22:04 +00:00
|
|
|
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
|
|
|
|
SKStoreReviewController.requestReview(in: scene)
|
|
|
|
}
|
2023-02-16 11:14:57 +00:00
|
|
|
preferences.requestedReview = true
|
|
|
|
}
|
2023-02-12 17:24:09 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-12 17:23:29 +00:00
|
|
|
|
2022-12-28 09:45:05 +00:00
|
|
|
@ViewBuilder
|
|
|
|
private var spoilerTextView: some View {
|
|
|
|
if viewModel.spoilerOn {
|
|
|
|
VStack {
|
2023-01-19 17:14:08 +00:00
|
|
|
TextField("status.editor.spoiler", text: $viewModel.spoilerText)
|
2022-12-28 09:45:05 +00:00
|
|
|
.focused($isSpoilerTextFocused)
|
2023-01-03 06:41:29 +00:00
|
|
|
.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 {
|
2023-02-15 05:44:51 +00:00
|
|
|
if let account = currentAccount.account, !viewModel.mode.isEditing {
|
2022-12-25 05:55:33 +00:00
|
|
|
HStack {
|
2023-02-17 13:02:05 +00:00
|
|
|
if viewModel.mode.isInShareExtension {
|
|
|
|
AppAccountsSelectorView(routerPath: RouterPath(),
|
|
|
|
accountCreationEnabled: false,
|
|
|
|
avatarSize: .status)
|
|
|
|
} else {
|
|
|
|
AvatarView(url: account.avatar, size: .status)
|
|
|
|
}
|
2023-01-11 11:44:34 +00:00
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
|
|
privacyMenu
|
2023-02-17 13:02:05 +00:00
|
|
|
Text("@\(account.acct)@\(appAccounts.currentClient.server)")
|
2023-01-17 18:41:46 +00:00
|
|
|
.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 {
|
2023-01-19 17:14:08 +00:00
|
|
|
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")
|
|
|
|
}
|
2023-01-17 18:41:46 +00:00
|
|
|
.font(.scaledFootnote)
|
2023-01-11 11:44:34 +00:00
|
|
|
.padding(4)
|
|
|
|
.overlay(
|
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
|
.stroke(theme.tintColor, lineWidth: 1)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2022-12-23 09:41:55 +00:00
|
|
|
}
|