Start new compose design
This commit is contained in:
parent
57990f8339
commit
2fb76e322a
|
@ -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)
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
//}
|
//}
|
||||||
|
|
|
@ -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 {
|
||||||
|
VStack(spacing: 8) {
|
||||||
NewReplyStatusView(draft: draft, mastodonController: mastodonController)
|
NewReplyStatusView(draft: draft, mastodonController: mastodonController)
|
||||||
.listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.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]) {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 :/
|
||||||
|
|
Loading…
Reference in New Issue