Start new compose design

This commit is contained in:
Shadowfacts 2024-11-17 13:14:23 -05:00
parent 57990f8339
commit 2fb76e322a
7 changed files with 186 additions and 59 deletions

View File

@ -21,13 +21,14 @@ let package = Package(
.package(path: "../TuskerComponents"), .package(path: "../TuskerComponents"),
.package(path: "../MatchedGeometryPresentation"), .package(path: "../MatchedGeometryPresentation"),
.package(path: "../TuskerPreferences"), .package(path: "../TuskerPreferences"),
.package(path: "../UserAccounts"),
], ],
targets: [ targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on. // Targets can depend on other targets in this package, and on products in packages this package depends on.
.target( .target(
name: "ComposeUI", name: "ComposeUI",
dependencies: ["Pachyderm", "InstanceFeatures", "TuskerComponents", "MatchedGeometryPresentation", "TuskerPreferences"], dependencies: ["Pachyderm", "InstanceFeatures", "TuskerComponents", "MatchedGeometryPresentation", "TuskerPreferences", "UserAccounts"],
swiftSettings: [ swiftSettings: [
.swiftLanguageMode(.v5) .swiftLanguageMode(.v5)
]), ]),

View File

@ -128,7 +128,7 @@ private struct TogglePollButton: View {
struct AddAttachmentConditionsModifier: ViewModifier { struct AddAttachmentConditionsModifier: ViewModifier {
@ObservedObject var draft: Draft @ObservedObject var draft: Draft
@ObservedObject var instanceFeatures: InstanceFeatures @EnvironmentObject private var instanceFeatures: InstanceFeatures
private var canAddAttachment: Bool { private var canAddAttachment: Bool {
if instanceFeatures.mastodonAttachmentRestrictions { if instanceFeatures.mastodonAttachmentRestrictions {

View File

@ -0,0 +1,57 @@
//
// ComposeDraftView.swift
// ComposeUI
//
// Created by Shadowfacts on 11/16/24.
//
import SwiftUI
import Pachyderm
import TuskerComponents
struct ComposeDraftView: View {
@ObservedObject var draft: Draft
@FocusState.Binding var focusedField: FocusableField?
@Environment(\.currentAccount) private var currentAccount
@EnvironmentObject private var controller: ComposeController
var body: some View {
HStack(alignment: .top, spacing: 8) {
// TODO: scroll effect?
AvatarImageView(
url: currentAccount?.avatar,
size: 50,
style: controller.config.avatarStyle,
fetchAvatar: controller.fetchAvatar
)
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 4) {
if let currentAccount {
AccountNameView(account: currentAccount)
}
ContentWarningTextField(draft: draft, focusedField: $focusedField)
DraftContentEditor(draft: draft, focusedField: $focusedField)
}
}
}
}
private struct AccountNameView: View {
let account: any AccountProtocol
@EnvironmentObject private var controller: ComposeController
var body: some View {
HStack(spacing: 4) {
controller.displayNameLabel(account, .body, 16)
.lineLimit(1)
Text(verbatim: "@\(account.acct)")
.font(.body.weight(.light))
.foregroundColor(.secondary)
.lineLimit(1)
}
}
}

View File

@ -48,8 +48,6 @@ struct ComposeToolbarView: View {
FormatButtons() FormatButtons()
Spacer() Spacer()
LangaugeButton(draft: draft, instanceFeatures: mastodonController.instanceFeatures)
} }
} }
} }
@ -239,34 +237,6 @@ private struct FormatButton: View {
} }
} }
private struct LangaugeButton: View {
@ObservedObject var draft: Draft
@ObservedObject var instanceFeatures: InstanceFeatures
@FocusedValue(\.composeInput) private var input
@State private var hasChanged = false
var body: some View {
if instanceFeatures.createStatusWithLanguage {
LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $hasChanged)
.onReceive(NotificationCenter.default.publisher(for: UITextInputMode.currentInputModeDidChangeNotification), perform: currentInputModeChanged)
.onChange(of: draft.id) { _ in
hasChanged = false
}
}
}
@available(iOS 16.0, *)
private func currentInputModeChanged(_ notification: Foundation.Notification) {
guard !hasChanged,
!draft.hasContent,
let mode = input?.textInputMode,
let code = LanguagePicker.codeFromInputMode(mode) else {
return
}
draft.language = code.identifier
}
}
//#Preview { //#Preview {
// ComposeToolbarView() // ComposeToolbarView()
//} //}

View File

@ -18,14 +18,14 @@ struct ComposeView: View {
NavigationStack { NavigationStack {
navigationRoot navigationRoot
} }
.environmentObject(mastodonController.instanceFeatures)
} }
private var navigationRoot: some View { private var navigationRoot: some View {
ZStack { ZStack {
List { ScrollView {
listContent scrollContent
} }
.listStyle(.plain)
.scrollDismissesKeyboard(.interactively) .scrollDismissesKeyboard(.interactively)
#if !os(visionOS) && !targetEnvironment(macCatalyst) #if !os(visionOS) && !targetEnvironment(macCatalyst)
.modifier(ToolbarSafeAreaInsetModifier()) .modifier(ToolbarSafeAreaInsetModifier())
@ -49,7 +49,7 @@ struct ComposeView: View {
#endif #endif
// Have these after the overlays so they barely work instead of not working at all. FB11790805 // Have these after the overlays so they barely work instead of not working at all. FB11790805
.modifier(DropAttachmentModifier(draft: draft)) .modifier(DropAttachmentModifier(draft: draft))
.modifier(AddAttachmentConditionsModifier(draft: draft, instanceFeatures: mastodonController.instanceFeatures)) .modifier(AddAttachmentConditionsModifier(draft: draft))
.modifier(NavigationTitleModifier(draft: draft, mastodonController: mastodonController)) .modifier(NavigationTitleModifier(draft: draft, mastodonController: mastodonController))
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
@ -67,24 +67,26 @@ struct ComposeView: View {
} }
@ViewBuilder @ViewBuilder
private var listContent: some View { private var scrollContent: some View {
NewReplyStatusView(draft: draft, mastodonController: mastodonController) VStack(spacing: 8) {
.listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) NewReplyStatusView(draft: draft, mastodonController: mastodonController)
.listRowSeparator(.hidden)
NewHeaderView(draft: draft, instanceFeatures: mastodonController.instanceFeatures) ComposeDraftView(draft: draft, focusedField: $focusedField)
.listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) }
.listRowSeparator(.hidden) .padding(8)
// NewHeaderView(draft: draft, instanceFeatures: mastodonController.instanceFeatures)
ContentWarningTextField(draft: draft, focusedField: $focusedField) // .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
.listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) // .listRowSeparator(.hidden)
.listRowSeparator(.hidden) //
// ContentWarningTextField(draft: draft, focusedField: $focusedField)
NewMainTextView(value: $draft.text, focusedField: $focusedField, handleAttachmentDrop: self.addAttachments) // .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
.listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) // .listRowSeparator(.hidden)
.listRowSeparator(.hidden) //
// NewMainTextView(value: $draft.text, focusedField: $focusedField, handleAttachmentDrop: self.addAttachments)
AttachmentsListSection(draft: draft, instanceFeatures: mastodonController.instanceFeatures) // .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
// .listRowSeparator(.hidden)
//
// AttachmentsListSection(draft: draft, instanceFeatures: mastodonController.instanceFeatures)
} }
private func addAttachments(_ itemProviders: [NSItemProvider]) { private func addAttachments(_ itemProviders: [NSItemProvider]) {

View File

@ -0,0 +1,96 @@
//
// DraftContentEditor.swift
// ComposeUI
//
// Created by Shadowfacts on 11/16/24.
//
import SwiftUI
import InstanceFeatures
struct DraftContentEditor: View {
@ObservedObject var draft: Draft
@FocusState.Binding var focusedField: FocusableField?
@Environment(\.colorScheme) private var colorScheme
@Environment(\.composeUIConfig.fillColor) private var fillColor
var body: some View {
VStack(spacing: 4) {
NewMainTextView(value: $draft.text, focusedField: $focusedField, handleAttachmentDrop: self.addAttachments)
HStack(alignment: .firstTextBaseline) {
LanguageButton(draft: draft)
Spacer()
CharactersRemaining(draft: draft)
.padding(.trailing, 4)
}
.padding(.all.subtracting(.top), 2)
}
.background {
RoundedRectangle(cornerRadius: 5)
.fill(colorScheme == .dark ? fillColor : Color(uiColor: .secondarySystemBackground))
}
}
private func addAttachments(_ providers: [NSItemProvider]) {
AttachmentsListSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: providers)
}
}
private struct CharactersRemaining: View {
@ObservedObject var draft: Draft
@EnvironmentObject var instanceFeatures: InstanceFeatures
private var charsRemaining: Int {
let limit = instanceFeatures.maxStatusChars
let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0
return limit - (cwCount + CharacterCounter.count(text: draft.text, for: instanceFeatures))
}
var body: some View {
Text(verbatim: charsRemaining.description)
.foregroundStyle(charsRemaining < 0 ? .red : .secondary)
.font(.callout.monospacedDigit())
.accessibility(label: Text(charsRemaining < 0 ? "\(-charsRemaining) characters too many" : "\(charsRemaining) characters remaining"))
}
}
private struct LanguageButton: View {
@ObservedObject var draft: Draft
@EnvironmentObject private var instanceFeatures: InstanceFeatures
@FocusedValue(\.composeInput) private var input
@State private var hasChanged = false
var body: some View {
if instanceFeatures.createStatusWithLanguage {
LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $hasChanged)
.buttonStyle(LanguageButtonStyle())
.onReceive(NotificationCenter.default.publisher(for: UITextInputMode.currentInputModeDidChangeNotification), perform: currentInputModeChanged)
.onChange(of: draft.id) { _ in
hasChanged = false
}
}
}
private func currentInputModeChanged(_ notification: Foundation.Notification) {
guard !hasChanged,
!draft.hasContent,
let mode = input?.textInputMode,
let code = LanguagePicker.codeFromInputMode(mode) else {
return
}
draft.language = code.identifier
}
}
private struct LanguageButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.callout)
.foregroundStyle(.tint.opacity(configuration.isPressed ? 0.8 : 1))
.padding(.vertical, 2)
.padding(.horizontal, 4)
.background(.tint.opacity(configuration.isPressed ? 0.15 : 0.2), in: RoundedRectangle(cornerRadius: 3))
.animation(.linear(duration: 0.1), value: configuration.isPressed)
}
}

View File

@ -51,9 +51,10 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable {
.foregroundColor: UIColor.label, .foregroundColor: UIColor.label,
.font: UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: 20)), .font: UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: 20)),
] ]
view.backgroundColor = nil
view.layer.cornerRadius = 5 // view.layer.cornerRadius = 5
view.layer.cornerCurve = .continuous // view.layer.cornerCurve = .continuous
// view.layer.shadowColor = UIColor.black.cgColor // view.layer.shadowColor = UIColor.black.cgColor
// view.layer.shadowOpacity = 0.15 // view.layer.shadowOpacity = 0.15
// view.layer.shadowOffset = .zero // view.layer.shadowOffset = .zero
@ -75,9 +76,9 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable {
uiView.isEditable = isEnabled uiView.isEditable = isEnabled
uiView.keyboardType = useTwitterKeyboard ? .twitter : .default uiView.keyboardType = useTwitterKeyboard ? .twitter : .default
#if !os(visionOS) // #if !os(visionOS)
uiView.backgroundColor = colorScheme == .dark ? UIColor(fillColor) : .secondarySystemBackground // uiView.backgroundColor = colorScheme == .dark ? UIColor(fillColor) : .secondarySystemBackground
#endif // #endif
// Trying to set this with the @FocusState binding in onAppear results in the // Trying to set this with the @FocusState binding in onAppear results in the
// keyboard not appearing until after the sheet presentation animation completes :/ // keyboard not appearing until after the sheet presentation animation completes :/