diff --git a/Localization/app.json b/Localization/app.json index ab5b3f659..8734ea00f 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -178,7 +178,9 @@ "title": { "new_toot": "New Toot", "new_reply": "New Reply" - } + }, + "content_input_placeholder": "Type or paste what's on your mind", + "compose_action": "Toot" } } } \ No newline at end of file diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index d999c0e81..532ef6cbe 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -202,6 +202,7 @@ DB9D6C2425E502C60051B173 /* MosaicImageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C2225E502C60051B173 /* MosaicImageViewModel.swift */; }; DB9D6C2E25E504AC0051B173 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C2D25E504AC0051B173 /* Attachment.swift */; }; DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C3725E508BE0051B173 /* Attachment.swift */; }; + DBA0A10925FB3C2B0079C110 /* RoundedEdgesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA0A10825FB3C2B0079C110 /* RoundedEdgesButton.swift */; }; DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; }; DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; }; DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; }; @@ -464,6 +465,7 @@ DB9D6C2225E502C60051B173 /* MosaicImageViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MosaicImageViewModel.swift; sourceTree = ""; }; DB9D6C2D25E504AC0051B173 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = ""; }; DB9D6C3725E508BE0051B173 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = ""; }; + DBA0A10825FB3C2B0079C110 /* RoundedEdgesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedEdgesButton.swift; sourceTree = ""; }; DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = ""; }; DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = ""; }; DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewController.swift; sourceTree = ""; }; @@ -649,6 +651,7 @@ 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */, DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */, 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */, + DBA0A10825FB3C2B0079C110 /* RoundedEdgesButton.swift */, ); path = Button; sourceTree = ""; @@ -1633,6 +1636,7 @@ 2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */, DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, + DBA0A10925FB3C2B0079C110 /* RoundedEdgesButton.swift in Sources */, 2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */, 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */, DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */, diff --git a/Mastodon/Diffiable/Item/ComposeStatusItem.swift b/Mastodon/Diffiable/Item/ComposeStatusItem.swift index 2e125f636..812a27a6f 100644 --- a/Mastodon/Diffiable/Item/ComposeStatusItem.swift +++ b/Mastodon/Diffiable/Item/ComposeStatusItem.swift @@ -6,11 +6,34 @@ // import Foundation +import Combine import CoreData enum ComposeStatusItem { case replyTo(tootObjectID: NSManagedObjectID) - case toot(replyToTootObjectID: NSManagedObjectID?) + case toot(replyToTootObjectID: NSManagedObjectID?, attribute: ComposeTootAttribute) } extension ComposeStatusItem: Hashable { } + +extension ComposeStatusItem { + final class ComposeTootAttribute: Equatable, Hashable { + private let id = UUID() + + let avatarURL = CurrentValueSubject(nil) + let displayName = CurrentValueSubject(nil) + let username = CurrentValueSubject(nil) + let composeContent = CurrentValueSubject(nil) + + static func == (lhs: ComposeTootAttribute, rhs: ComposeTootAttribute) -> Bool { + return lhs.avatarURL.value == rhs.avatarURL.value && + lhs.displayName.value == rhs.displayName.value && + lhs.username.value == rhs.username.value && + lhs.composeContent.value == rhs.composeContent.value + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + } +} diff --git a/Mastodon/Diffiable/Section/ComposeStatusSection.swift b/Mastodon/Diffiable/Section/ComposeStatusSection.swift index 56b006892..e1405309b 100644 --- a/Mastodon/Diffiable/Section/ComposeStatusSection.swift +++ b/Mastodon/Diffiable/Section/ComposeStatusSection.swift @@ -5,9 +5,82 @@ // Created by MainasuK Cirno on 2021-3-11. // -import Foundation +import UIKit +import Combine +import CoreData +import CoreDataStack enum ComposeStatusSection: Equatable, Hashable { case repliedTo case status } + +extension ComposeStatusSection { + enum ComposeKind { + case toot + case replyToot(tootObjectID: NSManagedObjectID) + } +} + +extension ComposeStatusSection { + static func tableViewDiffableDataSource( + for tableView: UITableView, + dependency: NeedsDependency, + managedObjectContext: NSManagedObjectContext, + composeKind: ComposeKind + ) -> UITableViewDiffableDataSource { + UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in + switch item { + case .replyTo(let tootObjectID): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeRepliedToTootContentTableViewCell.self), for: indexPath) as! ComposeRepliedToTootContentTableViewCell + // TODO: + return cell + case .toot(let replyToTootObjectID, let attribute): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeTootContentTableViewCell.self), for: indexPath) as! ComposeTootContentTableViewCell + managedObjectContext.perform { + guard let replyToTootObjectID = replyToTootObjectID, + let replyTo = managedObjectContext.object(with: replyToTootObjectID) as? Toot else { + cell.statusView.headerContainerStackView.isHidden = true + return + } + cell.statusView.headerContainerStackView.isHidden = false + cell.statusView.headerInfoLabel.text = "[TODO] \(replyTo.author.displayName)" + } + ComposeStatusSection.configureComposeTootContent(cell: cell, attribute: attribute) + // self size input cell + cell.composeContent + .receive(on: DispatchQueue.main) + .sink { text in + tableView.beginUpdates() + tableView.endUpdates() + } + .store(in: &cell.disposeBag) + return cell + } + } + } +} + +extension ComposeStatusSection { + static func configureComposeTootContent( + cell: ComposeTootContentTableViewCell, + attribute: ComposeStatusItem.ComposeTootAttribute + ) { + attribute.avatarURL + .receive(on: DispatchQueue.main) + .sink { avatarURL in + cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: avatarURL)) + } + .store(in: &cell.disposeBag) + Publishers.CombineLatest( + attribute.displayName.eraseToAnyPublisher(), + attribute.username.eraseToAnyPublisher() + ) + .receive(on: DispatchQueue.main) + .sink { displayName, username in + cell.statusView.nameLabel.text = displayName + cell.statusView.usernameLabel.text = username + } + .store(in: &cell.disposeBag) + } +} diff --git a/Mastodon/Generated/Assets.swift b/Mastodon/Generated/Assets.swift index f68170460..f573d2d12 100644 --- a/Mastodon/Generated/Assets.swift +++ b/Mastodon/Generated/Assets.swift @@ -48,6 +48,7 @@ internal enum Asset { internal static let actionToolbar = ColorAsset(name: "Colors/Button/action.toolbar") internal static let disabled = ColorAsset(name: "Colors/Button/disabled") internal static let highlight = ColorAsset(name: "Colors/Button/highlight") + internal static let normal = ColorAsset(name: "Colors/Button/normal") } internal enum Icon { internal static let photo = ColorAsset(name: "Colors/Icon/photo") diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 6df84fb7d..49e4cd7c2 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -128,6 +128,10 @@ internal enum L10n { internal enum Scene { internal enum Compose { + /// Toot + internal static let composeAction = L10n.tr("Localizable", "Scene.Compose.ComposeAction") + /// Type or paste what's on your mind + internal static let contentInputPlaceholder = L10n.tr("Localizable", "Scene.Compose.ContentInputPlaceholder") internal enum Title { /// New Reply internal static let newReply = L10n.tr("Localizable", "Scene.Compose.Title.NewReply") diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Button/disabled.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Button/disabled.colorset/Contents.json index 78cde95fb..bca754614 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Button/disabled.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Button/disabled.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.784", - "green" : "0.682", - "red" : "0.608" + "blue" : "140", + "green" : "130", + "red" : "110" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Button/normal.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Button/normal.colorset/Contents.json new file mode 100644 index 000000000..d853a71aa --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Colors/Button/normal.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "217", + "green" : "144", + "red" : "43" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index a83819ddc..f9a1ffe64 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -34,6 +34,8 @@ "Common.Controls.Timeline.LoadMore" = "Load More"; "Common.Countable.Photo.Multiple" = "photos"; "Common.Countable.Photo.Single" = "photo"; +"Scene.Compose.ComposeAction" = "Toot"; +"Scene.Compose.ContentInputPlaceholder" = "Type or paste what's on your mind"; "Scene.Compose.Title.NewReply" = "New Reply"; "Scene.Compose.Title.NewToot" = "New Toot"; "Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email"; diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index adab1dd91..f183bb255 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -18,6 +18,20 @@ final class ComposeViewController: UIViewController, NeedsDependency { var disposeBag = Set() var viewModel: ComposeViewModel! + let composeTootBarButtonItem: UIBarButtonItem = { + let button = RoundedEdgesButton(type: .custom) + button.setTitle(L10n.Scene.Compose.composeAction, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold) + button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.normal.color), for: .normal) + button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.normal.color.withAlphaComponent(0.5)), for: .highlighted) + button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled) + button.setTitleColor(.white, for: .normal) + button.contentEdgeInsets = UIEdgeInsets(top: 3, left: 16, bottom: 3, right: 16) + button.adjustsImageWhenHighlighted = false + let barButtonItem = UIBarButtonItem(customView: button) + return barButtonItem + }() + let tableView: UITableView = { let tableView = ControlContainableTableView() tableView.register(ComposeRepliedToTootContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeRepliedToTootContentTableViewCell.self)) @@ -34,7 +48,6 @@ extension ComposeViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = Asset.Colors.Background.systemBackground.color viewModel.title .receive(on: DispatchQueue.main) .sink { [weak self] title in @@ -42,7 +55,10 @@ extension ComposeViewController { self.title = title } .store(in: &disposeBag) + view.backgroundColor = Asset.Colors.Background.systemBackground.color navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:))) + navigationItem.rightBarButtonItem = composeTootBarButtonItem + tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) @@ -54,9 +70,7 @@ extension ComposeViewController { ]) tableView.delegate = self - viewModel.setupDiffableDataSource(for: tableView) - - + viewModel.setupDiffableDataSource(for: tableView, dependency: self) } override func viewWillAppear(_ animated: Bool) { @@ -111,7 +125,9 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate { // MARK: - UITableViewDelegate extension ComposeViewController: UITableViewDelegate { - + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension + } } // MARK: - ComposeViewController diff --git a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift index 772bb97b6..5c27bf51d 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift @@ -9,30 +9,25 @@ import UIKit extension ComposeViewModel { - func setupDiffableDataSource(for tableView: UITableView) { - diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, item -> UITableViewCell? in - guard let self = self else { return nil } - - switch item { - case .replyTo(let tootObjectID): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeRepliedToTootContentTableViewCell.self), for: indexPath) as! ComposeRepliedToTootContentTableViewCell - // TODO: - return cell - case .toot(let attribute): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeTootContentTableViewCell.self), for: indexPath) as! ComposeTootContentTableViewCell - // TODO: - return cell - } - } + func setupDiffableDataSource( + for tableView: UITableView, + dependency: NeedsDependency + ) { + diffableDataSource = ComposeStatusSection.tableViewDiffableDataSource( + for: tableView, + dependency: dependency, + managedObjectContext: context.managedObjectContext, + composeKind: composeKind + ) var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.repliedTo, .status]) switch composeKind { case .replyToot(let tootObjectID): snapshot.appendItems([.replyTo(tootObjectID: tootObjectID)], toSection: .repliedTo) - snapshot.appendItems([.toot(replyToTootObjectID: tootObjectID)], toSection: .status) + snapshot.appendItems([.toot(replyToTootObjectID: tootObjectID, attribute: composeTootAttribute)], toSection: .status) case .toot: - snapshot.appendItems([.toot(replyToTootObjectID: nil)], toSection: .status) + snapshot.appendItems([.toot(replyToTootObjectID: nil, attribute: composeTootAttribute)], toSection: .status) } diffableDataSource.apply(snapshot, animatingDifferences: false) } diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index bcda65879..7aaadcb70 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -12,18 +12,25 @@ import CoreDataStack final class ComposeViewModel { + var disposeBag = Set() + // input let context: AppContext - let composeKind: ComposeKind + let composeKind: ComposeStatusSection.ComposeKind + let composeTootAttribute = ComposeStatusItem.ComposeTootAttribute() + let composeContent = CurrentValueSubject("") + let activeAuthentication: CurrentValueSubject // output var diffableDataSource: UITableViewDiffableDataSource! + + // UI & UX let title: CurrentValueSubject let shouldDismiss = CurrentValueSubject(true) init( context: AppContext, - composeKind: ComposeKind + composeKind: ComposeStatusSection.ComposeKind ) { self.context = context self.composeKind = composeKind @@ -31,14 +38,30 @@ final class ComposeViewModel { case .toot: self.title = CurrentValueSubject(L10n.Scene.Compose.Title.newToot) case .replyToot: self.title = CurrentValueSubject(L10n.Scene.Compose.Title.newReply) } + self.activeAuthentication = CurrentValueSubject(context.authenticationService.activeMastodonAuthentication.value) // end init + + // bind active authentication + context.authenticationService.activeMastodonAuthentication + .assign(to: \.value, on: activeAuthentication) + .store(in: &disposeBag) + + activeAuthentication + .sink { [weak self] mastodonAuthentication in + guard let self = self else { return } + let mastodonUser = mastodonAuthentication?.user + let username = mastodonUser?.username ?? " " + + self.composeTootAttribute.avatarURL.value = mastodonUser?.avatarImageURL() + self.composeTootAttribute.displayName.value = { + guard let displayName = mastodonUser?.displayName, !displayName.isEmpty else { + return username + } + return displayName + }() + self.composeTootAttribute.username.value = username + } + .store(in: &disposeBag) } } - -extension ComposeViewModel { - enum ComposeKind { - case toot - case replyToot(tootObjectID: NSManagedObjectID) - } -} diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeTootContentTableViewCell.swift b/Mastodon/Scene/Compose/TableViewCell/ComposeTootContentTableViewCell.swift index 6e7a2058c..5a7f311d1 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeTootContentTableViewCell.swift +++ b/Mastodon/Scene/Compose/TableViewCell/ComposeTootContentTableViewCell.swift @@ -6,19 +6,26 @@ // import UIKit +import Combine import TwitterTextEditor final class ComposeTootContentTableViewCell: UITableViewCell { + var disposeBag = Set() + let statusView = StatusView() + let textEditorView: TextEditorView = { let textEditorView = TextEditorView() textEditorView.font = .preferredFont(forTextStyle: .body) -// textEditorView.scrollView.isScrollEnabled = false + textEditorView.scrollView.isScrollEnabled = false textEditorView.isScrollEnabled = false + textEditorView.placeholderText = L10n.Scene.Compose.contentInputPlaceholder return textEditorView }() + let composeContent = PassthroughSubject() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) _init() @@ -56,19 +63,9 @@ extension ComposeTootContentTableViewCell { textEditorView.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), ]) - // let containerStackView = UIStackView() - // containerStackView.axis = .vertical - // containerStackView.spacing = 8 - // containerStackView.translatesAutoresizingMaskIntoConstraints = false - // contentView.addSubview(containerStackView) - // NSLayoutConstraint.activate([ - // containerStackView.topAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 10), - // containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), - // containerStackView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), - // contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor, constant: 20), - // ]) - // TODO: + + textEditorView.changeObserver = self } override func didMoveToWindow() { @@ -81,3 +78,11 @@ extension ComposeTootContentTableViewCell { extension ComposeTootContentTableViewCell { } + +// MARK: - UITextViewDelegate +extension ComposeTootContentTableViewCell: TextEditorViewChangeObserver { + func textEditorView(_ textEditorView: TextEditorView, didChangeWithChangeResult changeResult: TextEditorViewChangeResult) { + guard changeResult.isTextChanged else { return } + composeContent.send(textEditorView.text) + } +} diff --git a/Mastodon/Scene/Share/View/Button/RoundedEdgesButton.swift b/Mastodon/Scene/Share/View/Button/RoundedEdgesButton.swift new file mode 100644 index 000000000..a38b711dd --- /dev/null +++ b/Mastodon/Scene/Share/View/Button/RoundedEdgesButton.swift @@ -0,0 +1,19 @@ +// +// RoundedEdgesButton.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-3-12. +// + +import UIKit + +final class RoundedEdgesButton: UIButton { + + override func layoutSubviews() { + super.layoutSubviews() + + layer.masksToBounds = true + layer.cornerRadius = bounds.height * 0.5 + } + +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses.swift new file mode 100644 index 000000000..f01e6cb47 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses.swift @@ -0,0 +1,8 @@ +// +// Mastodon+API+Statuses.swift +// +// +// Created by MainasuK Cirno on 2021-3-12. +// + +import Foundation diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift index 073d926e9..5443fa22d 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift @@ -96,6 +96,7 @@ extension Mastodon.API { public enum Onboarding { } public enum Polls { } public enum Timeline { } + public enum Statuses { } public enum Favorites { } }