feat: add counter and emoji picker activity indicator

This commit is contained in:
CMK 2021-03-26 19:16:19 +08:00
parent 59889cd683
commit 87a6a4df77
13 changed files with 190 additions and 37 deletions

View File

@ -1171,8 +1171,8 @@
DB789A2125F9F76D0071ACA0 /* CollectionViewCell */, DB789A2125F9F76D0071ACA0 /* CollectionViewCell */,
DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */, DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */,
DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */, DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */,
DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */,
DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */, DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */,
DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */,
); );
path = Compose; path = Compose;
sourceTree = "<group>"; sourceTree = "<group>";

View File

@ -85,15 +85,8 @@ extension ComposeStatusSection {
UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut]) { UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut]) {
cell.statusContentWarningEditorView.alpha = 1 cell.statusContentWarningEditorView.alpha = 1
} completion: { _ in } completion: { _ in
if isContentWarningComposing {
cell.statusContentWarningEditorView.textView.becomeFirstResponder()
}
// do nothing // do nothing
} }
// restore responder if needs
if cell.statusContentWarningEditorView.textView.isFirstResponder {
cell.textEditorView.isEditing = true
}
} }
.store(in: &cell.disposeBag) .store(in: &cell.disposeBag)
cell.contentWarningContent cell.contentWarningContent

View File

@ -75,10 +75,10 @@ internal enum Asset {
internal static let buttonDefault = ColorAsset(name: "Colors/buttonDefault") internal static let buttonDefault = ColorAsset(name: "Colors/buttonDefault")
internal static let buttonDisabled = ColorAsset(name: "Colors/buttonDisabled") internal static let buttonDisabled = ColorAsset(name: "Colors/buttonDisabled")
internal static let buttonInactive = ColorAsset(name: "Colors/buttonInactive") internal static let buttonInactive = ColorAsset(name: "Colors/buttonInactive")
internal static let danger = ColorAsset(name: "Colors/danger")
internal static let lightAlertYellow = ColorAsset(name: "Colors/lightAlertYellow") internal static let lightAlertYellow = ColorAsset(name: "Colors/lightAlertYellow")
internal static let lightBackground = ColorAsset(name: "Colors/lightBackground") internal static let lightBackground = ColorAsset(name: "Colors/lightBackground")
internal static let lightBrandBlue = ColorAsset(name: "Colors/lightBrandBlue") internal static let lightBrandBlue = ColorAsset(name: "Colors/lightBrandBlue")
internal static let lightDangerRed = ColorAsset(name: "Colors/lightDangerRed")
internal static let lightDarkGray = ColorAsset(name: "Colors/lightDarkGray") internal static let lightDarkGray = ColorAsset(name: "Colors/lightDarkGray")
internal static let lightDisabled = ColorAsset(name: "Colors/lightDisabled") internal static let lightDisabled = ColorAsset(name: "Colors/lightDisabled")
internal static let lightInactive = ColorAsset(name: "Colors/lightInactive") internal static let lightInactive = ColorAsset(name: "Colors/lightInactive")

View File

@ -10,6 +10,7 @@ import UIKit
import Combine import Combine
protocol ComposeStatusPollOptionCollectionViewCellDelegate: class { protocol ComposeStatusPollOptionCollectionViewCellDelegate: class {
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textFieldDidBeginEditing textField: UITextField)
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?) func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?)
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, pollOptionTextFieldDidReturn: UITextField) func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, pollOptionTextFieldDidReturn: UITextField)
} }
@ -132,6 +133,12 @@ extension ComposeStatusPollOptionCollectionViewCell: DeleteBackwardResponseTextF
// MARK: - UITextFieldDelegate // MARK: - UITextFieldDelegate
extension ComposeStatusPollOptionCollectionViewCell: UITextFieldDelegate { extension ComposeStatusPollOptionCollectionViewCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
delegate?.composeStatusPollOptionCollectionViewCell(self, textFieldDidBeginEditing: textField)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool { func textFieldShouldReturn(_ textField: UITextField) -> Bool {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
if textField === pollOptionView.optionTextField { if textField === pollOptionView.optionTextField {

View File

@ -10,6 +10,7 @@ import UIKit
import Combine import Combine
import PhotosUI import PhotosUI
import Kingfisher import Kingfisher
import MastodonSDK
import TwitterTextEditor import TwitterTextEditor
final class ComposeViewController: UIViewController, NeedsDependency { final class ComposeViewController: UIViewController, NeedsDependency {
@ -102,14 +103,18 @@ final class ComposeViewController: UIViewController, NeedsDependency {
return documentPickerController return documentPickerController
}() }()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
} }
extension ComposeViewController { extension ComposeViewController {
private static func createLayout() -> UICollectionViewLayout { private static func createLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(100)) let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
let item = NSCollectionLayoutItem(layoutSize: itemSize) let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(100)) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group) let section = NSCollectionLayoutSection(group: group)
section.contentInsetsReference = .readableContent section.contentInsetsReference = .readableContent
// section.interGroupSpacing = 10 // section.interGroupSpacing = 10
@ -232,22 +237,61 @@ extension ComposeViewController {
}) })
.store(in: &disposeBag) .store(in: &disposeBag)
// bind publish bar button state
viewModel.isPublishBarButtonItemEnabled viewModel.isPublishBarButtonItemEnabled
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.assign(to: \.isEnabled, on: publishBarButtonItem) .assign(to: \.isEnabled, on: publishBarButtonItem)
.store(in: &disposeBag) .store(in: &disposeBag)
// bind media button toolbar state
viewModel.isMediaToolbarButtonEnabled viewModel.isMediaToolbarButtonEnabled
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.assign(to: \.isEnabled, on: composeToolbarView.mediaButton) .assign(to: \.isEnabled, on: composeToolbarView.mediaButton)
.store(in: &disposeBag) .store(in: &disposeBag)
// bind poll button toolbar state
viewModel.isPollToolbarButtonEnabled viewModel.isPollToolbarButtonEnabled
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.assign(to: \.isEnabled, on: composeToolbarView.pollButton) .assign(to: \.isEnabled, on: composeToolbarView.pollButton)
.store(in: &disposeBag) .store(in: &disposeBag)
// bind custom emojis // bind image picker toolbar state
viewModel.attachmentServices
.receive(on: DispatchQueue.main)
.sink { [weak self] attachmentServices in
guard let self = self else { return }
self.composeToolbarView.mediaButton.isEnabled = attachmentServices.count < 4
self.resetImagePicker()
}
.store(in: &disposeBag)
// bind visibility toolbar UI
viewModel.selectedStatusVisibility
.receive(on: DispatchQueue.main)
.sink { [weak self] type in
guard let self = self else { return }
self.composeToolbarView.visibilityButton.setImage(type.image, for: .normal)
}
.store(in: &disposeBag)
viewModel.characterCount
.receive(on: DispatchQueue.main)
.sink { [weak self] characterCount in
guard let self = self else { return }
let count = ComposeViewModel.composeContentLimit - characterCount
self.composeToolbarView.characterCountLabel.text = "\(count)"
switch count {
case _ where count < 0:
self.composeToolbarView.characterCountLabel.font = .systemFont(ofSize: 24, weight: .bold)
self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.danger.color
default:
self.composeToolbarView.characterCountLabel.font = .systemFont(ofSize: 15, weight: .regular)
self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.Label.secondary.color
}
}
.store(in: &disposeBag)
// bind text editor for custom emojis update event
viewModel.customEmojiViewModel viewModel.customEmojiViewModel
.compactMap { $0?.emojis } .compactMap { $0?.emojis }
.switchToLatest() .switchToLatest()
@ -261,22 +305,24 @@ extension ComposeViewController {
}) })
.store(in: &disposeBag) .store(in: &disposeBag)
// bind image picker toolbar state // bind custom emoji picker UI
viewModel.attachmentServices viewModel.customEmojiViewModel
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] attachmentServices in .map { viewModel -> AnyPublisher<[Mastodon.Entity.Emoji], Never> in
guard let self = self else { return } guard let viewModel = viewModel else {
self.composeToolbarView.mediaButton.isEnabled = attachmentServices.count < 4 return Just([]).eraseToAnyPublisher()
self.resetImagePicker() }
return viewModel.emojis.eraseToAnyPublisher()
} }
.store(in: &disposeBag) .switchToLatest()
.sink(receiveValue: { [weak self] emojis in
viewModel.selectedStatusVisibility
.receive(on: DispatchQueue.main)
.sink { [weak self] type in
guard let self = self else { return } guard let self = self else { return }
self.composeToolbarView.visibilityButton.setImage(type.image, for: .normal) if emojis.isEmpty {
} self.customEmojiPickerInputView.activityIndicatorView.startAnimating()
} else {
self.customEmojiPickerInputView.activityIndicatorView.stopAnimating()
}
})
.store(in: &disposeBag) .store(in: &disposeBag)
} }
@ -317,6 +363,25 @@ extension ComposeViewController {
textEditorView()?.isEditing = true textEditorView()?.isEditing = true
} }
private func contentWarningEditorTextView() -> UITextView? {
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
let items = diffableDataSource.snapshot().itemIdentifiers
for item in items {
switch item {
case .input:
guard let indexPath = diffableDataSource.indexPath(for: item),
let cell = collectionView.cellForItem(at: indexPath) as? ComposeStatusContentCollectionViewCell else {
continue
}
return cell.statusContentWarningEditorView.textView
default:
continue
}
}
return nil
}
private func pollOptionCollectionViewCell(of item: ComposeStatusItem) -> ComposeStatusPollOptionCollectionViewCell? { private func pollOptionCollectionViewCell(of item: ComposeStatusItem) -> ComposeStatusPollOptionCollectionViewCell? {
guard case .pollOption = item else { return nil } guard case .pollOption = item else { return nil }
guard let diffableDataSource = viewModel.diffableDataSource else { return nil } guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
@ -398,7 +463,7 @@ extension ComposeViewController {
imagePicker.delegate = self imagePicker.delegate = self
return imagePicker return imagePicker
} }
} }
extension ComposeViewController { extension ComposeViewController {
@ -587,6 +652,15 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate {
attributedString.addAttributes(attributes, range: match.range) attributedString.addAttributes(attributes, range: match.range)
} }
if string.count > ComposeViewModel.composeContentLimit {
var attributes = [NSAttributedString.Key: Any]()
attributes[.foregroundColor] = Asset.Colors.danger.color
let boundStart = string.index(string.startIndex, offsetBy: ComposeViewModel.composeContentLimit)
let boundEnd = string.endIndex
let range = boundStart..<boundEnd
attributedString.addAttributes(attributes, range: NSRange(range, in: string))
}
completion(attributedString) completion(attributedString)
} }
} }
@ -631,7 +705,20 @@ extension ComposeViewController: ComposeToolbarViewDelegate {
} }
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) { func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) {
// restore first responder for text editor when content warning dismiss
if viewModel.isContentWarningComposing.value {
if contentWarningEditorTextView()?.isFirstResponder == true {
markTextEditorViewBecomeFirstResponser()
}
}
// toggle composing status
viewModel.isContentWarningComposing.value.toggle() viewModel.isContentWarningComposing.value.toggle()
// active content warning after toggled
if viewModel.isContentWarningComposing.value {
contentWarningEditorTextView()?.becomeFirstResponder()
}
} }
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) { func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) {
@ -773,6 +860,14 @@ extension ComposeViewController: ComposeStatusAttachmentCollectionViewCellDelega
// MARK: - ComposeStatusPollOptionCollectionViewCellDelegate // MARK: - ComposeStatusPollOptionCollectionViewCellDelegate
extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelegate { extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelegate {
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textFieldDidBeginEditing textField: UITextField) {
// FIXME: make poll section visible
// DispatchQueue.main.async {
// self.collectionView.scroll(to: .bottom, animated: true)
// }
}
// handle delete backward event for poll option input // handle delete backward event for poll option input
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?) { func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?) {
guard (text ?? "").isEmpty else { return } guard (text ?? "").isEmpty else { return }

View File

@ -55,7 +55,6 @@ extension ComposeViewModel {
self.pollOptionAttributes.value = pollOptionAttributes self.pollOptionAttributes.value = pollOptionAttributes
} }
self.diffableDataSource = diffableDataSource self.diffableDataSource = diffableDataSource
var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusSection, ComposeStatusItem>() var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusSection, ComposeStatusItem>()
snapshot.appendSections([.repliedTo, .status, .attachment, .poll]) snapshot.appendSections([.repliedTo, .status, .attachment, .poll])

View File

@ -5,6 +5,7 @@
// Created by MainasuK Cirno on 2021-3-11. // Created by MainasuK Cirno on 2021-3-11.
// //
import os.log
import UIKit import UIKit
import Combine import Combine
import CoreData import CoreData
@ -14,6 +15,8 @@ import MastodonSDK
final class ComposeViewModel { final class ComposeViewModel {
static let composeContentLimit: Int = 500
var disposeBag = Set<AnyCancellable>() var disposeBag = Set<AnyCancellable>()
// input // input
@ -48,11 +51,13 @@ final class ComposeViewModel {
let isPublishBarButtonItemEnabled = CurrentValueSubject<Bool, Never>(false) let isPublishBarButtonItemEnabled = CurrentValueSubject<Bool, Never>(false)
let isMediaToolbarButtonEnabled = CurrentValueSubject<Bool, Never>(true) let isMediaToolbarButtonEnabled = CurrentValueSubject<Bool, Never>(true)
let isPollToolbarButtonEnabled = CurrentValueSubject<Bool, Never>(true) let isPollToolbarButtonEnabled = CurrentValueSubject<Bool, Never>(true)
let characterCount = CurrentValueSubject<Int, Never>(0)
// custom emojis // custom emojis
var customEmojiViewModelSubscription: AnyCancellable? var customEmojiViewModelSubscription: AnyCancellable?
let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil) let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
let customEmojiPickerInputViewModel = CustomEmojiPickerInputViewModel() let customEmojiPickerInputViewModel = CustomEmojiPickerInputViewModel()
let isLoadingCustomEmoji = CurrentValueSubject<Bool, Never>(false)
// attachment // attachment
let attachmentServices = CurrentValueSubject<[MastodonAttachmentService], Never>([]) let attachmentServices = CurrentValueSubject<[MastodonAttachmentService], Never>([])
@ -109,10 +114,30 @@ final class ComposeViewModel {
} }
.store(in: &disposeBag) .store(in: &disposeBag)
// bind character count
Publishers.CombineLatest3(
composeStatusAttribute.composeContent.eraseToAnyPublisher(),
composeStatusAttribute.isContentWarningComposing.eraseToAnyPublisher(),
composeStatusAttribute.contentWarningContent.eraseToAnyPublisher()
)
.map { composeContent, isContentWarningComposing, contentWarningContent -> Int in
let composeContent = composeContent ?? ""
var count = composeContent.count
if isContentWarningComposing {
count += contentWarningContent.count
}
return count
}
.assign(to: \.value, on: characterCount)
.store(in: &disposeBag)
// bind compose bar button item UI state // bind compose bar button item UI state
let isComposeContentEmpty = composeStatusAttribute.composeContent let isComposeContentEmpty = composeStatusAttribute.composeContent
.map { ($0 ?? "").isEmpty } .map { ($0 ?? "").isEmpty }
let isComposeContentValid = Just(true).eraseToAnyPublisher() let isComposeContentValid = composeStatusAttribute.composeContent
.map { composeContent -> Bool in
let composeContent = composeContent ?? ""
return composeContent.count <= ComposeViewModel.composeContentLimit
}
let isMediaEmpty = attachmentServices let isMediaEmpty = attachmentServices
.map { $0.isEmpty } .map { $0.isEmpty }
let isMediaUploadAllSuccess = attachmentServices let isMediaUploadAllSuccess = attachmentServices
@ -278,6 +303,10 @@ final class ComposeViewModel {
.store(in: &disposeBag) .store(in: &disposeBag)
} }
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
} }
extension ComposeViewModel { extension ComposeViewModel {
@ -301,6 +330,6 @@ extension ComposeViewModel: MastodonAttachmentServiceDelegate {
extension ComposeViewModel: ComposePollAttributeDelegate { extension ComposeViewModel: ComposePollAttributeDelegate {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollOptionAttribute, pollOptionDidChange: String?) { func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollOptionAttribute, pollOptionDidChange: String?) {
// trigger update // trigger update
pollOptionAttributes.value = pollOptionAttributes.value // pollOptionAttributes.value = pollOptionAttributes.value
} }
} }

View File

@ -59,6 +59,14 @@ final class ComposeToolbarView: UIView {
return button return button
}() }()
let characterCountLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 15, weight: .regular)
label.text = "500"
label.textColor = Asset.Colors.Label.secondary.color
return label
}()
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
_init() _init()
@ -102,6 +110,16 @@ extension ComposeToolbarView {
]) ])
} }
characterCountLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(characterCountLabel)
NSLayoutConstraint.activate([
characterCountLabel.topAnchor.constraint(equalTo: topAnchor),
characterCountLabel.leadingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor, constant: 8),
characterCountLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
characterCountLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
])
characterCountLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
mediaButton.menu = createMediaContextMenu() mediaButton.menu = createMediaContextMenu()
mediaButton.showsMenuAsPrimaryAction = true mediaButton.showsMenuAsPrimaryAction = true
pollButton.addTarget(self, action: #selector(ComposeToolbarView.pollButtonDidPressed(_:)), for: .touchUpInside) pollButton.addTarget(self, action: #selector(ComposeToolbarView.pollButtonDidPressed(_:)), for: .touchUpInside)

View File

@ -17,6 +17,8 @@ final class CustomEmojiPickerInputView: UIInputView {
return collectionView return collectionView
}() }()
let activityIndicatorView = UIActivityIndicatorView(style: .large)
override init(frame: CGRect, inputViewStyle: UIInputView.Style) { override init(frame: CGRect, inputViewStyle: UIInputView.Style) {
super.init(frame: frame, inputViewStyle: inputViewStyle) super.init(frame: frame, inputViewStyle: inputViewStyle)
_init() _init()
@ -33,6 +35,13 @@ extension CustomEmojiPickerInputView {
private func _init() { private func _init() {
allowsSelfSizing = true allowsSelfSizing = true
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
addSubview(activityIndicatorView)
NSLayoutConstraint.activate([
activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor),
activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor),
])
collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.translatesAutoresizingMaskIntoConstraints = false
addSubview(collectionView) addSubview(collectionView)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
@ -41,6 +50,9 @@ extension CustomEmojiPickerInputView {
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor), collectionView.bottomAnchor.constraint(equalTo: bottomAnchor),
]) ])
activityIndicatorView.hidesWhenStopped = true
activityIndicatorView.startAnimating()
} }
} }

View File

@ -15,7 +15,7 @@ final class HomeTimelineNavigationBarView {
}() }()
static let offlineView: UIView = { static let offlineView: UIView = {
let view = HomeTimelineNavigationBarView.backgroundViewWithColor(color: Asset.Colors.lightDangerRed.color) let view = HomeTimelineNavigationBarView.backgroundViewWithColor(color: Asset.Colors.danger.color)
let label = HomeTimelineNavigationBarView.contentLabel(text: L10n.Scene.HomeTimeline.NavigationBarState.offline) let label = HomeTimelineNavigationBarView.contentLabel(text: L10n.Scene.HomeTimeline.NavigationBarState.offline)
HomeTimelineNavigationBarView.addLabelToView(label: label, view: view) HomeTimelineNavigationBarView.addLabelToView(label: label, view: view)
return view return view

View File

@ -106,7 +106,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let usernameErrorPromptLabel: UILabel = { let usernameErrorPromptLabel: UILabel = {
let label = UILabel() let label = UILabel()
let color = Asset.Colors.lightDangerRed.color let color = Asset.Colors.danger.color
let font = UIFont.preferredFont(forTextStyle: .caption1) let font = UIFont.preferredFont(forTextStyle: .caption1)
return label return label
}() }()
@ -146,7 +146,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let emailErrorPromptLabel: UILabel = { let emailErrorPromptLabel: UILabel = {
let label = UILabel() let label = UILabel()
let color = Asset.Colors.lightDangerRed.color let color = Asset.Colors.danger.color
let font = UIFont.preferredFont(forTextStyle: .caption1) let font = UIFont.preferredFont(forTextStyle: .caption1)
return label return label
}() }()
@ -177,7 +177,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let passwordErrorPromptLabel: UILabel = { let passwordErrorPromptLabel: UILabel = {
let label = UILabel() let label = UILabel()
let color = Asset.Colors.lightDangerRed.color let color = Asset.Colors.danger.color
let font = UIFont.preferredFont(forTextStyle: .caption1) let font = UIFont.preferredFont(forTextStyle: .caption1)
return label return label
}() }()
@ -201,7 +201,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
let reasonErrorPromptLabel: UILabel = { let reasonErrorPromptLabel: UILabel = {
let label = UILabel() let label = UILabel()
let color = Asset.Colors.lightDangerRed.color let color = Asset.Colors.danger.color
let font = UIFont.preferredFont(forTextStyle: .caption1) let font = UIFont.preferredFont(forTextStyle: .caption1)
return label return label
}() }()

View File

@ -198,10 +198,10 @@ extension MastodonRegisterViewModel {
let attributeString = NSMutableAttributedString() let attributeString = NSMutableAttributedString()
let image = MastodonRegisterViewModel.xmarkImage(font: font) let image = MastodonRegisterViewModel.xmarkImage(font: font)
attributeString.append(attributedStringImage(with: image, tintColor: Asset.Colors.lightDangerRed.color)) attributeString.append(attributedStringImage(with: image, tintColor: Asset.Colors.danger.color))
attributeString.append(NSAttributedString(string: " ")) attributeString.append(NSAttributedString(string: " "))
let promptAttributedString = NSAttributedString(string: prompt, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: Asset.Colors.lightDangerRed.color]) let promptAttributedString = NSAttributedString(string: prompt, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: Asset.Colors.danger.color])
attributeString.append(promptAttributedString) attributeString.append(promptAttributedString)
return attributeString return attributeString