diff --git a/ViewModels/Sources/ViewModels/CompositionViewModel.swift b/ViewModels/Sources/ViewModels/CompositionViewModel.swift index 4345fcb..66115f8 100644 --- a/ViewModels/Sources/ViewModels/CompositionViewModel.swift +++ b/ViewModels/Sources/ViewModels/CompositionViewModel.swift @@ -16,6 +16,7 @@ public final class CompositionViewModel: ObservableObject, Identifiable { @Published public private(set) var isPostable = false @Published public private(set) var canAddAttachment = true @Published public private(set) var canAddNonImageAttachment = true + @Published public private(set) var remainingCharacters = CompositionViewModel.maxCharacters private var attachmentUploadCancellable: AnyCancellable? @@ -32,10 +33,20 @@ public final class CompositionViewModel: ObservableObject, Identifiable { .map { $0.count < Self.maxAttachmentCount && $1 == nil } .assign(to: &$canAddAttachment) $attachmentViewModels.map(\.isEmpty).assign(to: &$canAddNonImageAttachment) + $text.map { + let tokens = $0.components(separatedBy: " ") + + return tokens.map(\.countShorteningIfURL).reduce(tokens.count - 1, +) + } + .combineLatest($displayContentWarning, $contentWarning) + .map { Self.maxCharacters - ($0 + ($1 ? $2.count : 0)) } + .assign(to: &$remainingCharacters) } } public extension CompositionViewModel { + static let maxCharacters = 500 + typealias Id = UUID enum Event { @@ -93,3 +104,9 @@ extension CompositionViewModel { private extension CompositionViewModel { static let maxAttachmentCount = 4 } + +private extension String { + static let urlCharacterCount = 23 + + var countShorteningIfURL: Int { starts(with: "http://") || starts(with: "https://") ? Self.urlCharacterCount : count } +} diff --git a/Views/CompositionInputAccessoryView.swift b/Views/CompositionInputAccessoryView.swift index ed66a92..f39b21e 100644 --- a/Views/CompositionInputAccessoryView.swift +++ b/Views/CompositionInputAccessoryView.swift @@ -109,6 +109,12 @@ private extension CompositionInputAccessoryView { stackView.addArrangedSubview(UIView()) + let charactersLabel = UILabel() + + stackView.addArrangedSubview(charactersLabel) + charactersLabel.font = .preferredFont(forTextStyle: .callout) + + stackView.addArrangedSubview(addButton) addButton.setImage( UIImage( @@ -121,12 +127,17 @@ private extension CompositionInputAccessoryView { self.parentViewModel.insert(after: self.viewModel) }, for: .touchUpInside) - viewModel.$canAddAttachment - .sink { - mediaButton.isEnabled = $0 - cameraButton.isEnabled = $0 - } - .store(in: &cancellables) + viewModel.$canAddAttachment.sink { + mediaButton.isEnabled = $0 + cameraButton.isEnabled = $0 + } + .store(in: &cancellables) + + viewModel.$remainingCharacters.sink { + charactersLabel.text = String($0) + charactersLabel.textColor = $0 < 0 ? .systemRed : .label + } + .store(in: &cancellables) viewModel.$isPostable .sink { [weak self] in self?.addButton.isEnabled = $0 }