forked from shadowfacts/Tusker
98 lines
3.4 KiB
Swift
98 lines
3.4 KiB
Swift
//
|
|
// DraftEditor.swift
|
|
// ComposeUI
|
|
//
|
|
// Created by Shadowfacts on 11/16/24.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Pachyderm
|
|
import TuskerComponents
|
|
import TuskerPreferences
|
|
|
|
struct DraftEditor: View {
|
|
@ObservedObject var draft: Draft
|
|
@FocusState.Binding var focusedField: FocusableField?
|
|
@Environment(\.currentAccount) private var currentAccount
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: 8) {
|
|
// TODO: scroll effect?
|
|
AvatarView(account: currentAccount)
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
if let currentAccount {
|
|
AccountNameView(account: currentAccount)
|
|
}
|
|
|
|
if draft.contentWarningEnabled {
|
|
ContentWarningTextField(draft: draft, focusedField: $focusedField)
|
|
.transition(.opacity)
|
|
}
|
|
|
|
DraftContentEditor(draft: draft, focusedField: $focusedField)
|
|
|
|
if let poll = draft.poll,
|
|
draft.pollEnabled {
|
|
PollEditor(poll: poll, focusedField: $focusedField)
|
|
.padding(.bottom, 4)
|
|
// So that during the appearance transition, it's behind the text view.
|
|
.zIndex(-1)
|
|
.transition(.move(edge: .top).combined(with: .opacity))
|
|
}
|
|
|
|
AttachmentsSection(draft: draft)
|
|
// We want the padding between the poll/attachments to be part of the poll, so it animates in/out with the transition.
|
|
// Otherwise, when the poll is added, its bottom edge is aligned with the top edge of the attachments
|
|
.padding(.top, draft.pollEnabled ? -4 : 0)
|
|
}
|
|
// These animations are here, because the height of the VStack and the positions of the lower views needs to animate too.
|
|
.animation(.snappy, value: draft.pollEnabled)
|
|
.modifier(PollAnimatingModifier(poll: draft.poll))
|
|
.animation(.snappy, value: draft.contentWarningEnabled)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct AvatarView: View {
|
|
let account: (any AccountProtocol)?
|
|
@PreferenceObserving(\.$avatarStyle) private var avatarStyle
|
|
@Environment(\.composeUIConfig.fetchAvatar) private var fetchAvatar
|
|
|
|
var body: some View {
|
|
AvatarImageView(
|
|
url: account?.avatar,
|
|
size: 50,
|
|
style: avatarStyle == .circle ? .circle : .roundRect,
|
|
fetchAvatar: fetchAvatar
|
|
)
|
|
.accessibilityHidden(true)
|
|
}
|
|
}
|
|
|
|
private struct AccountNameView: View {
|
|
let account: any AccountProtocol
|
|
@Environment(\.composeUIConfig.displayNameLabel) private var displayNameLabel
|
|
|
|
var body: some View {
|
|
HStack(spacing: 4) {
|
|
displayNameLabel(account, .body, 16)
|
|
.lineLimit(1)
|
|
|
|
Text(verbatim: "@\(account.acct)")
|
|
.font(.body.weight(.light))
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Separate modifier because we need to observe the Poll itself, not the draft
|
|
private struct PollAnimatingModifier: ViewModifier {
|
|
@OptionalObservedObject var poll: Poll?
|
|
|
|
func body(content: Content) -> some View {
|
|
content.animation(.snappy, value: poll?.pollOptions.count)
|
|
}
|
|
}
|