feat: handle suspended account in profile scene

This commit is contained in:
CMK 2021-04-08 16:53:32 +08:00
parent ba48adb470
commit 14176be4ed
17 changed files with 205 additions and 49 deletions

View File

@ -82,6 +82,7 @@
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="statusesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="suspended" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="url" optional="YES" attributeType="String"/>
<attribute name="username" attributeType="String"/>
@ -199,7 +200,7 @@
<element name="History" positionX="27" positionY="126" width="128" height="119"/>
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
<element name="MastodonAuthentication" positionX="18" positionY="162" width="128" height="209"/>
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="659"/>
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="674"/>
<element name="Mention" positionX="9" positionY="108" width="128" height="134"/>
<element name="Poll" positionX="72" positionY="162" width="128" height="194"/>
<element name="PollOption" positionX="81" positionY="171" width="128" height="134"/>

View File

@ -31,6 +31,7 @@ final public class MastodonUser: NSManagedObject {
@NSManaged public private(set) var locked: Bool
@NSManaged public private(set) var bot: Bool
@NSManaged public private(set) var suspended: Bool
@NSManaged public private(set) var createdAt: Date
@NSManaged public private(set) var updatedAt: Date
@ -93,6 +94,7 @@ extension MastodonUser {
user.locked = property.locked
user.bot = property.bot ?? false
user.suspended = property.suspended ?? false
// Mastodon do not provide relationship on the `Account`
// Update relationship via attribute updating interface
@ -174,6 +176,11 @@ extension MastodonUser {
self.bot = bot
}
}
public func update(suspended: Bool) {
if self.suspended != suspended {
self.suspended = suspended
}
}
public func update(isFollowing: Bool, by mastodonUser: MastodonUser) {
if isFollowing {
@ -268,6 +275,7 @@ extension MastodonUser {
public let followersCount: Int
public let locked: Bool
public let bot: Bool?
public let suspended: Bool?
public let createdAt: Date
public let networkDate: Date
@ -289,6 +297,7 @@ extension MastodonUser {
followersCount: Int,
locked: Bool,
bot: Bool?,
suspended: Bool?,
createdAt: Date,
networkDate: Date
) {
@ -309,6 +318,7 @@ extension MastodonUser {
self.followersCount = followersCount
self.locked = locked
self.bot = bot
self.suspended = suspended
self.createdAt = createdAt
self.networkDate = networkDate
}

View File

@ -69,6 +69,7 @@
"firendship": {
"follow": "Follow",
"following": "Following",
"request": "Request",
"pending": "Pending",
"block": "Block",
"block_user": "Block %s",
@ -91,7 +92,8 @@
"no_status_found": "No Status Found",
"blocking_warning": "You cant view Artbots profile\n until you unblock them.\nYour account looks like this to them.",
"blocked_warning": "You cant view Artbots profile\n until they unblock you.",
"suspended_warning": "This account is suspended."
"suspended_warning": "This account has been suspended.",
"user_suspended_warning": "%s's account has been suspended."
}
}
},

View File

@ -757,17 +757,9 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0F1E2D102615C39800C38565 /* View */ = {
isa = PBXGroup;
children = (
);
path = View;
sourceTree = "<group>";
};
0F2021F5261325ED000C64BF /* HashtagTimeline */ = {
isa = PBXGroup;
children = (
0F1E2D102615C39800C38565 /* View */,
0F2021FA2613262F000C64BF /* HashtagTimelineViewController.swift */,
0F202226261411BA000C64BF /* HashtagTimelineViewController+StatusProvider.swift */,
0F202200261326E6000C64BF /* HashtagTimelineViewModel.swift */,

View File

@ -63,11 +63,21 @@ extension Item {
let id = UUID()
let reason: Reason
enum Reason {
enum Reason: Equatable {
case noStatusFound
case blocking
case blocked
case suspended
case suspended(name: String?)
static func == (lhs: Item.EmptyStateHeaderAttribute.Reason, rhs: Item.EmptyStateHeaderAttribute.Reason) -> Bool {
switch (lhs, rhs) {
case (.noStatusFound, noStatusFound): return true
case (.blocking, blocking): return true
case (.blocked, blocked): return true
case (.suspended(let nameLeft), .suspended(let nameRight)): return nameLeft == nameRight
default: return false
}
}
}
init(reason: Reason) {

View File

@ -28,6 +28,7 @@ extension MastodonUser.Property {
followersCount: entity.followersCount,
locked: entity.locked,
bot: entity.bot,
suspended: entity.suspended,
createdAt: entity.createdAt,
networkDate: networkDate
)

View File

@ -112,6 +112,8 @@ internal enum L10n {
}
/// Pending
internal static let pending = L10n.tr("Localizable", "Common.Controls.Firendship.Pending")
/// Request
internal static let request = L10n.tr("Localizable", "Common.Controls.Firendship.Request")
/// Unblock
internal static let unblock = L10n.tr("Localizable", "Common.Controls.Firendship.Unblock")
/// Unblock %@
@ -179,8 +181,12 @@ internal enum L10n {
internal static let blockingWarning = L10n.tr("Localizable", "Common.Controls.Timeline.Header.BlockingWarning")
/// No Status Found
internal static let noStatusFound = L10n.tr("Localizable", "Common.Controls.Timeline.Header.NoStatusFound")
/// This account is suspended.
/// This account has been suspended.
internal static let suspendedWarning = L10n.tr("Localizable", "Common.Controls.Timeline.Header.SuspendedWarning")
/// %@'s account has been suspended.
internal static func userSuspendedWarning(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Timeline.Header.UserSuspendedWarning", String(describing: p1))
}
}
internal enum Loader {
/// Loading missing posts...

View File

@ -38,6 +38,7 @@ Please check your internet connection.";
"Common.Controls.Firendship.MuteUser" = "Mute %@";
"Common.Controls.Firendship.Muted" = "Muted";
"Common.Controls.Firendship.Pending" = "Pending";
"Common.Controls.Firendship.Request" = "Request";
"Common.Controls.Firendship.Unblock" = "Unblock";
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
"Common.Controls.Firendship.Unmute" = "Unmute";
@ -60,7 +61,8 @@ Please check your internet connection.";
until you unblock them.
Your account looks like this to them.";
"Common.Controls.Timeline.Header.NoStatusFound" = "No Status Found";
"Common.Controls.Timeline.Header.SuspendedWarning" = "This account is suspended.";
"Common.Controls.Timeline.Header.SuspendedWarning" = "This account has been suspended.";
"Common.Controls.Timeline.Header.UserSuspendedWarning" = "%@'s account has been suspended.";
"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Load missing posts";
"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Loading missing posts...";
"Common.Countable.Photo.Multiple" = "photos";

View File

@ -25,6 +25,10 @@ extension HomeTimelineViewController {
guard let self = self else { return }
self.showPublicTimelineAction(action)
},
UIAction(title: "Show Profile", image: UIImage(systemName: "person.crop.circle"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showProfileAction(action)
},
UIAction(title: "Sign Out", image: UIImage(systemName: "escape"), attributes: .destructive) { [weak self] action in
guard let self = self else { return }
self.signOutAction(action)
@ -277,5 +281,20 @@ extension HomeTimelineViewController {
coordinator.present(scene: .publicTimeline, from: self, transition: .show)
}
@objc private func showProfileAction(_ sender: UIAction) {
let alertController = UIAlertController(title: "Enter User ID", message: nil, preferredStyle: .alert)
alertController.addTextField()
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
guard let self = self else { return }
guard let textField = alertController?.textFields?.first else { return }
let profileViewModel = RemoteProfileViewModel(context: self.context, userID: textField.text ?? "")
self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show)
}
alertController.addAction(showAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
}
#endif

View File

@ -7,6 +7,7 @@
import os.log
import UIKit
import Combine
protocol ProfileHeaderViewControllerDelegate: class {
func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView)
@ -21,6 +22,8 @@ final class ProfileHeaderViewController: UIViewController {
weak var delegate: ProfileHeaderViewControllerDelegate?
var disposeBag = Set<AnyCancellable>()
let profileHeaderView = ProfileHeaderView()
let pageSegmentedControl: UISegmentedControl = {
let segmenetedControl = UISegmentedControl(items: ["A", "B"])
@ -33,6 +36,8 @@ final class ProfileHeaderViewController: UIViewController {
// private var isAdjustBannerImageViewForSafeAreaInset = false
private var containerSafeAreaInset: UIEdgeInsets = .zero
let needsSetupBottomShadow = CurrentValueSubject<Bool, Never>(true)
deinit {
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
@ -67,6 +72,14 @@ extension ProfileHeaderViewController {
])
pageSegmentedControl.addTarget(self, action: #selector(ProfileHeaderViewController.pageSegmentedControlValueChanged(_:)), for: .valueChanged)
needsSetupBottomShadow
.receive(on: DispatchQueue.main)
.sink { [weak self] needsSetupBottomShadow in
guard let self = self else { return }
self.setupBottomShadow()
}
.store(in: &disposeBag)
}
override func viewDidAppear(_ animated: Bool) {
@ -85,7 +98,7 @@ extension ProfileHeaderViewController {
super.viewDidLayoutSubviews()
delegate?.profileHeaderViewController(self, viewLayoutDidUpdate: view)
view.layer.setupShadow(color: UIColor.black.withAlphaComponent(0.12), alpha: Float(bottomShadowAlpha), x: 0, y: 2, blur: 2, spread: 0, roundedRect: view.bounds, byRoundingCorners: .allCorners, cornerRadii: .zero)
setupBottomShadow()
}
}
@ -105,6 +118,15 @@ extension ProfileHeaderViewController {
containerSafeAreaInset = inset
}
func setupBottomShadow() {
guard needsSetupBottomShadow.value else {
view.layer.shadowColor = nil
view.layer.shadowRadius = 0
return
}
view.layer.setupShadow(color: UIColor.black.withAlphaComponent(0.12), alpha: Float(bottomShadowAlpha), x: 0, y: 2, blur: 2, spread: 0, roundedRect: view.bounds, byRoundingCorners: .allCorners, cornerRadii: .zero)
}
private func updateHeaderBottomShadow(progress: CGFloat) {
let alpha = min(max(0, 10 * progress - 9), 1)
if bottomShadowAlpha != alpha {

View File

@ -34,7 +34,7 @@ extension ProfileRelationshipActionButton {
setTitleColor(UIColor.white.withAlphaComponent(0.5), for: .highlighted)
setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor), for: .normal)
setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor.withAlphaComponent(0.5)), for: .highlighted)
setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor), for: .disabled)
setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor.withAlphaComponent(0.5)), for: .disabled)
if let option = actionOptionSet.highPriorityAction(except: .editOptions), option == .blocked {
isEnabled = false

View File

@ -136,19 +136,24 @@ extension ProfileViewController {
navigationItem.titleView = UIView()
Publishers.CombineLatest3(
Publishers.CombineLatest4(
viewModel.suspended.eraseToAnyPublisher(),
viewModel.isMeBarButtonItemsHidden.eraseToAnyPublisher(),
viewModel.isReplyBarButtonItemHidden.eraseToAnyPublisher(),
viewModel.isMoreMenuBarButtonItemHidden.eraseToAnyPublisher()
)
.receive(on: DispatchQueue.main)
.sink { [weak self] isMeBarButtonItemsHidden, isReplyBarButtonItemHidden, isMoreMenuBarButtonItemHidden in
.sink { [weak self] suspended, isMeBarButtonItemsHidden, isReplyBarButtonItemHidden, isMoreMenuBarButtonItemHidden in
guard let self = self else { return }
var items: [UIBarButtonItem] = []
defer {
self.navigationItem.rightBarButtonItems = !items.isEmpty ? items : nil
}
guard !suspended else {
return
}
guard isMeBarButtonItemsHidden else {
items.append(self.settingBarButtonItem)
items.append(self.shareBarButtonItem)
@ -345,6 +350,21 @@ extension ProfileViewController {
}
}
.store(in: &disposeBag)
Publishers.CombineLatest3(
viewModel.isBlocking.eraseToAnyPublisher(),
viewModel.isBlockedBy.eraseToAnyPublisher(),
viewModel.suspended.eraseToAnyPublisher()
)
.receive(on: DispatchQueue.main)
.sink { [weak self] isBlocking, isBlockedBy, suspended in
guard let self = self else { return }
let isNeedSetHidden = isBlocking || isBlockedBy || suspended
self.profileHeaderViewController.needsSetupBottomShadow.value = !isNeedSetHidden
self.profileHeaderViewController.profileHeaderView.bioContainerView.isHidden = isNeedSetHidden
self.profileHeaderViewController.pageSegmentedControl.isHidden = isNeedSetHidden
self.viewModel.needsPagePinToTop.value = isNeedSetHidden
}
.store(in: &disposeBag)
viewModel.bioDescription
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] bio in
@ -411,6 +431,8 @@ extension ProfileViewController {
viewModel.userID.assign(to: \.value, on: userTimelineViewModel.userID).store(in: &disposeBag)
viewModel.isBlocking.assign(to: \.value, on: userTimelineViewModel.isBlocking).store(in: &disposeBag)
viewModel.isBlockedBy.assign(to: \.value, on: userTimelineViewModel.isBlockedBy).store(in: &disposeBag)
viewModel.suspended.assign(to: \.value, on: userTimelineViewModel.isSuspended).store(in: &disposeBag)
viewModel.name.assign(to: \.value, on: userTimelineViewModel.userDisplayName).store(in: &disposeBag)
}
}
@ -476,10 +498,15 @@ extension ProfileViewController: UIScrollViewDelegate {
contentOffsets.removeAll()
} else {
containerScrollView.contentOffset.y = topMaxContentOffsetY
if let customScrollViewContainerController = profileSegmentedViewController.pagingViewController.currentViewController as? ScrollViewContainer {
let contentOffsetY = scrollView.contentOffset.y - containerScrollView.contentOffset.y
customScrollViewContainerController.scrollView.contentOffset.y = contentOffsetY
if viewModel.needsPagePinToTop.value {
// do nothing
} else {
if let customScrollViewContainerController = profileSegmentedViewController.pagingViewController.currentViewController as? ScrollViewContainer {
let contentOffsetY = scrollView.contentOffset.y - containerScrollView.contentOffset.y
customScrollViewContainerController.scrollView.contentOffset.y = contentOffsetY
}
}
}
// elastically banner image
@ -538,16 +565,14 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
switch relationshipAction {
case .none:
break
case .follow, .following:
case .follow, .reqeust, .pending, .following:
UserProviderFacade.toggleUserFollowRelationship(provider: self)
.sink { _ in
// TODO: handle error
} receiveValue: { _ in
// do nothing
}
.store(in: &disposeBag)
case .pending:
break
case .muting:
guard let mastodonUser = viewModel.mastodonUser.value else { return }
let name = mastodonUser.displayNameWithFallback

View File

@ -41,7 +41,7 @@ class ProfileViewModel: NSObject {
let followersCount: CurrentValueSubject<Int?, Never>
let protected: CurrentValueSubject<Bool?, Never>
// let suspended: CurrentValueSubject<Bool, Never>
let suspended: CurrentValueSubject<Bool, Never>
let relationshipActionOptionSet = CurrentValueSubject<RelationshipActionOptionSet, Never>(.none)
let isEditing = CurrentValueSubject<Bool, Never>(false)
@ -55,6 +55,8 @@ class ProfileViewModel: NSObject {
let isMoreMenuBarButtonItemHidden = CurrentValueSubject<Bool, Never>(true)
let isMeBarButtonItemsHidden = CurrentValueSubject<Bool, Never>(true)
let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
init(context: AppContext, optionalMastodonUser mastodonUser: MastodonUser?) {
self.context = context
self.mastodonUser = CurrentValueSubject(mastodonUser)
@ -62,7 +64,6 @@ class ProfileViewModel: NSObject {
self.userID = CurrentValueSubject(mastodonUser?.id)
self.bannerImageURL = CurrentValueSubject(mastodonUser?.headerImageURL())
self.avatarImageURL = CurrentValueSubject(mastodonUser?.avatarImageURL())
// self.protected = CurrentValueSubject(twitterUser?.protected)
self.name = CurrentValueSubject(mastodonUser?.displayNameWithFallback)
self.username = CurrentValueSubject(mastodonUser?.acctWithDomain)
self.bioDescription = CurrentValueSubject(mastodonUser?.note)
@ -71,6 +72,7 @@ class ProfileViewModel: NSObject {
self.followingCount = CurrentValueSubject(mastodonUser.flatMap { Int(truncating: $0.followingCount) })
self.followersCount = CurrentValueSubject(mastodonUser.flatMap { Int(truncating: $0.followersCount) })
self.protected = CurrentValueSubject(mastodonUser?.locked)
self.suspended = CurrentValueSubject(mastodonUser?.suspended ?? false)
super.init()
relationshipActionOptionSet
@ -226,6 +228,7 @@ extension ProfileViewModel {
self.followingCount.value = mastodonUser.flatMap { Int(truncating: $0.followingCount) }
self.followersCount.value = mastodonUser.flatMap { Int(truncating: $0.followersCount) }
self.protected.value = mastodonUser?.locked
self.suspended.value = mastodonUser?.suspended ?? false
}
private func update(mastodonUser: MastodonUser?, currentMastodonUser: MastodonUser?) {
@ -255,6 +258,14 @@ extension ProfileViewModel {
// set with follow action default
var relationshipActionSet = RelationshipActionOptionSet([.follow])
if mastodonUser.locked {
relationshipActionSet.insert(.request)
}
if mastodonUser.suspended {
relationshipActionSet.insert(.suspended)
}
let isFollowing = mastodonUser.followingBy.flatMap { $0.contains(currentMastodonUser) } ?? false
if isFollowing {
relationshipActionSet.insert(.following)
@ -308,11 +319,13 @@ extension ProfileViewModel {
enum RelationshipAction: Int, CaseIterable {
case none // set hide from UI
case follow
case reqeust
case pending
case following
case muting
case blocked
case blocking
case suspended
case edit
case editing
@ -327,11 +340,13 @@ extension ProfileViewModel {
static let none = RelationshipAction.none.option
static let follow = RelationshipAction.follow.option
static let request = RelationshipAction.reqeust.option
static let pending = RelationshipAction.pending.option
static let following = RelationshipAction.following.option
static let muting = RelationshipAction.muting.option
static let blocked = RelationshipAction.blocked.option
static let blocking = RelationshipAction.blocking.option
static let suspended = RelationshipAction.suspended.option
static let edit = RelationshipAction.edit.option
static let editing = RelationshipAction.editing.option
@ -354,11 +369,13 @@ extension ProfileViewModel {
switch highPriorityAction {
case .none: return " "
case .follow: return L10n.Common.Controls.Firendship.follow
case .reqeust: return L10n.Common.Controls.Firendship.request
case .pending: return L10n.Common.Controls.Firendship.pending
case .following: return L10n.Common.Controls.Firendship.following
case .muting: return L10n.Common.Controls.Firendship.muted
case .blocked: return L10n.Common.Controls.Firendship.follow // blocked by user
case .blocking: return L10n.Common.Controls.Firendship.blocked
case .suspended: return L10n.Common.Controls.Firendship.follow
case .edit: return L10n.Common.Controls.Firendship.editInfo
case .editing: return L10n.Common.Controls.Actions.done
}
@ -372,11 +389,13 @@ extension ProfileViewModel {
switch highPriorityAction {
case .none: return Asset.Colors.Button.normal.color
case .follow: return Asset.Colors.Button.normal.color
case .reqeust: return Asset.Colors.Button.normal.color
case .pending: return Asset.Colors.Button.normal.color
case .following: return Asset.Colors.Button.normal.color
case .muting: return Asset.Colors.Background.alertYellow.color
case .blocked: return Asset.Colors.Button.disabled.color
case .blocked: return Asset.Colors.Button.normal.color
case .blocking: return Asset.Colors.Background.danger.color
case .suspended: return Asset.Colors.Button.normal.color
case .edit: return Asset.Colors.Button.normal.color
case .editing: return Asset.Colors.Button.normal.color
}

View File

@ -27,6 +27,8 @@ final class UserTimelineViewModel {
let isBlocking = CurrentValueSubject<Bool, Never>(false)
let isBlockedBy = CurrentValueSubject<Bool, Never>(false)
let isSuspended = CurrentValueSubject<Bool, Never>(false)
let userDisplayName = CurrentValueSubject<String?, Never>(nil) // for suspended prompt label
// output
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>?
@ -59,14 +61,15 @@ final class UserTimelineViewModel {
.assign(to: \.value, on: statusFetchedResultsController.domain)
.store(in: &disposeBag)
Publishers.CombineLatest3(
Publishers.CombineLatest4(
statusFetchedResultsController.objectIDs.eraseToAnyPublisher(),
isBlocking.eraseToAnyPublisher(),
isBlockedBy.eraseToAnyPublisher()
isBlockedBy.eraseToAnyPublisher(),
isSuspended.eraseToAnyPublisher()
)
.receive(on: DispatchQueue.main)
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
.sink { [weak self] objectIDs, isBlocking, isBlockedBy in
.sink { [weak self] objectIDs, isBlocking, isBlockedBy, isSuspended in
guard let self = self else { return }
guard let diffableDataSource = self.diffableDataSource else { return }
@ -89,6 +92,12 @@ final class UserTimelineViewModel {
return
}
let name = self.userDisplayName.value
guard !isSuspended else {
snapshot.appendItems([Item.emptyStateHeader(attribute: Item.EmptyStateHeaderAttribute(reason: .suspended(name: name)))], toSection: .main)
return
}
var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:]
let oldSnapshot = diffableDataSource.snapshot()
for item in oldSnapshot.itemIdentifiers {

View File

@ -84,8 +84,10 @@ extension TimelineHeaderView {
extension Item.EmptyStateHeaderAttribute.Reason {
var iconImage: UIImage? {
switch self {
case .noStatusFound, .blocking, .blocked, .suspended:
case .noStatusFound, .blocking, .blocked:
return UIImage(systemName: "nosign", withConfiguration: UIImage.SymbolConfiguration(pointSize: 64, weight: .bold))!
case .suspended:
return UIImage(systemName: "person.crop.circle.badge.xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 64, weight: .bold))!
}
}
@ -97,8 +99,12 @@ extension Item.EmptyStateHeaderAttribute.Reason {
return L10n.Common.Controls.Timeline.Header.blockingWarning
case .blocked:
return L10n.Common.Controls.Timeline.Header.blockedWarning
case .suspended:
return L10n.Common.Controls.Timeline.Header.suspendedWarning
case .suspended(let name):
if let name = name {
return L10n.Common.Controls.Timeline.Header.userSuspendedWarning(name)
} else {
return L10n.Common.Controls.Timeline.Header.suspendedWarning
}
}
}
}

View File

@ -158,6 +158,7 @@ extension APIService {
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
let domain = mastodonAuthenticationBox.domain
let authorization = mastodonAuthenticationBox.userAuthorization
let requestMastodonUserID = mastodonAuthenticationBox.userID
return Mastodon.API.Account.follow(
session: session,
@ -166,22 +167,50 @@ extension APIService {
followQueryType: followQueryType,
authorization: authorization
)
.handleEvents(receiveCompletion: { [weak self] completion in
guard let _ = self else { return }
switch completion {
case .failure(let error):
// TODO: handle error
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Relationship] update follow fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
break
case .finished:
switch followQueryType {
case .follow:
break
case .unfollow:
break
// .handleEvents(receiveCompletion: { [weak self] completion in
// guard let _ = self else { return }
// switch completion {
// case .failure(let error):
// // TODO: handle error
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Relationship] update follow fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
// break
// case .finished:
// switch followQueryType {
// case .follow:
// break
// case .unfollow:
// break
// }
// }
// })
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> in
let managedObjectContext = self.backgroundManagedObjectContext
return managedObjectContext.performChanges {
let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID)
requestMastodonUserRequest.fetchLimit = 1
guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
let lookUpMastodonUserRequest = MastodonUser.sortedFetchRequest
lookUpMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: mastodonUserID)
lookUpMastodonUserRequest.fetchLimit = 1
let lookUpMastodonuser = managedObjectContext.safeFetch(lookUpMastodonUserRequest).first
if let lookUpMastodonuser = lookUpMastodonuser {
let entity = response.value
APIService.CoreData.update(user: lookUpMastodonuser, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
}
}
})
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Relationship> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}

View File

@ -95,6 +95,9 @@ extension APIService.CoreData {
user.update(statusesCount: property.statusesCount)
user.update(followingCount: property.followingCount)
user.update(followersCount: property.followersCount)
user.update(locked: property.locked)
property.bot.flatMap { user.update(bot: $0) }
property.suspended.flatMap { user.update(suspended: $0) }
user.didUpdate(at: networkDate)
}