From 2fb76e322a889ab6a3910a1ee489d7074ec6cfcf Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 17 Nov 2024 13:14:23 -0500 Subject: [PATCH] Start new compose design --- Packages/ComposeUI/Package.swift | 3 +- .../Views/AttachmentsListSection.swift | 2 +- .../ComposeUI/Views/ComposeDraftView.swift | 57 +++++++++++ .../ComposeUI/Views/ComposeToolbarView.swift | 30 ------ .../Sources/ComposeUI/Views/ComposeView.swift | 46 ++++----- .../ComposeUI/Views/DraftContentEditor.swift | 96 +++++++++++++++++++ .../ComposeUI/Views/NewMainTextView.swift | 11 ++- 7 files changed, 186 insertions(+), 59 deletions(-) create mode 100644 Packages/ComposeUI/Sources/ComposeUI/Views/ComposeDraftView.swift create mode 100644 Packages/ComposeUI/Sources/ComposeUI/Views/DraftContentEditor.swift diff --git a/Packages/ComposeUI/Package.swift b/Packages/ComposeUI/Package.swift index 36abc61b..0cb82d3e 100644 --- a/Packages/ComposeUI/Package.swift +++ b/Packages/ComposeUI/Package.swift @@ -21,13 +21,14 @@ let package = Package( .package(path: "../TuskerComponents"), .package(path: "../MatchedGeometryPresentation"), .package(path: "../TuskerPreferences"), + .package(path: "../UserAccounts"), ], targets: [ // 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. .target( name: "ComposeUI", - dependencies: ["Pachyderm", "InstanceFeatures", "TuskerComponents", "MatchedGeometryPresentation", "TuskerPreferences"], + dependencies: ["Pachyderm", "InstanceFeatures", "TuskerComponents", "MatchedGeometryPresentation", "TuskerPreferences", "UserAccounts"], swiftSettings: [ .swiftLanguageMode(.v5) ]), diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/AttachmentsListSection.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/AttachmentsListSection.swift index deb8da46..187f38c9 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/AttachmentsListSection.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/AttachmentsListSection.swift @@ -128,7 +128,7 @@ private struct TogglePollButton: View { struct AddAttachmentConditionsModifier: ViewModifier { @ObservedObject var draft: Draft - @ObservedObject var instanceFeatures: InstanceFeatures + @EnvironmentObject private var instanceFeatures: InstanceFeatures private var canAddAttachment: Bool { if instanceFeatures.mastodonAttachmentRestrictions { diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeDraftView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeDraftView.swift new file mode 100644 index 00000000..a8b20745 --- /dev/null +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeDraftView.swift @@ -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) + } + } +} diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeToolbarView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeToolbarView.swift index 90929f6b..f5c20b04 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeToolbarView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeToolbarView.swift @@ -48,8 +48,6 @@ struct ComposeToolbarView: View { FormatButtons() 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 { // ComposeToolbarView() //} diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift index 42d89810..0d5c789f 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift @@ -18,14 +18,14 @@ struct ComposeView: View { NavigationStack { navigationRoot } + .environmentObject(mastodonController.instanceFeatures) } private var navigationRoot: some View { ZStack { - List { - listContent + ScrollView { + scrollContent } - .listStyle(.plain) .scrollDismissesKeyboard(.interactively) #if !os(visionOS) && !targetEnvironment(macCatalyst) .modifier(ToolbarSafeAreaInsetModifier()) @@ -49,7 +49,7 @@ struct ComposeView: View { #endif // Have these after the overlays so they barely work instead of not working at all. FB11790805 .modifier(DropAttachmentModifier(draft: draft)) - .modifier(AddAttachmentConditionsModifier(draft: draft, instanceFeatures: mastodonController.instanceFeatures)) + .modifier(AddAttachmentConditionsModifier(draft: draft)) .modifier(NavigationTitleModifier(draft: draft, mastodonController: mastodonController)) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -67,24 +67,26 @@ struct ComposeView: View { } @ViewBuilder - private var listContent: some View { - NewReplyStatusView(draft: draft, mastodonController: mastodonController) - .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) - .listRowSeparator(.hidden) - - NewHeaderView(draft: draft, instanceFeatures: mastodonController.instanceFeatures) - .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) - .listRowSeparator(.hidden) - - ContentWarningTextField(draft: draft, focusedField: $focusedField) - .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) - .listRowSeparator(.hidden) - - NewMainTextView(value: $draft.text, focusedField: $focusedField, handleAttachmentDrop: self.addAttachments) - .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) - .listRowSeparator(.hidden) - - AttachmentsListSection(draft: draft, instanceFeatures: mastodonController.instanceFeatures) + private var scrollContent: some View { + VStack(spacing: 8) { + NewReplyStatusView(draft: draft, mastodonController: mastodonController) + + ComposeDraftView(draft: draft, focusedField: $focusedField) + } + .padding(8) +// NewHeaderView(draft: draft, instanceFeatures: mastodonController.instanceFeatures) +// .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) +// .listRowSeparator(.hidden) +// +// ContentWarningTextField(draft: draft, focusedField: $focusedField) +// .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) +// .listRowSeparator(.hidden) +// +// NewMainTextView(value: $draft.text, focusedField: $focusedField, handleAttachmentDrop: self.addAttachments) +// .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) +// .listRowSeparator(.hidden) +// +// AttachmentsListSection(draft: draft, instanceFeatures: mastodonController.instanceFeatures) } private func addAttachments(_ itemProviders: [NSItemProvider]) { diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/DraftContentEditor.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/DraftContentEditor.swift new file mode 100644 index 00000000..1e5f7e3d --- /dev/null +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/DraftContentEditor.swift @@ -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) + } +} diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift index 0e09e294..17cb8df7 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift @@ -51,9 +51,10 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable { .foregroundColor: UIColor.label, .font: UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: 20)), ] + view.backgroundColor = nil - view.layer.cornerRadius = 5 - view.layer.cornerCurve = .continuous +// view.layer.cornerRadius = 5 +// view.layer.cornerCurve = .continuous // view.layer.shadowColor = UIColor.black.cgColor // view.layer.shadowOpacity = 0.15 // view.layer.shadowOffset = .zero @@ -75,9 +76,9 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable { uiView.isEditable = isEnabled uiView.keyboardType = useTwitterKeyboard ? .twitter : .default - #if !os(visionOS) - uiView.backgroundColor = colorScheme == .dark ? UIColor(fillColor) : .secondarySystemBackground - #endif +// #if !os(visionOS) +// uiView.backgroundColor = colorScheme == .dark ? UIColor(fillColor) : .secondarySystemBackground +// #endif // Trying to set this with the @FocusState binding in onAppear results in the // keyboard not appearing until after the sheet presentation animation completes :/