Merge pull request #187 from tootsuite/fix/recommand-refresh

Add refresh trigger for recommend list and disable reblog if needs
This commit is contained in:
CMK 2021-06-29 20:05:05 +08:00 committed by GitHub
commit 0dd2aaa068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 163 additions and 64 deletions

View File

@ -4778,7 +4778,7 @@
repositoryURL = "https://github.com/TwidereProject/MetaTextView.git"; repositoryURL = "https://github.com/TwidereProject/MetaTextView.git";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 1.2.1; version = 1.2.2;
}; };
}; };
DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = { DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = {

View File

@ -12,7 +12,7 @@
<key>CoreDataStack.xcscheme_^#shared#^_</key> <key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>23</integer> <integer>21</integer>
</dict> </dict>
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key> <key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
<dict> <dict>
@ -37,7 +37,7 @@
<key>NotificationService.xcscheme_^#shared#^_</key> <key>NotificationService.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>21</integer> <integer>22</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View File

@ -114,8 +114,8 @@
"repositoryURL": "https://github.com/TwidereProject/MetaTextView.git", "repositoryURL": "https://github.com/TwidereProject/MetaTextView.git",
"state": { "state": {
"branch": null, "branch": null,
"revision": "2660fc30ef6ed8de347ddca499341a965d1fda56", "revision": "d48cf6a2479ce6fc4f836b6c4d7ba855cdbc71cc",
"version": "1.2.1" "version": "1.2.2"
} }
}, },
{ {

View File

@ -25,6 +25,7 @@ extension ComposeStatusItem {
let avatarURL = CurrentValueSubject<URL?, Never>(nil) let avatarURL = CurrentValueSubject<URL?, Never>(nil)
let displayName = CurrentValueSubject<String?, Never>(nil) let displayName = CurrentValueSubject<String?, Never>(nil)
let emojiDict = CurrentValueSubject<MastodonStatusContent.EmojiDict, Never>([:])
let username = CurrentValueSubject<String?, Never>(nil) let username = CurrentValueSubject<String?, Never>(nil)
let composeContent = CurrentValueSubject<String?, Never>(nil) let composeContent = CurrentValueSubject<String?, Never>(nil)
@ -34,6 +35,7 @@ extension ComposeStatusItem {
static func == (lhs: ComposeStatusAttribute, rhs: ComposeStatusAttribute) -> Bool { static func == (lhs: ComposeStatusAttribute, rhs: ComposeStatusAttribute) -> Bool {
return lhs.avatarURL.value == rhs.avatarURL.value && return lhs.avatarURL.value == rhs.avatarURL.value &&
lhs.displayName.value == rhs.displayName.value && lhs.displayName.value == rhs.displayName.value &&
lhs.emojiDict.value == rhs.emojiDict.value &&
lhs.username.value == rhs.username.value && lhs.username.value == rhs.username.value &&
lhs.composeContent.value == rhs.composeContent.value && lhs.composeContent.value == rhs.composeContent.value &&
lhs.isContentWarningComposing.value == rhs.isContentWarningComposing.value && lhs.isContentWarningComposing.value == rhs.isContentWarningComposing.value &&

View File

@ -43,13 +43,14 @@ extension ComposeStatusSection {
} }
.store(in: &cell.disposeBag) .store(in: &cell.disposeBag)
// set display name and username // set display name and username
Publishers.CombineLatest( Publishers.CombineLatest3(
attribute.displayName.eraseToAnyPublisher(), attribute.displayName,
attribute.emojiDict,
attribute.username.eraseToAnyPublisher() attribute.username.eraseToAnyPublisher()
) )
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { displayName, username in .sink { displayName, emojiDict, username in
cell.statusView.nameLabel.text = displayName cell.statusView.nameLabel.configure(content: displayName ?? " ", emojiDict: emojiDict)
cell.statusView.usernameLabel.text = username.flatMap { "@" + $0 } ?? " " cell.statusView.usernameLabel.text = username.flatMap { "@" + $0 } ?? " "
} }
.store(in: &cell.disposeBag) .store(in: &cell.disposeBag)

View File

@ -574,9 +574,8 @@ extension StatusSection {
cell.statusView.contentMetaText.textView.accessibilityLanguage = (status.reblog ?? status).language cell.statusView.contentMetaText.textView.accessibilityLanguage = (status.reblog ?? status).language
// set visibility // set visibility
if let visibility = (status.reblog ?? status).visibility { if let visibility = (status.reblog ?? status).visibilityEnum {
cell.statusView.updateVisibility(visibility: visibility) cell.statusView.updateVisibility(visibility: visibility)
cell.statusView.revealContentWarningButton.publisher(for: \.isHidden) cell.statusView.revealContentWarningButton.publisher(for: \.isHidden)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak cell] isHidden in .sink { [weak cell] isHidden in
@ -953,6 +952,13 @@ extension StatusSection {
guard status.reblogsCount.intValue > 0 else { return nil } guard status.reblogsCount.intValue > 0 else { return nil }
return L10n.Common.Controls.Timeline.Accessibility.countReblogs(status.reblogsCount.intValue) return L10n.Common.Controls.Timeline.Accessibility.countReblogs(status.reblogsCount.intValue)
}() }()
// disable reblog when non-public (except self)
cell.statusView.actionToolbarContainer.reblogButton.isEnabled = true
if let visibility = status.visibilityEnum, visibility != .public, status.author.id != requestUserID {
cell.statusView.actionToolbarContainer.reblogButton.isEnabled = false
}
// set like // set like
let isLike = status.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false let isLike = status.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
let favoriteCountTitle: String = { let favoriteCountTitle: String = {

View File

@ -89,3 +89,10 @@ extension Status {
} }
extension Status: EmojiContainer { } extension Status: EmojiContainer { }
extension Status {
var visibilityEnum: Mastodon.Entity.Status.Visibility? {
return visibility.flatMap { Mastodon.Entity.Status.Visibility(rawValue: $0) }
}
}

View File

@ -767,6 +767,22 @@ extension ComposeViewController: UITextViewDelegate {
return autoCompleteInfo return autoCompleteInfo
} }
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if textView === textEditorView()?.textView {
return false
}
return true
}
func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if textView === textEditorView()?.textView {
return false
}
return true
}
} }
// MARK: - TextEditorViewTextAttributesDelegate // MARK: - TextEditorViewTextAttributesDelegate

View File

@ -181,6 +181,7 @@ final class ComposeViewModel: NSObject {
} }
return displayName return displayName
}() }()
self.composeStatusAttribute.emojiDict.value = mastodonUser?.emojiDict ?? [:]
self.composeStatusAttribute.username.value = username self.composeStatusAttribute.username.value = username
} }
.store(in: &disposeBag) .store(in: &disposeBag)

View File

@ -27,6 +27,7 @@ final class ComposeStatusContentTableViewCell: UITableViewCell {
metaText.textView.backgroundColor = .clear metaText.textView.backgroundColor = .clear
metaText.textView.isScrollEnabled = false metaText.textView.isScrollEnabled = false
metaText.textView.keyboardType = .twitter metaText.textView.keyboardType = .twitter
metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment
metaText.textView.textContainer.lineFragmentPadding = 10 // leading inset metaText.textView.textContainer.lineFragmentPadding = 10 // leading inset
metaText.textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) metaText.textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
metaText.textView.attributedPlaceholder = { metaText.textView.attributedPlaceholder = {

View File

@ -202,15 +202,6 @@ extension ComposeToolbarView {
} }
} }
func imageNameForTimeline() -> String {
switch self {
case .public: return "globe"
// case .unlisted: return "eye.slash"
case .private: return "person.3"
case .direct: return "at"
}
}
var visibility: Mastodon.Entity.Status.Visibility { var visibility: Mastodon.Entity.Status.Visibility {
switch self { switch self {
case .public: return .public case .public: return .public

View File

@ -56,7 +56,7 @@ extension HashtagTimelineViewController {
super.viewDidLoad() super.viewDidLoad()
title = "#\(viewModel.hashtag)" title = "#\(viewModel.hashtag)"
titleView.update(title: viewModel.hashtag, subtitle: nil) titleView.update(title: viewModel.hashtag, subtitle: nil, emojiDict: [:])
navigationItem.titleView = titleView navigationItem.titleView = titleView
view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color
@ -143,7 +143,7 @@ extension HashtagTimelineViewController {
private func updatePromptTitle() { private func updatePromptTitle() {
var subtitle: String? var subtitle: String?
defer { defer {
titleView.update(title: "#" + viewModel.hashtag, subtitle: subtitle) titleView.update(title: "#" + viewModel.hashtag, subtitle: subtitle, emojiDict: [:])
} }
guard let histories = viewModel.hashtagEntity.value?.history else { guard let histories = viewModel.hashtagEntity.value?.history else {
return return

View File

@ -49,7 +49,7 @@ extension FavoriteViewController {
view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color
navigationItem.titleView = titleView navigationItem.titleView = titleView
titleView.update(title: L10n.Scene.Favorite.title, subtitle: nil) titleView.update(title: L10n.Scene.Favorite.title, subtitle: nil, emojiDict: [:])
tableView.translatesAutoresizingMaskIntoConstraints = false tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView) view.addSubview(tableView)

View File

@ -13,6 +13,7 @@ import ActiveLabel
import AlamofireImage import AlamofireImage
import CropViewController import CropViewController
import TwitterTextEditor import TwitterTextEditor
import MastodonMeta
protocol ProfileHeaderViewControllerDelegate: AnyObject { protocol ProfileHeaderViewControllerDelegate: AnyObject {
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView) func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView)
@ -166,14 +167,27 @@ extension ProfileHeaderViewController {
) )
} }
.store(in: &disposeBag) .store(in: &disposeBag)
Publishers.CombineLatest3( Publishers.CombineLatest4(
viewModel.isEditing.eraseToAnyPublisher(), viewModel.isEditing,
viewModel.displayProfileInfo.name.removeDuplicates().eraseToAnyPublisher(), viewModel.displayProfileInfo.name.removeDuplicates(),
viewModel.editProfileInfo.name.removeDuplicates().eraseToAnyPublisher() viewModel.editProfileInfo.name.removeDuplicates(),
viewModel.emojiDict
) )
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] isEditing, name, editingName in .sink { [weak self] isEditing, name, editingName, emojiDict in
guard let self = self else { return } guard let self = self else { return }
do {
var emojis = MastodonContent.Emojis()
for (key, value) in emojiDict {
emojis[key] = value.absoluteString
}
let metaContent = try MastodonMetaContent.convert(
document: MastodonContent(content: name ?? " ", emojis: emojis)
)
self.profileHeaderView.nameMetaText.configure(content: metaContent)
} catch {
assertionFailure()
}
self.profileHeaderView.nameTextField.text = isEditing ? editingName : name self.profileHeaderView.nameTextField.text = isEditing ? editingName : name
} }
.store(in: &disposeBag) .store(in: &disposeBag)
@ -412,7 +426,7 @@ extension ProfileHeaderViewController {
profileHeaderView.avatarImageView.alpha = alpha profileHeaderView.avatarImageView.alpha = alpha
profileHeaderView.editAvatarBackgroundView.alpha = alpha profileHeaderView.editAvatarBackgroundView.alpha = alpha
profileHeaderView.nameTextFieldBackgroundView.alpha = alpha profileHeaderView.nameTextFieldBackgroundView.alpha = alpha
profileHeaderView.nameTextField.alpha = alpha profileHeaderView.displayNameStackView.alpha = alpha
profileHeaderView.usernameLabel.alpha = alpha profileHeaderView.usernameLabel.alpha = alpha
} }

View File

@ -10,6 +10,7 @@ import UIKit
import ActiveLabel import ActiveLabel
import TwitterTextEditor import TwitterTextEditor
import FLAnimatedImage import FLAnimatedImage
import MetaTextView
protocol ProfileHeaderViewDelegate: AnyObject { protocol ProfileHeaderViewDelegate: AnyObject {
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView) func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView)
@ -111,7 +112,24 @@ final class ProfileHeaderView: UIView {
view.layer.cornerRadius = 10 view.layer.cornerRadius = 10
return view return view
}() }()
let displayNameStackView = UIStackView()
let nameMetaText: MetaText = {
let metaText = MetaText()
metaText.textView.backgroundColor = .clear
metaText.textView.isEditable = false
metaText.textView.isSelectable = false
metaText.textView.isScrollEnabled = false
metaText.textView.layer.masksToBounds = false
metaText.textView.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold), maximumPointSize: 28)
metaText.textView.textColor = .white
metaText.textView.textContainer.lineFragmentPadding = 0
metaText.textAttributes = [
.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold), maximumPointSize: 28),
.foregroundColor: UIColor.white
]
return metaText
}()
let nameTextField: UITextField = { let nameTextField: UITextField = {
let textField = UITextField() let textField = UITextField()
textField.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold), maximumPointSize: 28) textField.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold), maximumPointSize: 28)
@ -303,7 +321,6 @@ extension ProfileHeaderView {
nameContainerStackView.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor), nameContainerStackView.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor),
]) ])
let displayNameStackView = UIStackView()
displayNameStackView.axis = .horizontal displayNameStackView.axis = .horizontal
nameTextField.translatesAutoresizingMaskIntoConstraints = false nameTextField.translatesAutoresizingMaskIntoConstraints = false
displayNameStackView.addArrangedSubview(nameTextField) displayNameStackView.addArrangedSubview(nameTextField)
@ -321,6 +338,16 @@ extension ProfileHeaderView {
]) ])
displayNameStackView.bringSubviewToFront(nameTextField) displayNameStackView.bringSubviewToFront(nameTextField)
displayNameStackView.addArrangedSubview(UIView()) displayNameStackView.addArrangedSubview(UIView())
// overlay meta text for display name
nameMetaText.textView.translatesAutoresizingMaskIntoConstraints = false
displayNameStackView.addSubview(nameMetaText.textView)
NSLayoutConstraint.activate([
nameMetaText.textView.topAnchor.constraint(equalTo: nameTextField.topAnchor),
nameMetaText.textView.leadingAnchor.constraint(equalTo: nameTextField.leadingAnchor),
nameMetaText.textView.trailingAnchor.constraint(equalTo: nameTextField.trailingAnchor),
nameMetaText.textView.bottomAnchor.constraint(equalTo: nameTextField.bottomAnchor),
])
nameContainerStackView.addArrangedSubview(displayNameStackView) nameContainerStackView.addArrangedSubview(displayNameStackView)
nameContainerStackView.addArrangedSubview(usernameLabel) nameContainerStackView.addArrangedSubview(usernameLabel)
@ -436,6 +463,8 @@ extension ProfileHeaderView {
switch state { switch state {
case .normal: case .normal:
nameMetaText.textView.alpha = 1
nameTextField.alpha = 0
nameTextField.isEnabled = false nameTextField.isEnabled = false
bioActiveLabelContainer.isHidden = false bioActiveLabelContainer.isHidden = false
bioTextEditorView.isHidden = true bioTextEditorView.isHidden = true
@ -449,7 +478,9 @@ extension ProfileHeaderView {
self.editAvatarBackgroundView.isHidden = true self.editAvatarBackgroundView.isHidden = true
} }
case .editing: case .editing:
nameMetaText.textView.alpha = 0
nameTextField.isEnabled = true nameTextField.isEnabled = true
nameTextField.alpha = 1
bioActiveLabelContainer.isHidden = true bioActiveLabelContainer.isHidden = true
bioTextEditorView.isHidden = false bioTextEditorView.isHidden = false

View File

@ -303,12 +303,13 @@ extension ProfileViewController {
profileSegmentedViewController.pagingViewController.pagingDelegate = self profileSegmentedViewController.pagingViewController.pagingDelegate = self
// bind view model // bind view model
Publishers.CombineLatest( Publishers.CombineLatest3(
viewModel.name.eraseToAnyPublisher(), viewModel.name,
viewModel.statusesCount.eraseToAnyPublisher() viewModel.emojiDict,
viewModel.statusesCount
) )
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] name, statusesCount in .sink { [weak self] name, emojiDict, statusesCount in
guard let self = self else { return } guard let self = self else { return }
guard let title = name, let statusesCount = statusesCount, guard let title = name, let statusesCount = statusesCount,
let formattedStatusCount = MastodonMetricFormatter().string(from: statusesCount) else { let formattedStatusCount = MastodonMetricFormatter().string(from: statusesCount) else {
@ -316,7 +317,7 @@ extension ProfileViewController {
return return
} }
let subtitle = L10n.Scene.Profile.subtitle(formattedStatusCount) let subtitle = L10n.Scene.Profile.subtitle(formattedStatusCount)
self.titleView.update(title: title, subtitle: subtitle) self.titleView.update(title: title, subtitle: subtitle, emojiDict: emojiDict)
self.titleView.isHidden = false self.titleView.isHidden = false
} }
.store(in: &disposeBag) .store(in: &disposeBag)
@ -368,7 +369,7 @@ extension ProfileViewController {
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.assign(to: \.value, on: profileHeaderViewController.viewModel.displayProfileInfo.name) .assign(to: \.value, on: profileHeaderViewController.viewModel.displayProfileInfo.name)
.store(in: &disposeBag) .store(in: &disposeBag)
viewModel.fileds viewModel.fields
.removeDuplicates() .removeDuplicates()
.map { fields -> [ProfileFieldItem.FieldValue] in .map { fields -> [ProfileFieldItem.FieldValue] in
fields.map { ProfileFieldItem.FieldValue(name: $0.name, value: $0.value) } fields.map { ProfileFieldItem.FieldValue(name: $0.name, value: $0.value) }

View File

@ -39,7 +39,7 @@ class ProfileViewModel: NSObject {
let statusesCount: CurrentValueSubject<Int?, Never> let statusesCount: CurrentValueSubject<Int?, Never>
let followingCount: CurrentValueSubject<Int?, Never> let followingCount: CurrentValueSubject<Int?, Never>
let followersCount: CurrentValueSubject<Int?, Never> let followersCount: CurrentValueSubject<Int?, Never>
let fileds: CurrentValueSubject<[Mastodon.Entity.Field], Never> let fields: CurrentValueSubject<[Mastodon.Entity.Field], Never>
let emojiDict: CurrentValueSubject<MastodonStatusContent.EmojiDict, Never> let emojiDict: CurrentValueSubject<MastodonStatusContent.EmojiDict, Never>
// fulfill this before editing // fulfill this before editing
@ -82,7 +82,7 @@ class ProfileViewModel: NSObject {
self.followersCount = CurrentValueSubject(mastodonUser.flatMap { Int(truncating: $0.followersCount) }) self.followersCount = CurrentValueSubject(mastodonUser.flatMap { Int(truncating: $0.followersCount) })
self.protected = CurrentValueSubject(mastodonUser?.locked) self.protected = CurrentValueSubject(mastodonUser?.locked)
self.suspended = CurrentValueSubject(mastodonUser?.suspended ?? false) self.suspended = CurrentValueSubject(mastodonUser?.suspended ?? false)
self.fileds = CurrentValueSubject(mastodonUser?.fields ?? []) self.fields = CurrentValueSubject(mastodonUser?.fields ?? [])
self.emojiDict = CurrentValueSubject(mastodonUser?.emojiDict ?? [:]) self.emojiDict = CurrentValueSubject(mastodonUser?.emojiDict ?? [:])
super.init() super.init()
@ -257,7 +257,7 @@ extension ProfileViewModel {
self.followersCount.value = mastodonUser.flatMap { Int(truncating: $0.followersCount) } self.followersCount.value = mastodonUser.flatMap { Int(truncating: $0.followersCount) }
self.protected.value = mastodonUser?.locked self.protected.value = mastodonUser?.locked
self.suspended.value = mastodonUser?.suspended ?? false self.suspended.value = mastodonUser?.suspended ?? false
self.fileds.value = mastodonUser?.fields ?? [] self.fields.value = mastodonUser?.fields ?? []
self.emojiDict.value = mastodonUser?.emojiDict ?? [:] self.emojiDict.value = mastodonUser?.emojiDict ?? [:]
} }

View File

@ -10,6 +10,7 @@ import CoreDataStack
import Foundation import Foundation
import MastodonSDK import MastodonSDK
import UIKit import UIKit
import ActiveLabel
protocol SearchRecommendAccountsCollectionViewCellDelegate: NSObject { protocol SearchRecommendAccountsCollectionViewCellDelegate: NSObject {
func followButtonDidPressed(clickedUser: MastodonUser) func followButtonDidPressed(clickedUser: MastodonUser)
@ -42,8 +43,8 @@ class SearchRecommendAccountsCollectionViewCell: UICollectionViewCell {
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
let displayNameLabel: UILabel = { let displayNameLabel: ActiveLabel = {
let label = UILabel() let label = ActiveLabel(style: .statusName)
label.textColor = .white label.textColor = .white
label.textAlignment = .center label.textAlignment = .center
label.font = .systemFont(ofSize: 18, weight: .semibold) label.font = .systemFont(ofSize: 18, weight: .semibold)
@ -164,7 +165,7 @@ extension SearchRecommendAccountsCollectionViewCell {
} }
func config(with mastodonUser: MastodonUser) { func config(with mastodonUser: MastodonUser) {
displayNameLabel.text = mastodonUser.displayName.isEmpty ? mastodonUser.username : mastodonUser.displayName displayNameLabel.configure(content: mastodonUser.displayNameWithFallback, emojiDict: mastodonUser.emojiDict)
acctLabel.text = "@" + mastodonUser.acct acctLabel.text = "@" + mastodonUser.acct
avatarImageView.af.setImage( avatarImageView.af.setImage(
withURL: URL(string: mastodonUser.avatar)!, withURL: URL(string: mastodonUser.avatar)!,

View File

@ -151,6 +151,12 @@ extension SearchViewController {
view.bringSubviewToFront(statusBar) view.bringSubviewToFront(statusBar)
} }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewModel.viewDidAppeared.send()
}
func setupSearchBar() { func setupSearchBar() {
searchBar.delegate = self searchBar.delegate = self
view.addSubview(searchBar) view.addSubview(searchBar)

View File

@ -23,6 +23,7 @@ final class SearchViewModel: NSObject {
let mastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil) let mastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil)
let currentMastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil) let currentMastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil)
let viewDidAppeared = PassthroughSubject<Void, Never>()
// output // output
let searchText = CurrentValueSubject<String, Never>("") let searchText = CurrentValueSubject<String, Never>("")
@ -145,9 +146,10 @@ final class SearchViewModel: NSObject {
dataSource.apply(snapshot, animatingDifferences: false, completion: nil) dataSource.apply(snapshot, animatingDifferences: false, completion: nil)
} }
.store(in: &disposeBag) .store(in: &disposeBag)
requestRecommendHashTags() viewDidAppeared
.receive(on: DispatchQueue.main) .compactMap { _ in self.requestRecommendHashTags() }
.receive(on: RunLoop.main)
.sink { [weak self] _ in .sink { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
if !self.recommendHashTags.isEmpty { if !self.recommendHashTags.isEmpty {
@ -160,8 +162,9 @@ final class SearchViewModel: NSObject {
} receiveValue: { _ in } receiveValue: { _ in
} }
.store(in: &disposeBag) .store(in: &disposeBag)
viewDidAppeared
requestRecommendAccountsV2() .compactMap { _ in self.requestRecommendAccountsV2() }
.receive(on: RunLoop.main)
.sink { [weak self] _ in .sink { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
if !self.recommendAccounts.isEmpty { if !self.recommendAccounts.isEmpty {
@ -172,6 +175,7 @@ final class SearchViewModel: NSObject {
.store(in: &disposeBag) .store(in: &disposeBag)
recommendAccountsFallback recommendAccountsFallback
.receive(on: RunLoop.main)
.sink { [weak self] _ in .sink { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
self.requestRecommendAccounts() self.requestRecommendAccounts()

View File

@ -6,13 +6,14 @@
// //
import UIKit import UIKit
import ActiveLabel
final class DoubleTitleLabelNavigationBarTitleView: UIView { final class DoubleTitleLabelNavigationBarTitleView: UIView {
let containerView = UIStackView() let containerView = UIStackView()
let titleLabel: UILabel = { let titleLabel: ActiveLabel = {
let label = UILabel() let label = ActiveLabel(style: .default)
label.font = .systemFont(ofSize: 17, weight: .semibold) label.font = .systemFont(ofSize: 17, weight: .semibold)
label.textColor = Asset.Colors.Label.primary.color label.textColor = Asset.Colors.Label.primary.color
label.textAlignment = .center label.textAlignment = .center
@ -58,8 +59,8 @@ extension DoubleTitleLabelNavigationBarTitleView {
containerView.addArrangedSubview(subtitleLabel) containerView.addArrangedSubview(subtitleLabel)
} }
func update(title: String, subtitle: String?) { func update(title: String, subtitle: String?, emojiDict: MastodonStatusContent.EmojiDict) {
titleLabel.text = title titleLabel.configure(content: title, emojiDict: emojiDict)
if let subtitle = subtitle { if let subtitle = subtitle {
subtitleLabel.text = subtitle subtitleLabel.text = subtitle
subtitleLabel.isHidden = false subtitleLabel.isHidden = false

View File

@ -14,6 +14,7 @@ import AlamofireImage
import FLAnimatedImage import FLAnimatedImage
import MetaTextView import MetaTextView
import Meta import Meta
import MastodonSDK
// TODO: // TODO:
// import LinkPresentation // import LinkPresentation
@ -498,10 +499,20 @@ extension StatusView {
} }
// TODO: a11y // TODO: a11y
} }
func updateVisibility(visibility: String) { func updateVisibility(visibility: Mastodon.Entity.Status.Visibility) {
guard let visibility = ComposeToolbarView.VisibilitySelectionType(rawValue: visibility) else { return } switch visibility {
visibilityImageView.image = UIImage(systemName: visibility.imageNameForTimeline(), withConfiguration: UIImage.SymbolConfiguration(pointSize: 13, weight: .regular)) case .public:
visibilityImageView.image = UIImage(systemName: "globe", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13, weight: .regular))
case .private:
visibilityImageView.image = UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13, weight: .regular))
case .unlisted:
visibilityImageView.image = UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13, weight: .regular))
case .direct:
visibilityImageView.image = UIImage(systemName: "at", withConfiguration: UIImage.SymbolConfiguration(pointSize: 13, weight: .regular))
case ._other:
visibilityImageView.image = nil
}
} }
} }

View File

@ -11,6 +11,7 @@ import CoreDataStack
import Foundation import Foundation
import MastodonSDK import MastodonSDK
import UIKit import UIKit
import ActiveLabel
protocol SuggestionAccountTableViewCellDelegate: AnyObject { protocol SuggestionAccountTableViewCellDelegate: AnyObject {
func accountButtonPressed(objectID: NSManagedObjectID, cell: SuggestionAccountTableViewCell) func accountButtonPressed(objectID: NSManagedObjectID, cell: SuggestionAccountTableViewCell)
@ -28,8 +29,8 @@ final class SuggestionAccountTableViewCell: UITableViewCell {
return imageView return imageView
}() }()
let titleLabel: UILabel = { let titleLabel: ActiveLabel = {
let label = UILabel() let label = ActiveLabel(style: .statusName)
label.textColor = Asset.Colors.brandBlue.color label.textColor = Asset.Colors.brandBlue.color
label.font = .systemFont(ofSize: 17, weight: .semibold) label.font = .systemFont(ofSize: 17, weight: .semibold)
label.lineBreakMode = .byTruncatingTail label.lineBreakMode = .byTruncatingTail
@ -153,7 +154,7 @@ extension SuggestionAccountTableViewCell {
imageTransition: .crossDissolve(0.2) imageTransition: .crossDissolve(0.2)
) )
} }
titleLabel.text = account.displayName.isEmpty ? account.username : account.displayName titleLabel.configure(content: account.displayNameWithFallback, emojiDict: account.emojiDict)
subTitleLabel.text = account.acct subTitleLabel.text = account.acct
button.isSelected = isSelected button.isSelected = isSelected
button.publisher(for: .touchUpInside) button.publisher(for: .touchUpInside)

View File

@ -80,9 +80,13 @@ extension ThreadViewController {
viewModel.navigationBarTitle viewModel.navigationBarTitle
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] title in .sink { [weak self] tuple in
guard let self = self else { return } guard let self = self else { return }
self.titleView.update(title: title ?? L10n.Scene.Thread.backTitle, subtitle: nil) guard let (title, emojiDict) = tuple else {
self.titleView.update(title: L10n.Scene.Thread.backTitle, subtitle: nil, emojiDict: [:])
return
}
self.titleView.update(title: title, subtitle: nil, emojiDict: emojiDict)
} }
.store(in: &disposeBag) .store(in: &disposeBag)
} }

View File

@ -45,7 +45,7 @@ class ThreadViewModel {
let ancestorItems = CurrentValueSubject<[Item], Never>([]) let ancestorItems = CurrentValueSubject<[Item], Never>([])
let descendantNodes = CurrentValueSubject<[LeafNode], Never>([]) let descendantNodes = CurrentValueSubject<[LeafNode], Never>([])
let descendantItems = CurrentValueSubject<[Item], Never>([]) let descendantItems = CurrentValueSubject<[Item], Never>([])
let navigationBarTitle: CurrentValueSubject<String?, Never> let navigationBarTitle: CurrentValueSubject<(String, MastodonStatusContent.EmojiDict)?, Never>
init(context: AppContext, optionalStatus: Status?) { init(context: AppContext, optionalStatus: Status?) {
self.context = context self.context = context
@ -53,7 +53,7 @@ class ThreadViewModel {
self.rootItem = CurrentValueSubject(optionalStatus.flatMap { Item.root(statusObjectID: $0.objectID, attribute: Item.StatusAttribute()) }) self.rootItem = CurrentValueSubject(optionalStatus.flatMap { Item.root(statusObjectID: $0.objectID, attribute: Item.StatusAttribute()) })
self.existStatusFetchedResultsController = StatusFetchedResultsController(managedObjectContext: context.managedObjectContext, domain: nil, additionalTweetPredicate: nil) self.existStatusFetchedResultsController = StatusFetchedResultsController(managedObjectContext: context.managedObjectContext, domain: nil, additionalTweetPredicate: nil)
self.navigationBarTitle = CurrentValueSubject( self.navigationBarTitle = CurrentValueSubject(
optionalStatus.flatMap { L10n.Scene.Thread.title($0.author.displayNameWithFallback) } optionalStatus.flatMap { (L10n.Scene.Thread.title($0.author.displayNameWithFallback), $0.emojiDict) }
) )
// bind fetcher domain // bind fetcher domain
@ -85,7 +85,7 @@ class ThreadViewModel {
return return
} }
self.rootNode.value = RootNode(domain: status.domain, statusID: status.id, replyToID: status.inReplyToID) self.rootNode.value = RootNode(domain: status.domain, statusID: status.id, replyToID: status.inReplyToID)
self.navigationBarTitle.value = L10n.Scene.Thread.title(status.author.displayNameWithFallback) self.navigationBarTitle.value = (L10n.Scene.Thread.title(status.author.displayNameWithFallback), status.author.emojiDict)
} }
} }
.store(in: &disposeBag) .store(in: &disposeBag)