View following / followers

This commit is contained in:
Justin Mazzocchi 2020-12-02 17:06:46 -08:00
parent 03a4d1299d
commit acfd127672
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
9 changed files with 87 additions and 14 deletions

View file

@ -0,0 +1,14 @@
// Copyright © 2020 Metabolist. All rights reserved.
import UIKit
extension UIButton {
func setAttributedLocalizedTitle(localizationKey: String, count: Int) {
let localizedTitle = String.localizedStringWithFormat(NSLocalizedString(localizationKey, comment: ""), count)
setAttributedTitle(localizedTitle.countEmphasizedAttributedString(count: count), for: .normal)
setAttributedTitle(
localizedTitle.countEmphasizedAttributedString(count: count, highlighted: true),
for: .highlighted)
}
}

View file

@ -4,6 +4,7 @@
"account.field.verified" = "Verified %@";
"account.follow" = "Follow";
"account.following" = "Following";
"account.following-count" = "%ld Following";
"account.hide-reblogs" = "Hide boosts";
"account.mute" = "Mute";
"account.request" = "Request";

View file

@ -9,6 +9,8 @@ public enum AccountsEndpoint {
case favouritedBy(id: Status.Id)
case mutes
case blocks
case accountsFollowers(id: Account.Id)
case accountsFollowing(id: Account.Id)
}
extension AccountsEndpoint: Endpoint {
@ -20,6 +22,8 @@ extension AccountsEndpoint: Endpoint {
return defaultContext + ["statuses"]
case .mutes, .blocks:
return defaultContext
case .accountsFollowers, .accountsFollowing:
return defaultContext + ["accounts"]
}
}
@ -33,6 +37,10 @@ extension AccountsEndpoint: Endpoint {
return ["mutes"]
case .blocks:
return ["blocks"]
case let .accountsFollowers(id):
return [id, "followers"]
case let .accountsFollowing(id):
return [id, "following"]
}
}

View file

@ -39,6 +39,7 @@
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D71254246E200B1EBEF /* PollView.swift */; };
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D812544D80000B1EBEF /* PollOptionButton.swift */; };
D08B8D8D2544E6EC00B1EBEF /* PollResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */; };
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */; };
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */; };
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
@ -157,6 +158,7 @@
D08B8D71254246E200B1EBEF /* PollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollView.swift; sourceTree = "<group>"; };
D08B8D812544D80000B1EBEF /* PollOptionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionButton.swift; sourceTree = "<group>"; };
D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollResultView.swift; sourceTree = "<group>"; };
D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extensions.swift"; sourceTree = "<group>"; };
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
@ -433,6 +435,7 @@
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */,
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */,
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
D0030981250C6C8500EACB32 /* URL+Extensions.swift */,
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */,
@ -674,6 +677,7 @@
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */,
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */,
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */,
D0EA59482522B8B600804347 /* ViewConstants.swift in Sources */,
D04226FD2546AC0B000980A3 /* StartupAndSyncingPreferencesView.swift in Sources */,
D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */,

View file

@ -90,6 +90,20 @@ public extension AccountService {
func report(_ elements: ReportElements) -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(ReportEndpoint.create(elements)).ignoreOutput().eraseToAnyPublisher()
}
func followingService() -> AccountListService {
AccountListService(
endpoint: .accountsFollowing(id: account.id),
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
func followersService() -> AccountListService {
AccountListService(
endpoint: .accountsFollowers(id: account.id),
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
}
}
private extension AccountService {

View file

@ -19,7 +19,7 @@ final class ProfileViewController: TableViewController {
super.viewDidLoad()
// Initial size is to avoid unsatisfiable constraint warning
let accountHeaderView = AccountHeaderView(frame: .init(origin: .zero, size: .init(width: 100, height: 100)))
let accountHeaderView = AccountHeaderView(frame: .init(origin: .zero, size: .init(width: 300, height: 300)))
accountHeaderView.viewModel = viewModel

View file

@ -46,6 +46,10 @@ public extension AccountViewModel {
var emoji: [Emoji] { accountService.account.emojis }
var followingCount: Int { accountService.account.followingCount }
var followersCount: Int { accountService.account.followersCount }
var isSelf: Bool { accountService.account.id == identification.identity.account?.id }
func avatarURL(profile: Bool = false) -> URL {
@ -66,6 +70,20 @@ public extension AccountViewModel {
.eraseToAnyPublisher())
}
func followingSelected() {
eventsSubject.send(
Just(.navigation(.collection(accountService.followingService())))
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
func followersSelected() {
eventsSubject.send(
Just(.navigation(.collection(accountService.followersService())))
.setFailureType(to: Error.self)
.eraseToAnyPublisher())
}
func reportViewModel() -> ReportViewModel {
ReportViewModel(accountService: accountService, identification: identification)
}

View file

@ -18,6 +18,9 @@ final class AccountHeaderView: UIView {
let lockedImageView = UIImageView()
let fieldsStackView = UIStackView()
let noteTextView = TouchFallthroughTextView()
let followStackView = UIStackView()
let followingButton = UIButton()
let followersButton = UIButton()
let segmentedControl = UISegmentedControl()
var viewModel: ProfileViewModel? {
@ -100,8 +103,17 @@ final class AccountHeaderView: UIView {
mutableNote.resizeAttachments(toLineHeight: noteFont.lineHeight)
noteTextView.attributedText = mutableNote
noteTextView.isHidden = false
followingButton.setAttributedLocalizedTitle(
localizationKey: "account.following-count",
count: accountViewModel.followingCount)
followersButton.setAttributedLocalizedTitle(
localizationKey: "account.followers-count",
count: accountViewModel.followersCount)
followStackView.isHidden = false
} else {
noteTextView.isHidden = true
followStackView.isHidden = true
}
}
}
@ -264,6 +276,19 @@ private extension AccountHeaderView {
noteTextView.isScrollEnabled = false
noteTextView.delegate = self
baseStackView.addArrangedSubview(followStackView)
followStackView.distribution = .fillEqually
followingButton.addAction(
UIAction { [weak self] _ in self?.viewModel?.accountViewModel?.followingSelected() },
for: .touchUpInside)
followStackView.addArrangedSubview(followingButton)
followersButton.addAction(
UIAction { [weak self] _ in self?.viewModel?.accountViewModel?.followersSelected() },
for: .touchUpInside)
followStackView.addArrangedSubview(followersButton)
for (index, collection) in ProfileCollection.allCases.enumerated() {
segmentedControl.insertSegment(
action: UIAction(title: collection.title) { [weak self] _ in

View file

@ -347,13 +347,11 @@ private extension StatusView {
let noFavorites = viewModel.favoritesCount == 0
let noInteractions = !isContextParent || (noReblogs && noFavorites)
setAttributedLocalizedTitle(
button: rebloggedByButton,
rebloggedByButton.setAttributedLocalizedTitle(
localizationKey: "status.reblogs-count",
count: viewModel.reblogsCount)
rebloggedByButton.isHidden = noReblogs
setAttributedLocalizedTitle(
button: favoritedByButton,
favoritedByButton.setAttributedLocalizedTitle(
localizationKey: "status.favorites-count",
count: viewModel.favoritesCount)
favoritedByButton.isHidden = noFavorites
@ -421,15 +419,6 @@ private extension StatusView {
menuButton.setImage(UIImage(systemName: "ellipsis",
withConfiguration: UIImage.SymbolConfiguration(scale: scale)), for: .normal)
}
func setAttributedLocalizedTitle(button: UIButton, localizationKey: String, count: Int) {
let localizedTitle = String.localizedStringWithFormat(NSLocalizedString(localizationKey, comment: ""), count)
button.setAttributedTitle(localizedTitle.countEmphasizedAttributedString(count: count), for: .normal)
button.setAttributedTitle(
localizedTitle.countEmphasizedAttributedString(count: count, highlighted: true),
for: .highlighted)
}
}
private extension UIButton {