WIP: Comment out and replace user with status (IOS-192)
This commit is contained in:
parent
a9fc62eda4
commit
2be8d5b8df
|
@ -120,11 +120,12 @@ extension DataSourceFacade {
|
|||
extension DataSourceFacade {
|
||||
static func responseToShowHideReblogAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
account: Mastodon.Entity.Account
|
||||
) async throws {
|
||||
_ = try await dependency.context.apiService.toggleShowReblogs(
|
||||
for: user,
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox)
|
||||
#warning("TODO: Implement")
|
||||
// _ = try await dependency.context.apiService.toggleShowReblogs(
|
||||
// for: user,
|
||||
// authenticationBox: dependency.authContext.mastodonAuthenticationBox)
|
||||
}
|
||||
|
||||
static func responseToShowHideReblogAction(
|
||||
|
|
|
@ -6,20 +6,20 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserMuteAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
account: Mastodon.Entity.Account
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await dependency.context.apiService.toggleMute(
|
||||
user: user,
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox,
|
||||
account: account
|
||||
)
|
||||
} // end func
|
||||
}
|
||||
|
|
|
@ -49,17 +49,63 @@ extension DataSourceFacade {
|
|||
@MainActor
|
||||
static func coordinateToProfileScene(
|
||||
provider: ViewControllerWithDependencies & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
username: String,
|
||||
domain: String
|
||||
) async {
|
||||
guard let user = user.object(in: provider.context.managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
provider.coordinator.showLoading()
|
||||
|
||||
Task {
|
||||
do {
|
||||
guard let account = try await provider.context.apiService.fetchUser(username: username,
|
||||
domain: domain,
|
||||
authenticationBox: provider.authContext.mastodonAuthenticationBox) else {
|
||||
return provider.coordinator.hideLoading()
|
||||
}
|
||||
|
||||
provider.coordinator.hideLoading()
|
||||
|
||||
await coordinateToProfileScene(provider: provider, account: account)
|
||||
} catch {
|
||||
provider.coordinator.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func coordinateToProfileScene(
|
||||
provider: ViewControllerWithDependencies & AuthContextProvider,
|
||||
domain: String,
|
||||
accountID: String
|
||||
) async {
|
||||
provider.coordinator.showLoading()
|
||||
|
||||
Task {
|
||||
do {
|
||||
let account = try await provider.context.apiService.accountInfo(
|
||||
domain: domain,
|
||||
userID: accountID,
|
||||
authorization: provider.authContext.mastodonAuthenticationBox.userAuthorization
|
||||
).value
|
||||
|
||||
provider.coordinator.hideLoading()
|
||||
|
||||
await coordinateToProfileScene(provider: provider, account: account)
|
||||
} catch {
|
||||
provider.coordinator.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func coordinateToProfileScene(
|
||||
provider: ViewControllerWithDependencies & AuthContextProvider,
|
||||
account: Mastodon.Entity.Account
|
||||
) {
|
||||
|
||||
let profileViewModel = ProfileViewModel(
|
||||
context: provider.context,
|
||||
authContext: provider.authContext,
|
||||
optionalMastodonUser: user
|
||||
account: account
|
||||
)
|
||||
|
||||
_ = provider.coordinator.present(
|
||||
|
@ -68,31 +114,6 @@ extension DataSourceFacade {
|
|||
transition: .show
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func coordinateToProfileScene(
|
||||
provider: ViewControllerWithDependencies & AuthContextProvider,
|
||||
account: Mastodon.Entity.Account
|
||||
) async {
|
||||
provider.coordinator.showLoading()
|
||||
|
||||
guard let domain = account.domain else { return provider.coordinator.hideLoading() }
|
||||
|
||||
Task {
|
||||
do {
|
||||
let user = try await provider.context.apiService.fetchUser(username: account.username,
|
||||
domain: domain,
|
||||
authenticationBox: provider.authContext.mastodonAuthenticationBox)
|
||||
provider.coordinator.hideLoading()
|
||||
|
||||
if let user {
|
||||
await coordinateToProfileScene(provider: provider, user: user.asRecord)
|
||||
}
|
||||
} catch {
|
||||
provider.coordinator.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DataSourceFacade {
|
||||
|
@ -112,42 +133,31 @@ extension DataSourceFacade {
|
|||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let mentions = status.entity.mentions ?? []
|
||||
|
||||
guard let mention = mentions.first(where: { $0.url == href }) else {
|
||||
_ = provider.coordinator.present(
|
||||
_ = provider.coordinator.present(
|
||||
scene: .safari(url: url),
|
||||
from: provider,
|
||||
transition: .safariPresent(animated: true, completion: nil)
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let userID = mention.id
|
||||
let profileViewModel: ProfileViewModel = {
|
||||
// check if self
|
||||
guard userID != provider.authContext.mastodonAuthenticationBox.userID else {
|
||||
return MeProfileViewModel(context: provider.context, authContext: provider.authContext)
|
||||
}
|
||||
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.fetchLimit = 1
|
||||
request.predicate = MastodonUser.predicate(domain: domain, id: userID)
|
||||
let _user = provider.context.managedObjectContext.safeFetch(request).first
|
||||
|
||||
if let user = _user {
|
||||
return ProfileViewModel(context: provider.context, authContext: provider.authContext, optionalMastodonUser: user)
|
||||
} else {
|
||||
return RemoteProfileViewModel(context: provider.context, authContext: provider.authContext, userID: userID)
|
||||
}
|
||||
}()
|
||||
|
||||
_ = provider.coordinator.present(
|
||||
scene: .profile(viewModel: profileViewModel),
|
||||
from: provider,
|
||||
transition: .show
|
||||
)
|
||||
#warning("TODO: Implement")
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: provider, domain: "", accountID: mention.id)
|
||||
// let profileViewModel = ProfileViewModel(
|
||||
// context: provider.context,
|
||||
// authContext: provider.authContext,
|
||||
// account: status.entity.account
|
||||
// )
|
||||
//
|
||||
// _ = provider.coordinator.present(
|
||||
// scene: .profile(viewModel: profileViewModel),
|
||||
// from: provider,
|
||||
// transition: .show
|
||||
// )
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -166,20 +176,11 @@ extension DataSourceFacade {
|
|||
|
||||
static func createActivityViewController(
|
||||
dependency: NeedsDependency,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
) async throws -> UIActivityViewController? {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let activityItems: [Any] = try await managedObjectContext.perform {
|
||||
guard let user = user.object(in: managedObjectContext) else { return [] }
|
||||
return user.activityItems
|
||||
}
|
||||
guard !activityItems.isEmpty else {
|
||||
assertionFailure()
|
||||
return nil
|
||||
}
|
||||
account: Mastodon.Entity.Account
|
||||
) -> UIActivityViewController {
|
||||
|
||||
let activityViewController = await UIActivityViewController(
|
||||
activityItems: activityItems,
|
||||
let activityViewController = UIActivityViewController(
|
||||
activityItems: [account.url],
|
||||
applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)]
|
||||
)
|
||||
return activityViewController
|
||||
|
|
|
@ -144,8 +144,7 @@ extension DataSourceFacade {
|
|||
extension DataSourceFacade {
|
||||
|
||||
struct MenuContext {
|
||||
let author: ManagedObjectRecord<MastodonUser>? // todo: Remove once IOS-192 is ready
|
||||
let authorEntity: Mastodon.Entity.Account?
|
||||
let author: Mastodon.Entity.Account
|
||||
let statusViewModel: StatusView.ViewModel?
|
||||
let button: UIButton?
|
||||
let barButtonItem: UIBarButtonItem?
|
||||
|
@ -176,17 +175,9 @@ extension DataSourceFacade {
|
|||
guard let dependency else { return }
|
||||
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let _user: ManagedObjectRecord<MastodonUser>? = try? await managedObjectContext.perform {
|
||||
guard let user = menuContext.author?.object(in: managedObjectContext) else { return nil }
|
||||
return ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
}
|
||||
|
||||
guard let user = _user else { return }
|
||||
|
||||
try await DataSourceFacade.responseToShowHideReblogAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
account: menuContext.author
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -207,17 +198,11 @@ extension DataSourceFacade {
|
|||
title: actionContext.isMuting ? L10n.Common.Controls.Friendship.unmute : L10n.Common.Controls.Friendship.mute,
|
||||
style: .destructive
|
||||
) { [weak dependency] _ in
|
||||
guard let dependency = dependency else { return }
|
||||
guard let dependency else { return }
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let _user: ManagedObjectRecord<MastodonUser>? = try? await managedObjectContext.perform {
|
||||
guard let user = menuContext.author?.object(in: managedObjectContext) else { return nil }
|
||||
return ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
}
|
||||
guard let user = _user else { return }
|
||||
try await DataSourceFacade.responseToUserMuteAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
account: menuContext.author
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -235,19 +220,13 @@ extension DataSourceFacade {
|
|||
title: actionContext.isBlocking ? L10n.Common.Controls.Friendship.unblock : L10n.Common.Controls.Friendship.block,
|
||||
style: .destructive
|
||||
) { [weak dependency] _ in
|
||||
guard let dependency = dependency else { return }
|
||||
guard let dependency else { return }
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let _user: ManagedObjectRecord<MastodonUser>? = try? await managedObjectContext.perform {
|
||||
guard let user = menuContext.author?.object(in: managedObjectContext) else { return nil }
|
||||
return ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
}
|
||||
guard let user = _user else { return }
|
||||
try await DataSourceFacade.responseToUserBlockAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
user: menuContext.author
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
}
|
||||
alertController.addAction(confirmAction)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
|
||||
|
@ -255,12 +234,11 @@ extension DataSourceFacade {
|
|||
dependency.present(alertController, animated: true)
|
||||
case .reportUser:
|
||||
Task {
|
||||
guard let user = menuContext.author else { return }
|
||||
|
||||
let reportViewModel = ReportViewModel(
|
||||
context: dependency.context,
|
||||
authContext: dependency.authContext,
|
||||
user: user,
|
||||
account: menuContext.author,
|
||||
status: menuContext.statusViewModel?.originalStatus
|
||||
)
|
||||
|
||||
|
@ -272,15 +250,11 @@ extension DataSourceFacade {
|
|||
} // end Task
|
||||
|
||||
case .shareUser:
|
||||
guard let user = menuContext.author else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
let _activityViewController = try await DataSourceFacade.createActivityViewController(
|
||||
let activityViewController = DataSourceFacade.createActivityViewController(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
account: menuContext.author
|
||||
)
|
||||
guard let activityViewController = _activityViewController else { return }
|
||||
|
||||
_ = dependency.coordinator.present(
|
||||
scene: .activityViewController(
|
||||
activityViewController: activityViewController,
|
||||
|
@ -303,7 +277,6 @@ extension DataSourceFacade {
|
|||
} // end Task
|
||||
case .shareStatus:
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
guard let status: MastodonStatus = menuContext.statusViewModel?.originalStatus?.reblog ?? menuContext.statusViewModel?.originalStatus else {
|
||||
assertionFailure()
|
||||
return
|
||||
|
@ -380,11 +353,8 @@ extension DataSourceFacade {
|
|||
// do nothing, as the translation is reverted in `StatusTableViewCellDelegate` in `DataSourceProvider+StatusTableViewCellDelegate.swift`.
|
||||
break
|
||||
case .followUser(_):
|
||||
|
||||
guard let author = menuContext.author else { return }
|
||||
|
||||
try await DataSourceFacade.responseToUserFollowAction(dependency: dependency,
|
||||
user: author)
|
||||
user: menuContext.author)
|
||||
}
|
||||
} // end func
|
||||
}
|
||||
|
|
|
@ -31,20 +31,11 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
return
|
||||
}
|
||||
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = try await self.context.managedObjectContext.perform {
|
||||
return .init(objectID: notification.account.objectID)
|
||||
}
|
||||
guard let author = _author else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
try await DataSourceFacade.responseToMenuAction(
|
||||
dependency: self,
|
||||
action: action,
|
||||
menuContext: .init(
|
||||
author: author,
|
||||
authorEntity: notification.entity.account,
|
||||
author: notification.entity.account,
|
||||
statusViewModel: nil,
|
||||
button: button,
|
||||
barButtonItem: nil
|
||||
|
@ -71,16 +62,10 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
assertionFailure("only works for status data provider")
|
||||
return
|
||||
}
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = try await self.context.managedObjectContext.perform {
|
||||
return .init(objectID: notification.account.objectID)
|
||||
}
|
||||
guard let author = _author else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: author
|
||||
account: notification.entity.account
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -322,7 +307,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: notification.account.asRecord
|
||||
account: notification.entity.account
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -494,21 +479,20 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
return
|
||||
}
|
||||
switch item {
|
||||
case .status(let status):
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
case .user(let user):
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: user
|
||||
)
|
||||
case .notification:
|
||||
assertionFailure("TODO")
|
||||
default:
|
||||
assertionFailure("TODO")
|
||||
case .status(let status):
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
case .user(let user):
|
||||
break
|
||||
case .account(let account, let relationship):
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
|
||||
case .notification:
|
||||
assertionFailure("TODO")
|
||||
case .hashtag(_):
|
||||
assertionFailure("TODO")
|
||||
}
|
||||
} // end Task
|
||||
}
|
||||
|
|
|
@ -35,26 +35,14 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
}
|
||||
|
||||
switch await statusView.viewModel.header {
|
||||
case .none:
|
||||
break
|
||||
case .reply:
|
||||
let _replyToAuthor: ManagedObjectRecord<MastodonUser>? = try? await context.managedObjectContext.perform {
|
||||
guard let inReplyToAccountID = status.entity.inReplyToAccountID else { return nil }
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: domain, id: inReplyToAccountID)
|
||||
request.fetchLimit = 1
|
||||
guard let author = self.context.managedObjectContext.safeFetch(request).first else { return nil }
|
||||
return .init(objectID: author.objectID)
|
||||
}
|
||||
guard let replyToAuthor = _replyToAuthor else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: replyToAuthor
|
||||
)
|
||||
case .none:
|
||||
break
|
||||
case .reply:
|
||||
guard let replyToAccountID = status.entity.inReplyToAccountID else { return }
|
||||
#warning("TODO: Implement Domain")
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: self,
|
||||
domain: "",
|
||||
accountID: replyToAccountID)
|
||||
|
||||
case .repost:
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
|
@ -472,18 +460,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
|
||||
let status = _status.reblog ?? _status
|
||||
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = try await self.context.managedObjectContext.perform {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: self.authContext.mastodonAuthenticationBox.domain, id: status.entity.account.id)
|
||||
request.fetchLimit = 1
|
||||
guard let author = self.context.managedObjectContext.safeFetch(request).first else { return nil }
|
||||
return .init(objectID: author.objectID)
|
||||
}
|
||||
guard let author = _author else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
if case .translateStatus = action {
|
||||
DispatchQueue.main.async {
|
||||
if let cell = cell as? StatusTableViewCell {
|
||||
|
@ -517,8 +493,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
dependency: self,
|
||||
action: action,
|
||||
menuContext: .init(
|
||||
author: author,
|
||||
authorEntity: status.entity.account,
|
||||
author: status.entity.account,
|
||||
statusViewModel: statusViewModel,
|
||||
button: button,
|
||||
barButtonItem: nil
|
||||
|
@ -709,21 +684,23 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
return
|
||||
}
|
||||
switch item {
|
||||
case .status(let status):
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
case .user(let user):
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: user
|
||||
)
|
||||
case .notification:
|
||||
assertionFailure("TODO")
|
||||
default:
|
||||
assertionFailure("TODO")
|
||||
case .status(let status):
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
case .account(let account, _):
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
account: account
|
||||
)
|
||||
case .user(_):
|
||||
assertionFailure("TODO")
|
||||
case .notification:
|
||||
assertionFailure("TODO")
|
||||
case .hashtag(_):
|
||||
assertionFailure("TODO")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,43 +24,37 @@ extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvid
|
|||
switch item {
|
||||
case .account(let account, relationship: _):
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
|
||||
case .status(let status):
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
case .user(let user):
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: user
|
||||
)
|
||||
case .hashtag(let tag):
|
||||
await DataSourceFacade.coordinateToHashtagScene(
|
||||
provider: self,
|
||||
tag: tag
|
||||
)
|
||||
case .notification(let notification):
|
||||
let _status: MastodonStatus? = notification.status
|
||||
if let status = _status {
|
||||
case .status(let status):
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
} else {
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = notification.account.asRecord
|
||||
if let author = _author {
|
||||
case .user(let user):
|
||||
break
|
||||
case .hashtag(let tag):
|
||||
await DataSourceFacade.coordinateToHashtagScene(
|
||||
provider: self,
|
||||
tag: tag
|
||||
)
|
||||
case .notification(let notification):
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
|
||||
let _status: MastodonStatus? = notification.status
|
||||
if let status = _status {
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
} else {
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: author
|
||||
)
|
||||
account: notification.entity.account)
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end Task
|
||||
} // end func
|
||||
|
||||
} // end Task
|
||||
} // end func
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewableViewController {
|
||||
|
|
|
@ -13,6 +13,7 @@ import class CoreDataStack.Notification
|
|||
|
||||
enum DataSourceItem: Hashable {
|
||||
case status(record: MastodonStatus)
|
||||
@available(*, deprecated, message: "Use .account")
|
||||
case user(record: ManagedObjectRecord<MastodonUser>)
|
||||
case hashtag(tag: Mastodon.Entity.Tag)
|
||||
case notification(record: MastodonNotification)
|
||||
|
|
|
@ -295,25 +295,17 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
|
|||
transition: .show
|
||||
)
|
||||
} else {
|
||||
context.managedObjectContext.perform {
|
||||
let mastodonUserRequest = MastodonUser.sortedFetchRequest
|
||||
mastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: notification.account.id)
|
||||
mastodonUserRequest.fetchLimit = 1
|
||||
guard let mastodonUser = try? self.context.managedObjectContext.fetch(mastodonUserRequest).first else {
|
||||
return
|
||||
}
|
||||
|
||||
let profileViewModel = ProfileViewModel(
|
||||
context: self.context,
|
||||
authContext: self.viewModel.authContext,
|
||||
optionalMastodonUser: mastodonUser
|
||||
)
|
||||
_ = self.coordinator.present(
|
||||
scene: .profile(viewModel: profileViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
)
|
||||
}
|
||||
let profileViewModel = ProfileViewModel(
|
||||
context: self.context,
|
||||
authContext: self.viewModel.authContext,
|
||||
account: notification.account
|
||||
)
|
||||
_ = self.coordinator.present(
|
||||
scene: .profile(viewModel: profileViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
|
|
@ -19,7 +19,7 @@ final class ProfileAboutViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
@Published var user: MastodonUser?
|
||||
@Published var account: Mastodon.Entity.Account?
|
||||
@Published var isEditing = false
|
||||
@Published var accountForEdit: Mastodon.Entity.Account?
|
||||
|
||||
|
@ -34,23 +34,22 @@ final class ProfileAboutViewModel {
|
|||
|
||||
init(context: AppContext) {
|
||||
self.context = context
|
||||
// end init
|
||||
|
||||
$user
|
||||
.compactMap { $0 }
|
||||
.flatMap { $0.publisher(for: \.emojis) }
|
||||
.map { $0.asDictionary }
|
||||
.assign(to: &$emojiMeta)
|
||||
|
||||
$user
|
||||
.compactMap { $0 }
|
||||
.flatMap { $0.publisher(for: \.fields) }
|
||||
.assign(to: &$fields)
|
||||
|
||||
$user
|
||||
.compactMap { $0 }
|
||||
.flatMap { $0.publisher(for: \.createdAt) }
|
||||
.assign(to: &$createdAt)
|
||||
#warning("TODO: Implement")
|
||||
// $account
|
||||
// .compactMap { $0 }
|
||||
// .flatMap { $0.publisher(for: \.emojis) }
|
||||
// .map { $0.asDictionary }
|
||||
// .assign(to: &$emojiMeta)
|
||||
//
|
||||
// $account
|
||||
// .compactMap { $0 }
|
||||
// .flatMap { $0.publisher(for: \.fields) }
|
||||
// .assign(to: &$fields)
|
||||
//
|
||||
// $account
|
||||
// .compactMap { $0 }
|
||||
// .flatMap { $0.publisher(for: \.createdAt) }
|
||||
// .assign(to: &$createdAt)
|
||||
|
||||
Publishers.CombineLatest(
|
||||
$fields,
|
||||
|
|
|
@ -128,17 +128,17 @@ extension ProfileHeaderViewController {
|
|||
self.titleView.subtitleLabel.alpha = isTitleViewContentOffsetDidSet ? 1 : 0
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
viewModel.$user
|
||||
viewModel.$account
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] user in
|
||||
guard let self = self else { return }
|
||||
guard let user = user else { return }
|
||||
.sink { [weak self] account in
|
||||
guard let self, let account else { return }
|
||||
|
||||
self.profileHeaderView.prepareForReuse()
|
||||
self.profileHeaderView.configuration(user: user)
|
||||
self.profileHeaderView.configuration(account: account)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
viewModel.$relationshipActionOptionSet
|
||||
.assign(to: \.relationshipActionOptionSet, on: profileHeaderView.viewModel)
|
||||
viewModel.$relationship
|
||||
.assign(to: \.relationship, on: profileHeaderView.viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.$isMyself
|
||||
.assign(to: \.isMyself, on: profileHeaderView.viewModel)
|
||||
|
@ -269,35 +269,37 @@ extension ProfileHeaderViewController {
|
|||
// MARK: - ProfileHeaderViewDelegate
|
||||
extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
|
||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarButtonDidPressed button: AvatarButton) {
|
||||
guard let user = viewModel.user else { return }
|
||||
let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
||||
|
||||
Task {
|
||||
try await DataSourceFacade.coordinateToMediaPreviewScene(
|
||||
dependency: self,
|
||||
user: record,
|
||||
previewContext: DataSourceFacade.ImagePreviewContext(
|
||||
imageView: button.avatarImageView,
|
||||
containerView: .profileAvatar(profileHeaderView)
|
||||
)
|
||||
)
|
||||
} // end Task
|
||||
#warning("TODO: Implement")
|
||||
// guard let user = viewModel.user else { return }
|
||||
// let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
||||
//
|
||||
// Task {
|
||||
// try await DataSourceFacade.coordinateToMediaPreviewScene(
|
||||
// dependency: self,
|
||||
// user: record,
|
||||
// previewContext: DataSourceFacade.ImagePreviewContext(
|
||||
// imageView: button.avatarImageView,
|
||||
// containerView: .profileAvatar(profileHeaderView)
|
||||
// )
|
||||
// )
|
||||
// } // end Task
|
||||
}
|
||||
|
||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, bannerImageViewDidPressed imageView: UIImageView) {
|
||||
guard let user = viewModel.user else { return }
|
||||
let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
||||
|
||||
Task {
|
||||
try await DataSourceFacade.coordinateToMediaPreviewScene(
|
||||
dependency: self,
|
||||
user: record,
|
||||
previewContext: DataSourceFacade.ImagePreviewContext(
|
||||
imageView: imageView,
|
||||
containerView: .profileBanner(profileHeaderView)
|
||||
)
|
||||
)
|
||||
} // end Task
|
||||
#warning("TODO: Implement")
|
||||
// guard let account = viewModel.account else { return }
|
||||
// let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
||||
//
|
||||
// Task {
|
||||
// try await DataSourceFacade.coordinateToMediaPreviewScene(
|
||||
// dependency: self,
|
||||
// user: record,
|
||||
// previewContext: DataSourceFacade.ImagePreviewContext(
|
||||
// imageView: imageView,
|
||||
// containerView: .profileBanner(profileHeaderView)
|
||||
// )
|
||||
// )
|
||||
// } // end Task
|
||||
}
|
||||
|
||||
func profileHeaderView(
|
||||
|
@ -331,35 +333,39 @@ extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
|
|||
// do nothing
|
||||
break
|
||||
case .follower:
|
||||
guard let domain = viewModel.user?.domain,
|
||||
let userID = viewModel.user?.id
|
||||
else { return }
|
||||
let followerListViewModel = FollowerListViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
domain: domain,
|
||||
userID: userID
|
||||
)
|
||||
_ = coordinator.present(
|
||||
scene: .follower(viewModel: followerListViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
)
|
||||
#warning("TODO: Implement")
|
||||
// guard let domain = viewModel.account.domain,
|
||||
// let userID = viewModel.account.id
|
||||
// else { return }
|
||||
// let followerListViewModel = FollowerListViewModel(
|
||||
// context: context,
|
||||
// authContext: viewModel.authContext,
|
||||
// domain: domain,
|
||||
// userID: userID
|
||||
// )
|
||||
// _ = coordinator.present(
|
||||
// scene: .follower(viewModel: followerListViewModel),
|
||||
// from: self,
|
||||
// transition: .show
|
||||
// )
|
||||
break
|
||||
case .following:
|
||||
guard let domain = viewModel.user?.domain,
|
||||
let userID = viewModel.user?.id
|
||||
else { return }
|
||||
let followingListViewModel = FollowingListViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
domain: domain,
|
||||
userID: userID
|
||||
)
|
||||
_ = coordinator.present(
|
||||
scene: .following(viewModel: followingListViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
)
|
||||
#warning("TODO: Implement")
|
||||
// guard let domain = viewModel.account.domain,
|
||||
// let userID = viewModel.account.id
|
||||
// else { return }
|
||||
// let followingListViewModel = FollowingListViewModel(
|
||||
// context: context,
|
||||
// authContext: viewModel.authContext,
|
||||
// domain: domain,
|
||||
// userID: userID
|
||||
// )
|
||||
// _ = coordinator.present(
|
||||
// scene: .following(viewModel: followingListViewModel),
|
||||
// from: self,
|
||||
// transition: .show
|
||||
// )
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ final class ProfileHeaderViewModel {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
|
||||
@Published var user: MastodonUser?
|
||||
@Published var relationshipActionOptionSet: RelationshipActionOptionSet = .none
|
||||
@Published var account: Mastodon.Entity.Account?
|
||||
@Published var relationship: Mastodon.Entity.Relationship?
|
||||
|
||||
@Published var isMyself = false
|
||||
@Published var isEditing = false
|
||||
|
|
|
@ -7,49 +7,48 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension ProfileHeaderView {
|
||||
func configuration(user: MastodonUser) {
|
||||
// header
|
||||
user.publisher(for: \.header)
|
||||
.map { _ in user.headerImageURL() }
|
||||
.assign(to: \.headerImageURL, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// avatar
|
||||
user.publisher(for: \.avatar)
|
||||
.map { _ in user.avatarImageURL() }
|
||||
.assign(to: \.avatarImageURL, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// emojiMeta
|
||||
user.publisher(for: \.emojis)
|
||||
.map { $0.asDictionary }
|
||||
.assign(to: \.emojiMeta, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// name
|
||||
user.publisher(for: \.displayName)
|
||||
.map { _ in user.displayNameWithFallback }
|
||||
.assign(to: \.name, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// username
|
||||
viewModel.acct = user.acctWithDomain
|
||||
// bio
|
||||
user.publisher(for: \.note)
|
||||
.assign(to: \.note, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// dashboard
|
||||
user.publisher(for: \.statusesCount)
|
||||
.map { Int($0) }
|
||||
.assign(to: \.statusesCount, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
user.publisher(for: \.followingCount)
|
||||
.map { Int($0) }
|
||||
.assign(to: \.followingCount, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
user.publisher(for: \.followersCount)
|
||||
.map { Int($0) }
|
||||
.assign(to: \.followersCount, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
func configuration(account: Mastodon.Entity.Account) {
|
||||
#warning("TODO: Implement")
|
||||
// // header
|
||||
// account.header.publisher
|
||||
// .assign(to: \.headerImageURL, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
// // avatar
|
||||
// account.avatar.publisher
|
||||
// .assign(to: \.avatarImageURL, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
// // emojiMeta
|
||||
// account.emojis.publisher
|
||||
// .map { $0.asDictionary }
|
||||
// .assign(to: \.emojiMeta, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
// // name
|
||||
// account.publisher(for: \.displayName)
|
||||
// .map { _ in account.displayNameWithFallback }
|
||||
// .assign(to: \.name, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
// // username
|
||||
// viewModel.acct = account.acctWithDomain
|
||||
// // bio
|
||||
// account.publisher(for: \.note)
|
||||
// .assign(to: \.note, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
// // dashboard
|
||||
// account.publisher(for: \.statusesCount)
|
||||
// .map { Int($0) }
|
||||
// .assign(to: \.statusesCount, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
// account.publisher(for: \.followingCount)
|
||||
// .map { Int($0) }
|
||||
// .assign(to: \.followingCount, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
// account.publisher(for: \.followersCount)
|
||||
// .map { Int($0) }
|
||||
// .assign(to: \.followersCount, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import MastodonCore
|
|||
import MastodonUI
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonSDK
|
||||
|
||||
extension ProfileHeaderView {
|
||||
class ViewModel: ObservableObject {
|
||||
|
@ -45,15 +46,16 @@ extension ProfileHeaderView {
|
|||
|
||||
@Published var fields: [MastodonField] = []
|
||||
|
||||
@Published var relationshipActionOptionSet: RelationshipActionOptionSet = .none
|
||||
@Published var relationship: Mastodon.Entity.Relationship?
|
||||
@Published var isRelationshipActionButtonHidden = false
|
||||
@Published var isMyself = false
|
||||
|
||||
init() {
|
||||
$relationshipActionOptionSet
|
||||
.compactMap { $0.highPriorityAction(except: []) }
|
||||
.map { $0 == .none }
|
||||
.assign(to: &$isRelationshipActionButtonHidden)
|
||||
#warning("TODO: Implement")
|
||||
// $relationshipActionOptionSet
|
||||
// .compactMap { $0.highPriorityAction(except: []) }
|
||||
// .map { $0 == .none }
|
||||
// .assign(to: &$isRelationshipActionButtonHidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,13 +98,14 @@ extension ProfileHeaderView.ViewModel {
|
|||
}
|
||||
.store(in: &disposeBag)
|
||||
// follows you
|
||||
$relationshipActionOptionSet
|
||||
.map { $0.contains(.followingBy) && !$0.contains(.isMyself) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { isFollowingBy in
|
||||
view.followsYouBlurEffectView.isHidden = !isFollowingBy
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
#warning("TODO: Implement")
|
||||
// $relationshipActionOptionSet
|
||||
// .map { $0.contains(.followingBy) && !$0.contains(.isMyself) }
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { isFollowingBy in
|
||||
// view.followsYouBlurEffectView.isHidden = !isFollowingBy
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// avatar
|
||||
Publishers.CombineLatest4(
|
||||
$avatarImageURL,
|
||||
|
@ -117,18 +120,19 @@ extension ProfileHeaderView.ViewModel {
|
|||
))
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
// blur for blocking & blockingBy
|
||||
$relationshipActionOptionSet
|
||||
.map { $0.contains(.blocking) || $0.contains(.blockingBy) }
|
||||
.sink { needsImageOverlayBlurred in
|
||||
UIView.animate(withDuration: 0.33) {
|
||||
let bannerEffect: UIVisualEffect? = needsImageOverlayBlurred ? ProfileHeaderView.bannerImageViewOverlayBlurEffect : nil
|
||||
view.bannerImageViewOverlayVisualEffectView.effect = bannerEffect
|
||||
let avatarEffect: UIVisualEffect? = needsImageOverlayBlurred ? ProfileHeaderView.avatarImageViewOverlayBlurEffect : nil
|
||||
view.avatarImageViewOverlayVisualEffectView.effect = avatarEffect
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
#warning("TODO: Implement")
|
||||
// // blur for blocking & blockingBy
|
||||
// $relationshipActionOptionSet
|
||||
// .map { $0.contains(.blocking) || $0.contains(.blockingBy) }
|
||||
// .sink { needsImageOverlayBlurred in
|
||||
// UIView.animate(withDuration: 0.33) {
|
||||
// let bannerEffect: UIVisualEffect? = needsImageOverlayBlurred ? ProfileHeaderView.bannerImageViewOverlayBlurEffect : nil
|
||||
// view.bannerImageViewOverlayVisualEffectView.effect = bannerEffect
|
||||
// let avatarEffect: UIVisualEffect? = needsImageOverlayBlurred ? ProfileHeaderView.avatarImageViewOverlayBlurEffect : nil
|
||||
// view.avatarImageViewOverlayVisualEffectView.effect = avatarEffect
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// name
|
||||
Publishers.CombineLatest4(
|
||||
$isEditing.removeDuplicates(),
|
||||
|
@ -182,17 +186,18 @@ extension ProfileHeaderView.ViewModel {
|
|||
view.bioMetaText.configure(content: metaContent)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
$relationshipActionOptionSet
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { optionSet in
|
||||
let isBlocking = optionSet.contains(.blocking)
|
||||
let isBlockedBy = optionSet.contains(.blockingBy)
|
||||
let isSuspended = optionSet.contains(.suspended)
|
||||
let isNeedsHidden = isBlocking || isBlockedBy || isSuspended
|
||||
|
||||
view.bioMetaText.textView.isHidden = isNeedsHidden
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
#warning("TODO: Implement")
|
||||
// $relationshipActionOptionSet
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { optionSet in
|
||||
// let isBlocking = optionSet.contains(.blocking)
|
||||
// let isBlockedBy = optionSet.contains(.blockingBy)
|
||||
// let isSuspended = optionSet.contains(.suspended)
|
||||
// let isNeedsHidden = isBlocking || isBlockedBy || isSuspended
|
||||
//
|
||||
// view.bioMetaText.textView.isHidden = isNeedsHidden
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// dashboard
|
||||
$isMyself
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
@ -245,22 +250,23 @@ extension ProfileHeaderView.ViewModel {
|
|||
$isRelationshipActionButtonHidden
|
||||
.assign(to: \.isHidden, on: view.relationshipActionButtonShadowContainer)
|
||||
.store(in: &disposeBag)
|
||||
Publishers.CombineLatest3(
|
||||
$relationshipActionOptionSet,
|
||||
$isEditing,
|
||||
$isUpdating
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { relationshipActionOptionSet, isEditing, isUpdating in
|
||||
if relationshipActionOptionSet.contains(.edit) {
|
||||
// check .edit state and set .editing when isEditing
|
||||
view.relationshipActionButton.configure(actionOptionSet: isUpdating ? .updating : (isEditing ? .editing : .edit))
|
||||
view.configure(state: isEditing ? .editing : .normal)
|
||||
} else {
|
||||
view.relationshipActionButton.configure(actionOptionSet: relationshipActionOptionSet)
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
#warning("TODO: Implement")
|
||||
// Publishers.CombineLatest2(
|
||||
// $relationshipActionOptionSet,
|
||||
// $isEditing,
|
||||
// $isUpdating
|
||||
// )
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { relationshipActionOptionSet, isEditing, isUpdating in
|
||||
// if relationshipActionOptionSet.contains(.edit) {
|
||||
// // check .edit state and set .editing when isEditing
|
||||
// view.relationshipActionButton.configure(actionOptionSet: isUpdating ? .updating : (isEditing ? .editing : .edit))
|
||||
// view.configure(state: isEditing ? .editing : .normal)
|
||||
// } else {
|
||||
// view.relationshipActionButton.configure(actionOptionSet: relationshipActionOptionSet)
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -542,27 +542,3 @@ extension ProfileHeaderView: ProfileStatusDashboardViewDelegate {
|
|||
delegate?.profileHeaderView(self, profileStatusDashboardView: dashboardView, dashboardMeterViewDidPressed: dashboardMeterView, meter: meter)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
import SwiftUI
|
||||
|
||||
struct ProfileHeaderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
UIViewPreview(width: 375) {
|
||||
let banner = ProfileHeaderView()
|
||||
banner.bannerImageView.image = UIImage(named: "lucas-ludwig")
|
||||
return banner
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 800))
|
||||
UIViewPreview(width: 375) {
|
||||
let banner = ProfileHeaderView()
|
||||
//banner.bannerImageView.image = UIImage(named: "peter-luo")
|
||||
return banner
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
.previewLayout(.fixed(width: 375, height: 800))
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -16,19 +16,12 @@ final class MeProfileViewModel: ProfileViewModel {
|
|||
|
||||
@MainActor
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
let user = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
let me = authContext.mastodonAuthenticationBox.authentication.account()
|
||||
super.init(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
optionalMastodonUser: user
|
||||
account: me
|
||||
)
|
||||
|
||||
$me
|
||||
.sink { [weak self] me in
|
||||
guard let self = self else { return }
|
||||
self.user = me
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
@ -37,17 +30,9 @@ final class MeProfileViewModel: ProfileViewModel {
|
|||
|
||||
Task {
|
||||
do {
|
||||
|
||||
_ = try await context.apiService.authenticatedUserInfo(authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
|
||||
try await context.managedObjectContext.performChanges {
|
||||
guard let me = self.authContext.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
self.me = me
|
||||
}
|
||||
let account = try await context.apiService.authenticatedUserInfo(authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
self.account = account
|
||||
self.me = account
|
||||
} catch {
|
||||
// do nothing?
|
||||
}
|
||||
|
|
|
@ -203,7 +203,7 @@ extension ProfileViewController {
|
|||
.store(in: &disposeBag)
|
||||
|
||||
Publishers.CombineLatest4 (
|
||||
viewModel.relationshipViewModel.$isSuspended,
|
||||
viewModel.account.suspended.publisher,
|
||||
profileHeaderViewController.viewModel.$isTitleViewDisplaying,
|
||||
editingAndUpdatingPublisher.eraseToAnyPublisher(),
|
||||
barButtonItemHiddenPublisher.eraseToAnyPublisher()
|
||||
|
@ -296,43 +296,43 @@ extension ProfileViewController {
|
|||
|
||||
private func bindViewModel() {
|
||||
// header
|
||||
#warning("TODO: Implement")
|
||||
let headerViewModel = profileHeaderViewController.viewModel!
|
||||
viewModel.$user
|
||||
.assign(to: \.user, on: headerViewModel)
|
||||
.store(in: &disposeBag)
|
||||
// viewModel.$account
|
||||
// .assign(to: \.account, on: headerViewModel)
|
||||
// .store(in: &disposeBag)
|
||||
viewModel.$isEditing
|
||||
.assign(to: \.isEditing, on: headerViewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.$isUpdating
|
||||
.assign(to: \.isUpdating, on: headerViewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.relationshipViewModel.$isMyself
|
||||
.assign(to: \.isMyself, on: headerViewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.relationshipViewModel.$optionSet
|
||||
.map { $0 ?? .none }
|
||||
.assign(to: \.relationshipActionOptionSet, on: headerViewModel)
|
||||
// viewModel.relationshipViewModel.$isMyself
|
||||
// .assign(to: \.isMyself, on: headerViewModel)
|
||||
// .store(in: &disposeBag)
|
||||
viewModel.$relationship
|
||||
.assign(to: \.relationship, on: headerViewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.$accountForEdit
|
||||
.assign(to: \.accountForEdit, on: headerViewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
#warning("TODO: Implement")
|
||||
// timeline
|
||||
[
|
||||
viewModel.postsUserTimelineViewModel,
|
||||
viewModel.repliesUserTimelineViewModel,
|
||||
viewModel.mediaUserTimelineViewModel,
|
||||
].forEach { userTimelineViewModel in
|
||||
viewModel.relationshipViewModel.$isBlocking.assign(to: \.isBlocking, on: userTimelineViewModel).store(in: &disposeBag)
|
||||
viewModel.relationshipViewModel.$isBlockingBy.assign(to: \.isBlockedBy, on: userTimelineViewModel).store(in: &disposeBag)
|
||||
viewModel.relationshipViewModel.$isSuspended.assign(to: \.isSuspended, on: userTimelineViewModel).store(in: &disposeBag)
|
||||
}
|
||||
// [
|
||||
// viewModel.postsUserTimelineViewModel,
|
||||
// viewModel.repliesUserTimelineViewModel,
|
||||
// viewModel.mediaUserTimelineViewModel,
|
||||
// ].forEach { userTimelineViewModel in
|
||||
// viewModel.relationshipViewModel.$isBlocking.assign(to: \.isBlocking, on: userTimelineViewModel).store(in: &disposeBag)
|
||||
// viewModel.relationshipViewModel.$isBlockingBy.assign(to: \.isBlockedBy, on: userTimelineViewModel).store(in: &disposeBag)
|
||||
// viewModel.relationshipViewModel.$isSuspended.assign(to: \.isSuspended, on: userTimelineViewModel).store(in: &disposeBag)
|
||||
// }
|
||||
|
||||
// about
|
||||
let aboutViewModel = viewModel.profileAboutViewModel
|
||||
viewModel.$user
|
||||
.assign(to: \.user, on: aboutViewModel)
|
||||
.store(in: &disposeBag)
|
||||
// viewModel.$account
|
||||
// .assign(to: \.account, on: aboutViewModel)
|
||||
// .store(in: &disposeBag)
|
||||
viewModel.$isEditing
|
||||
.assign(to: \.isEditing, on: aboutViewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
@ -374,7 +374,7 @@ extension ProfileViewController {
|
|||
}
|
||||
.store(in: &disposeBag)
|
||||
Publishers.CombineLatest(
|
||||
profileHeaderViewController.viewModel.$user,
|
||||
profileHeaderViewController.viewModel.$account,
|
||||
profileHeaderViewController.profileHeaderView.viewModel.viewDidAppear
|
||||
)
|
||||
.sink { [weak self] (user, _) in
|
||||
|
@ -382,7 +382,7 @@ extension ProfileViewController {
|
|||
Task {
|
||||
_ = try await self.context.apiService.fetchUser(
|
||||
username: user.username,
|
||||
domain: user.domainFromAcct,
|
||||
domain: "user.domainFromAcct",
|
||||
authenticationBox: self.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
}
|
||||
|
@ -392,26 +392,23 @@ extension ProfileViewController {
|
|||
|
||||
private func bindMoreBarButtonItem() {
|
||||
Publishers.CombineLatest(
|
||||
viewModel.$user,
|
||||
viewModel.relationshipViewModel.$optionSet
|
||||
viewModel.$account,
|
||||
viewModel.$relationship
|
||||
)
|
||||
.asyncMap { [weak self] user, relationshipSet -> UIMenu? in
|
||||
guard let self = self else { return nil }
|
||||
guard let user = user else {
|
||||
return nil
|
||||
}
|
||||
.asyncMap { [weak self] user, relationship -> UIMenu? in
|
||||
guard let self, let relationship else { return nil }
|
||||
|
||||
let name = user.displayNameWithFallback
|
||||
let _ = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
|
||||
var menuActions: [MastodonMenu.Action] = [
|
||||
.muteUser(.init(name: name, isMuting: self.viewModel.relationshipViewModel.isMuting)),
|
||||
.blockUser(.init(name: name, isBlocking: self.viewModel.relationshipViewModel.isBlocking)),
|
||||
.muteUser(.init(name: name, isMuting: relationship.muting ?? false)),
|
||||
.blockUser(.init(name: name, isBlocking: relationship.blocking)),
|
||||
.reportUser(.init(name: name)),
|
||||
.shareUser(.init(name: name)),
|
||||
]
|
||||
|
||||
if let me = self.viewModel?.me, me.following.contains(user) {
|
||||
let showReblogs = me.showingReblogsBy.contains(user)
|
||||
if relationship.following {
|
||||
let showReblogs = relationship.showingReblogs ?? false// me.showingReblogsBy.contains(user)
|
||||
let context = MastodonMenu.HideReblogsActionContext(showReblogs: showReblogs)
|
||||
menuActions.insert(.hideReblogs(context), at: 1)
|
||||
}
|
||||
|
@ -473,26 +470,6 @@ extension ProfileViewController {
|
|||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
// private func bindProfileRelationship() {
|
||||
//
|
||||
// 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.viewModel.needsSetupBottomShadow.value = !isNeedSetHidden
|
||||
// self.profileHeaderViewController.profileHeaderView.bioContainerView.isHidden = isNeedSetHidden
|
||||
// self.profileHeaderViewController.viewModel.needsFiledCollectionViewHidden.value = isNeedSetHidden
|
||||
// self.profileHeaderViewController.buttonBar.isUserInteractionEnabled = !isNeedSetHidden
|
||||
// self.viewModel.needsPagePinToTop.value = isNeedSetHidden
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// } // end func bindProfileRelationship
|
||||
|
||||
private func handleMetaPress(_ meta: Meta) {
|
||||
switch meta {
|
||||
case .url(_, _, let url, _):
|
||||
|
@ -525,24 +502,19 @@ extension ProfileViewController {
|
|||
}
|
||||
|
||||
@objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
guard let user = viewModel.user else { return }
|
||||
let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
||||
Task {
|
||||
let _activityViewController = try await DataSourceFacade.createActivityViewController(
|
||||
dependency: self,
|
||||
user: record
|
||||
)
|
||||
guard let activityViewController = _activityViewController else { return }
|
||||
_ = self.coordinator.present(
|
||||
scene: .activityViewController(
|
||||
activityViewController: activityViewController,
|
||||
sourceView: nil,
|
||||
barButtonItem: sender
|
||||
),
|
||||
from: self,
|
||||
transition: .activityViewControllerPresent(animated: true, completion: nil)
|
||||
)
|
||||
} // end Task
|
||||
let activityViewController = DataSourceFacade.createActivityViewController(
|
||||
dependency: self,
|
||||
account: viewModel.account
|
||||
)
|
||||
_ = self.coordinator.present(
|
||||
scene: .activityViewController(
|
||||
activityViewController: activityViewController,
|
||||
sourceView: nil,
|
||||
barButtonItem: sender
|
||||
),
|
||||
from: self,
|
||||
transition: .activityViewControllerPresent(animated: true, completion: nil)
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func favoriteBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
|
@ -556,8 +528,8 @@ extension ProfileViewController {
|
|||
}
|
||||
|
||||
@objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
guard let mastodonUser = viewModel.user else { return }
|
||||
let mention = "@" + mastodonUser.acct
|
||||
|
||||
let mention = "@" + viewModel.account.acct
|
||||
UITextChecker.learnWord(mention)
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
|
@ -683,34 +655,6 @@ extension ProfileViewController: TabBarPagerDataSource {
|
|||
}
|
||||
}
|
||||
|
||||
//// MARK: - UIScrollViewDelegate
|
||||
//extension ProfileViewController: UIScrollViewDelegate {
|
||||
//
|
||||
// func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
// contentOffsets[profileSegmentedViewController.pagingViewController.currentIndex!] = scrollView.contentOffset.y
|
||||
// let topMaxContentOffsetY = profileSegmentedViewController.view.frame.minY - ProfileHeaderViewController.headerMinHeight - containerScrollView.safeAreaInsets.top
|
||||
// if scrollView.contentOffset.y < topMaxContentOffsetY {
|
||||
// self.containerScrollView.contentOffset.y = scrollView.contentOffset.y
|
||||
// for postTimelineView in profileSegmentedViewController.pagingViewController.viewModel.viewControllers {
|
||||
// postTimelineView.scrollView?.contentOffset.y = 0
|
||||
// }
|
||||
// contentOffsets.removeAll()
|
||||
// } else {
|
||||
// containerScrollView.contentOffset.y = topMaxContentOffsetY
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension ProfileViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
|
@ -723,140 +667,140 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
|||
profileHeaderView: ProfileHeaderView,
|
||||
relationshipButtonDidPressed button: ProfileRelationshipActionButton
|
||||
) {
|
||||
let relationshipActionSet = viewModel.relationshipViewModel.optionSet ?? .none
|
||||
|
||||
// let relationshipActionSet = viewModel.relationshipViewModel.optionSet ?? .none
|
||||
#warning("TODO: Implement")
|
||||
// handle edit logic for editable profile
|
||||
// handle relationship logic for non-editable profile
|
||||
if relationshipActionSet.contains(.edit) {
|
||||
// do nothing when updating
|
||||
guard !viewModel.isUpdating else { return }
|
||||
|
||||
guard let profileHeaderViewModel = profileHeaderViewController.viewModel else { return }
|
||||
guard let profileAboutViewModel = profilePagingViewController.viewModel.profileAboutViewController.viewModel else { return }
|
||||
|
||||
let isEdited = profileHeaderViewModel.isEdited || profileAboutViewModel.isEdited
|
||||
|
||||
if isEdited {
|
||||
// update profile when edited
|
||||
viewModel.isUpdating = true
|
||||
Task { @MainActor in
|
||||
do {
|
||||
// TODO: handle error
|
||||
_ = try await viewModel.updateProfileInfo(
|
||||
headerProfileInfo: profileHeaderViewModel.profileInfoEditing,
|
||||
aboutProfileInfo: profileAboutViewModel.profileInfoEditing
|
||||
)
|
||||
self.viewModel.isEditing = false
|
||||
|
||||
} catch {
|
||||
let alertController = UIAlertController(
|
||||
for: error,
|
||||
title: L10n.Common.Alerts.EditProfileFailure.title,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
|
||||
alertController.addAction(okAction)
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
// finish updating
|
||||
self.viewModel.isUpdating = false
|
||||
} // end Task
|
||||
} else {
|
||||
// set `updating` then toggle `edit` state
|
||||
viewModel.isUpdating = true
|
||||
viewModel.fetchEditProfileInfo()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] completion in
|
||||
guard let self = self else { return }
|
||||
defer {
|
||||
// finish updating
|
||||
self.viewModel.isUpdating = false
|
||||
}
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
let alertController = UIAlertController(for: error, title: L10n.Common.Alerts.EditProfileFailure.title, preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
_ = self.coordinator.present(
|
||||
scene: .alertController(alertController: alertController),
|
||||
from: nil,
|
||||
transition: .alertController(animated: true, completion: nil)
|
||||
)
|
||||
case .finished:
|
||||
// enter editing mode
|
||||
self.viewModel.isEditing.toggle()
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
self.viewModel.accountForEdit = response.value
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
} else {
|
||||
guard let relationshipAction = relationshipActionSet.highPriorityAction(except: .editOptions) else { return }
|
||||
switch relationshipAction {
|
||||
case .none:
|
||||
break
|
||||
case .follow, .request, .pending, .following:
|
||||
guard let user = viewModel.user else { return }
|
||||
let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
Task {
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: self,
|
||||
user: record
|
||||
)
|
||||
}
|
||||
case .muting:
|
||||
guard let user = viewModel.user else { return }
|
||||
let name = user.displayNameWithFallback
|
||||
|
||||
let alertController = UIAlertController(
|
||||
title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.title,
|
||||
message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.message(name),
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unmute, style: .default) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
Task {
|
||||
try await DataSourceFacade.responseToUserMuteAction(
|
||||
dependency: self,
|
||||
user: record
|
||||
)
|
||||
}
|
||||
}
|
||||
alertController.addAction(unmuteAction)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
case .blocking:
|
||||
guard let user = viewModel.user else { return }
|
||||
let name = user.displayNameWithFallback
|
||||
|
||||
let alertController = UIAlertController(
|
||||
title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.title,
|
||||
message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.message(name),
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
let unblockAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unblock, style: .default) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
Task {
|
||||
try await DataSourceFacade.responseToUserBlockAction(
|
||||
dependency: self,
|
||||
user: record
|
||||
)
|
||||
}
|
||||
}
|
||||
alertController.addAction(unblockAction)
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
case .blocked, .showReblogs, .isMyself,.followingBy, .blockingBy, .suspended, .edit, .editing, .updating:
|
||||
break
|
||||
}
|
||||
}
|
||||
// if relationshipActionSet.contains(.edit) {
|
||||
// // do nothing when updating
|
||||
// guard !viewModel.isUpdating else { return }
|
||||
//
|
||||
// guard let profileHeaderViewModel = profileHeaderViewController.viewModel else { return }
|
||||
// guard let profileAboutViewModel = profilePagingViewController.viewModel.profileAboutViewController.viewModel else { return }
|
||||
//
|
||||
// let isEdited = profileHeaderViewModel.isEdited || profileAboutViewModel.isEdited
|
||||
//
|
||||
// if isEdited {
|
||||
// // update profile when edited
|
||||
// viewModel.isUpdating = true
|
||||
// Task { @MainActor in
|
||||
// do {
|
||||
// // TODO: handle error
|
||||
// _ = try await viewModel.updateProfileInfo(
|
||||
// headerProfileInfo: profileHeaderViewModel.profileInfoEditing,
|
||||
// aboutProfileInfo: profileAboutViewModel.profileInfoEditing
|
||||
// )
|
||||
// self.viewModel.isEditing = false
|
||||
//
|
||||
// } catch {
|
||||
// let alertController = UIAlertController(
|
||||
// for: error,
|
||||
// title: L10n.Common.Alerts.EditProfileFailure.title,
|
||||
// preferredStyle: .alert
|
||||
// )
|
||||
// let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
|
||||
// alertController.addAction(okAction)
|
||||
// self.present(alertController, animated: true)
|
||||
// }
|
||||
//
|
||||
// // finish updating
|
||||
// self.viewModel.isUpdating = false
|
||||
// } // end Task
|
||||
// } else {
|
||||
// // set `updating` then toggle `edit` state
|
||||
// viewModel.isUpdating = true
|
||||
// viewModel.fetchEditProfileInfo()
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] completion in
|
||||
// guard let self = self else { return }
|
||||
// defer {
|
||||
// // finish updating
|
||||
// self.viewModel.isUpdating = false
|
||||
// }
|
||||
// switch completion {
|
||||
// case .failure(let error):
|
||||
// let alertController = UIAlertController(for: error, title: L10n.Common.Alerts.EditProfileFailure.title, preferredStyle: .alert)
|
||||
// let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
|
||||
// alertController.addAction(okAction)
|
||||
// _ = self.coordinator.present(
|
||||
// scene: .alertController(alertController: alertController),
|
||||
// from: nil,
|
||||
// transition: .alertController(animated: true, completion: nil)
|
||||
// )
|
||||
// case .finished:
|
||||
// // enter editing mode
|
||||
// self.viewModel.isEditing.toggle()
|
||||
// }
|
||||
// } receiveValue: { [weak self] response in
|
||||
// guard let self = self else { return }
|
||||
// self.viewModel.accountForEdit = response.value
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// }
|
||||
// } else {
|
||||
// guard let relationshipAction = relationshipActionSet.highPriorityAction(except: .editOptions) else { return }
|
||||
// switch relationshipAction {
|
||||
// case .none:
|
||||
// break
|
||||
// case .follow, .request, .pending, .following:
|
||||
// guard let user = viewModel.user else { return }
|
||||
// let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
// Task {
|
||||
// try await DataSourceFacade.responseToUserFollowAction(
|
||||
// dependency: self,
|
||||
// user: record
|
||||
// )
|
||||
// }
|
||||
// case .muting:
|
||||
// guard let user = viewModel.user else { return }
|
||||
// let name = user.displayNameWithFallback
|
||||
//
|
||||
// let alertController = UIAlertController(
|
||||
// title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.title,
|
||||
// message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.message(name),
|
||||
// preferredStyle: .alert
|
||||
// )
|
||||
// let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
// let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unmute, style: .default) { [weak self] _ in
|
||||
// guard let self = self else { return }
|
||||
// Task {
|
||||
// try await DataSourceFacade.responseToUserMuteAction(
|
||||
// dependency: self,
|
||||
// user: record
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// alertController.addAction(unmuteAction)
|
||||
// let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
// alertController.addAction(cancelAction)
|
||||
// present(alertController, animated: true, completion: nil)
|
||||
// case .blocking:
|
||||
// guard let user = viewModel.user else { return }
|
||||
// let name = user.displayNameWithFallback
|
||||
//
|
||||
// let alertController = UIAlertController(
|
||||
// title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.title,
|
||||
// message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.message(name),
|
||||
// preferredStyle: .alert
|
||||
// )
|
||||
// let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
// let unblockAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unblock, style: .default) { [weak self] _ in
|
||||
// guard let self = self else { return }
|
||||
// Task {
|
||||
// try await DataSourceFacade.responseToUserBlockAction(
|
||||
// dependency: self,
|
||||
// user: record
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// alertController.addAction(unblockAction)
|
||||
// let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
// alertController.addAction(cancelAction)
|
||||
// present(alertController, animated: true, completion: nil)
|
||||
// case .blocked, .showReblogs, .isMyself,.followingBy, .blockingBy, .suspended, .edit, .editing, .updating:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
@ -885,23 +829,19 @@ extension ProfileViewController: ProfileAboutViewControllerDelegate {
|
|||
// MARK: - MastodonMenuDelegate
|
||||
extension ProfileViewController: MastodonMenuDelegate {
|
||||
func menuAction(_ action: MastodonMenu.Action) {
|
||||
guard let user = viewModel.user else { return }
|
||||
|
||||
let userRecord: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
||||
|
||||
Task {
|
||||
try await DataSourceFacade.responseToMenuAction(
|
||||
dependency: self,
|
||||
action: action,
|
||||
menuContext: DataSourceFacade.MenuContext(
|
||||
author: userRecord,
|
||||
authorEntity: nil,
|
||||
author: viewModel.account,
|
||||
statusViewModel: nil,
|
||||
button: nil,
|
||||
barButtonItem: self.moreMenuBarButtonItem
|
||||
)
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,11 +34,9 @@ class ProfileViewModel: NSObject {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
|
||||
@available(*, deprecated, message: "Replace with Account")
|
||||
@Published var me: MastodonUser?
|
||||
|
||||
@available(*, deprecated, message: "Replace with Account")
|
||||
@Published var user: MastodonUser?
|
||||
@Published var me: Mastodon.Entity.Account?
|
||||
@Published var account: Mastodon.Entity.Account
|
||||
@Published var relationship: Mastodon.Entity.Relationship?
|
||||
|
||||
let viewDidAppear = PassthroughSubject<Void, Never>()
|
||||
|
||||
|
@ -46,9 +44,6 @@ class ProfileViewModel: NSObject {
|
|||
@Published var isUpdating = false
|
||||
@Published var accountForEdit: Mastodon.Entity.Account?
|
||||
|
||||
// output
|
||||
let relationshipViewModel = RelationshipViewModel()
|
||||
|
||||
@Published var userIdentifier: UserIdentifier? = nil
|
||||
|
||||
@Published var isRelationshipActionButtonHidden: Bool = true
|
||||
|
@ -61,10 +56,10 @@ class ProfileViewModel: NSObject {
|
|||
// let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
@MainActor
|
||||
init(context: AppContext, authContext: AuthContext, optionalMastodonUser mastodonUser: MastodonUser?) {
|
||||
init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account?) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.user = mastodonUser
|
||||
self.account = account!
|
||||
self.postsUserTimelineViewModel = UserTimelineViewModel(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
|
@ -87,21 +82,15 @@ class ProfileViewModel: NSObject {
|
|||
super.init()
|
||||
|
||||
// bind me
|
||||
self.me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
$me
|
||||
.assign(to: \.me, on: relationshipViewModel)
|
||||
.store(in: &disposeBag)
|
||||
self.me = authContext.mastodonAuthenticationBox.authentication.account()
|
||||
|
||||
// bind user
|
||||
$user
|
||||
$account
|
||||
.map { user -> UserIdentifier? in
|
||||
guard let user = user else { return nil }
|
||||
return MastodonUserIdentifier(domain: user.domain, userID: user.id)
|
||||
guard let account, let domain = account.domain else { return nil }
|
||||
return MastodonUserIdentifier(domain: domain, userID: account.id)
|
||||
}
|
||||
.assign(to: &$userIdentifier)
|
||||
$user
|
||||
.assign(to: \.user, on: relationshipViewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind userIdentifier
|
||||
$userIdentifier.assign(to: &postsUserTimelineViewModel.$userIdentifier)
|
||||
|
@ -109,71 +98,70 @@ class ProfileViewModel: NSObject {
|
|||
$userIdentifier.assign(to: &mediaUserTimelineViewModel.$userIdentifier)
|
||||
|
||||
// bind bar button items
|
||||
relationshipViewModel.$optionSet
|
||||
.sink { [weak self] optionSet in
|
||||
guard let self = self else { return }
|
||||
guard let optionSet = optionSet, !optionSet.contains(.none) else {
|
||||
self.isReplyBarButtonItemHidden = true
|
||||
self.isMoreMenuBarButtonItemHidden = true
|
||||
self.isMeBarButtonItemsHidden = true
|
||||
return
|
||||
}
|
||||
|
||||
let isMyself = optionSet.contains(.isMyself)
|
||||
self.isReplyBarButtonItemHidden = isMyself
|
||||
self.isMoreMenuBarButtonItemHidden = isMyself
|
||||
self.isMeBarButtonItemsHidden = !isMyself
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
#warning("TODO: Implement")
|
||||
// relationshipViewModel.$optionSet
|
||||
// .sink { [weak self] optionSet in
|
||||
// guard let self = self else { return }
|
||||
// guard let optionSet = optionSet, !optionSet.contains(.none) else {
|
||||
// self.isReplyBarButtonItemHidden = true
|
||||
// self.isMoreMenuBarButtonItemHidden = true
|
||||
// self.isMeBarButtonItemsHidden = true
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let isMyself = optionSet.contains(.isMyself)
|
||||
// self.isReplyBarButtonItemHidden = isMyself
|
||||
// self.isMoreMenuBarButtonItemHidden = isMyself
|
||||
// self.isMeBarButtonItemsHidden = !isMyself
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
// query relationship
|
||||
let userRecord = $user.map { user -> ManagedObjectRecord<MastodonUser>? in
|
||||
user.flatMap { ManagedObjectRecord<MastodonUser>(objectID: $0.objectID) }
|
||||
}
|
||||
let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
|
||||
#warning("TODO: Implement")
|
||||
// let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
|
||||
|
||||
// observe friendship
|
||||
Publishers.CombineLatest(
|
||||
userRecord,
|
||||
pendingRetryPublisher
|
||||
)
|
||||
.sink { [weak self] userRecord, _ in
|
||||
guard let self = self else { return }
|
||||
guard let userRecord = userRecord else { return }
|
||||
Task {
|
||||
do {
|
||||
let response = try await self.updateRelationship(
|
||||
record: userRecord,
|
||||
authenticationBox: self.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
// there are seconds delay after request follow before requested -> following. Query again when needs
|
||||
guard let relationship = response.value.first else { return }
|
||||
if relationship.requested == true {
|
||||
let delay = pendingRetryPublisher.value
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||
guard let _ = self else { return }
|
||||
pendingRetryPublisher.value = min(2 * delay, 60)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
} // end Task
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
// // observe friendship
|
||||
// Publishers.CombineLatest(
|
||||
// account,
|
||||
// pendingRetryPublisher
|
||||
// )
|
||||
// .sink { [weak self] account, _ in
|
||||
// guard let self, let account else { return }
|
||||
//
|
||||
// Task {
|
||||
// do {
|
||||
// let response = try await self.updateRelationship(
|
||||
// account: account,
|
||||
// authenticationBox: self.authContext.mastodonAuthenticationBox
|
||||
// )
|
||||
// // there are seconds delay after request follow before requested -> following. Query again when needs
|
||||
// guard let relationship = response.value.first else { return }
|
||||
// if relationship.requested == true {
|
||||
// let delay = pendingRetryPublisher.value
|
||||
// DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||
// guard let _ = self else { return }
|
||||
// pendingRetryPublisher.value = min(2 * delay, 60)
|
||||
// }
|
||||
// }
|
||||
// } catch {
|
||||
// }
|
||||
// } // end Task
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
let isBlockingOrBlocked = Publishers.CombineLatest(
|
||||
relationshipViewModel.$isBlocking,
|
||||
relationshipViewModel.$isBlockingBy
|
||||
)
|
||||
.map { $0 || $1 }
|
||||
.share()
|
||||
|
||||
Publishers.CombineLatest(
|
||||
isBlockingOrBlocked,
|
||||
$isEditing
|
||||
)
|
||||
.map { !$0 && !$1 }
|
||||
.assign(to: &$isPagingEnabled)
|
||||
// let isBlockingOrBlocked = Publishers.CombineLatest(
|
||||
// relationshipViewModel.$isBlocking,
|
||||
// relationshipViewModel.$isBlockingBy
|
||||
// )
|
||||
// .map { $0 || $1 }
|
||||
// .share()
|
||||
//
|
||||
// Publishers.CombineLatest(
|
||||
// isBlockingOrBlocked,
|
||||
// $isEditing
|
||||
// )
|
||||
// .map { !$0 && !$1 }
|
||||
// .assign(to: &$isPagingEnabled)
|
||||
}
|
||||
|
||||
|
||||
|
@ -183,21 +171,21 @@ class ProfileViewModel: NSObject {
|
|||
|
||||
// fetch profile info before edit
|
||||
func fetchEditProfileInfo() -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||
guard let me else {
|
||||
guard let me, let domain = me.domain else {
|
||||
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let mastodonAuthentication = authContext.mastodonAuthenticationBox.authentication
|
||||
let authorization = Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.userAccessToken)
|
||||
return context.apiService.accountVerifyCredentials(domain: me.domain, authorization: authorization)
|
||||
return context.apiService.accountVerifyCredentials(domain: domain, authorization: authorization)
|
||||
}
|
||||
|
||||
private func updateRelationship(
|
||||
record: ManagedObjectRecord<MastodonUser>,
|
||||
account: Mastodon.Entity.Account,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Relationship]> {
|
||||
let response = try await context.apiService.relationship(
|
||||
records: [record],
|
||||
forAccounts: [account],
|
||||
authenticationBox: authenticationBox
|
||||
)
|
||||
return response
|
||||
|
|
|
@ -15,7 +15,7 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
|
||||
@MainActor
|
||||
init(context: AppContext, authContext: AuthContext, userID: Mastodon.Entity.Account.ID) {
|
||||
super.init(context: context, authContext: authContext, optionalMastodonUser: nil)
|
||||
super.init(context: context, authContext: authContext, account: nil)
|
||||
|
||||
let domain = authContext.mastodonAuthenticationBox.domain
|
||||
let authorization = authContext.mastodonAuthenticationBox.userAuthorization
|
||||
|
@ -38,62 +38,28 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
break
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.fetchLimit = 1
|
||||
request.predicate = MastodonUser.predicate(domain: domain, id: response.value.id)
|
||||
guard let mastodonUser = managedObjectContext.safeFetch(request).first else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
self.user = mastodonUser
|
||||
self?.account = response.value
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
init(context: AppContext, authContext: AuthContext, notificationID: Mastodon.Entity.Notification.ID) {
|
||||
super.init(context: context, authContext: authContext, optionalMastodonUser: nil)
|
||||
super.init(context: context, authContext: authContext, account: nil)
|
||||
|
||||
Task { @MainActor in
|
||||
let response = try await context.apiService.notification(
|
||||
notificationID: notificationID,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
let userID = response.value.account.id
|
||||
|
||||
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: authContext.mastodonAuthenticationBox.domain, id: userID)
|
||||
request.fetchLimit = 1
|
||||
return context.managedObjectContext.safeFetch(request).first
|
||||
}
|
||||
|
||||
if let user = _user {
|
||||
self.user = user
|
||||
} else {
|
||||
_ = try await context.apiService.accountInfo(
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
userID: userID,
|
||||
authorization: authContext.mastodonAuthenticationBox.userAuthorization
|
||||
)
|
||||
|
||||
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: authContext.mastodonAuthenticationBox.domain, id: userID)
|
||||
request.fetchLimit = 1
|
||||
return context.managedObjectContext.safeFetch(request).first
|
||||
}
|
||||
|
||||
self.user = _user
|
||||
}
|
||||
self.account = response.value.account
|
||||
} // end Task
|
||||
}
|
||||
|
||||
@MainActor
|
||||
init(context: AppContext, authContext: AuthContext, acct: String){
|
||||
super.init(context: context, authContext: authContext, optionalMastodonUser: nil)
|
||||
super.init(context: context, authContext: authContext, account: nil)
|
||||
|
||||
let domain = authContext.mastodonAuthenticationBox.domain
|
||||
let authenticationBox = authContext.mastodonAuthenticationBox
|
||||
|
@ -116,16 +82,9 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
break
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self, let value = response.value else { return }
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.fetchLimit = 1
|
||||
request.predicate = MastodonUser.predicate(domain: domain, id: value.id)
|
||||
guard let mastodonUser = managedObjectContext.safeFetch(request).first else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
self.user = mastodonUser
|
||||
guard let account = response.value else { return }
|
||||
|
||||
self?.account = account
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ extension ReportViewController: ReportReasonViewControllerDelegate {
|
|||
let reportResultViewModel = ReportResultViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
user: viewModel.user,
|
||||
account: viewModel.account,
|
||||
isReported: false
|
||||
)
|
||||
_ = coordinator.present(
|
||||
|
@ -160,7 +160,7 @@ extension ReportViewController: ReportSupplementaryViewControllerDelegate {
|
|||
let reportResultViewModel = ReportResultViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
user: viewModel.user,
|
||||
account: viewModel.account,
|
||||
isReported: true
|
||||
)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class ReportViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let user: ManagedObjectRecord<MastodonUser>
|
||||
let account: Mastodon.Entity.Account
|
||||
let status: MastodonStatus?
|
||||
|
||||
// output
|
||||
|
@ -39,17 +39,17 @@ class ReportViewModel {
|
|||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
account: Mastodon.Entity.Account,
|
||||
status: MastodonStatus?
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.user = user
|
||||
self.account = account
|
||||
self.status = status
|
||||
self.reportReasonViewModel = ReportReasonViewModel(context: context)
|
||||
self.reportServerRulesViewModel = ReportServerRulesViewModel(context: context)
|
||||
self.reportStatusViewModel = ReportStatusViewModel(context: context, authContext: authContext, user: user, status: status)
|
||||
self.reportSupplementaryViewModel = ReportSupplementaryViewModel(context: context, authContext: authContext, user: user)
|
||||
self.reportStatusViewModel = ReportStatusViewModel(context: context, authContext: authContext, account: account, status: status)
|
||||
self.reportSupplementaryViewModel = ReportSupplementaryViewModel(context: context, authContext: authContext, account: account)
|
||||
// end init
|
||||
|
||||
// setup reason viewModel
|
||||
|
@ -57,17 +57,8 @@ class ReportViewModel {
|
|||
reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisPost
|
||||
} else {
|
||||
Task { @MainActor in
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let _username: String? = try? await managedObjectContext.perform {
|
||||
let user = user.object(in: managedObjectContext)
|
||||
return user?.acctWithDomain
|
||||
}
|
||||
if let username = _username {
|
||||
reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisUsername(username)
|
||||
} else {
|
||||
reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisAccount
|
||||
}
|
||||
} // end Task
|
||||
reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisUsername(account.username)
|
||||
}
|
||||
}
|
||||
|
||||
// bind server rules
|
||||
|
@ -96,73 +87,63 @@ extension ReportViewModel {
|
|||
func report() async throws {
|
||||
guard !isReporting else { return }
|
||||
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let _query: Mastodon.API.Reports.FileReportQuery? = try await managedObjectContext.perform {
|
||||
guard let user = self.user.object(in: managedObjectContext) else { return nil }
|
||||
|
||||
// the status picker is essential step in report flow
|
||||
// only check isSkip or not
|
||||
let statusIDs: [MastodonStatus.ID]? = {
|
||||
if self.reportStatusViewModel.isSkip {
|
||||
let _id: MastodonStatus.ID? = self.reportStatusViewModel.status.flatMap { record -> MastodonStatus.ID? in
|
||||
return record.id
|
||||
}
|
||||
return _id.flatMap { [$0] } ?? []
|
||||
} else {
|
||||
return self.reportStatusViewModel.selectStatuses.compactMap { record -> MastodonStatus.ID? in
|
||||
return record.id
|
||||
}
|
||||
let account = self.account
|
||||
// the status picker is essential step in report flow
|
||||
// only check isSkip or not
|
||||
let statusIDs: [MastodonStatus.ID]? = {
|
||||
if self.reportStatusViewModel.isSkip {
|
||||
let _id: MastodonStatus.ID? = self.reportStatusViewModel.status.flatMap { record -> MastodonStatus.ID? in
|
||||
return record.id
|
||||
}
|
||||
}()
|
||||
|
||||
// the user comment is essential step in report flow
|
||||
// only check isSkip or not
|
||||
let comment: String? = {
|
||||
let _comment = self.reportSupplementaryViewModel.isSkip ? nil : self.reportSupplementaryViewModel.commentContext.comment
|
||||
if let comment = _comment, !comment.isEmpty {
|
||||
return comment
|
||||
} else {
|
||||
return nil
|
||||
return _id.flatMap { [$0] } ?? []
|
||||
} else {
|
||||
return self.reportStatusViewModel.selectStatuses.compactMap { record -> MastodonStatus.ID? in
|
||||
return record.id
|
||||
}
|
||||
}()
|
||||
return Mastodon.API.Reports.FileReportQuery(
|
||||
accountID: user.id,
|
||||
statusIDs: statusIDs,
|
||||
comment: comment,
|
||||
forward: true,
|
||||
category: {
|
||||
switch self.reportReasonViewModel.selectReason {
|
||||
}
|
||||
}()
|
||||
|
||||
// the user comment is essential step in report flow
|
||||
// only check isSkip or not
|
||||
let comment: String? = {
|
||||
let _comment = self.reportSupplementaryViewModel.isSkip ? nil : self.reportSupplementaryViewModel.commentContext.comment
|
||||
if let comment = _comment, !comment.isEmpty {
|
||||
return comment
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
let query = Mastodon.API.Reports.FileReportQuery(
|
||||
accountID: account.id,
|
||||
statusIDs: statusIDs,
|
||||
comment: comment,
|
||||
forward: true,
|
||||
category: {
|
||||
switch self.reportReasonViewModel.selectReason {
|
||||
case .dislike: return nil
|
||||
case .spam: return .spam
|
||||
case .violateRule: return .violation
|
||||
case .other: return .other
|
||||
case .none: return nil
|
||||
}
|
||||
}(),
|
||||
ruleIDs: {
|
||||
switch self.reportReasonViewModel.selectReason {
|
||||
}
|
||||
}(),
|
||||
ruleIDs: {
|
||||
switch self.reportReasonViewModel.selectReason {
|
||||
case .violateRule:
|
||||
let ruleIDs = self.reportServerRulesViewModel.selectRules.map { $0.id }.sorted()
|
||||
return ruleIDs
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
)
|
||||
}
|
||||
|
||||
guard let query = _query else { return }
|
||||
}
|
||||
}()
|
||||
)
|
||||
|
||||
do {
|
||||
isReporting = true
|
||||
#if DEBUG
|
||||
try await Task.sleep(nanoseconds: .second * 3)
|
||||
#else
|
||||
let _ = try await context.apiService.report(
|
||||
query: query,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
#endif
|
||||
isReportSuccess = true
|
||||
} catch {
|
||||
isReporting = false
|
||||
|
|
|
@ -90,7 +90,7 @@ extension ReportResultViewController {
|
|||
do {
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: self,
|
||||
user: self.viewModel.user
|
||||
user: self.viewModel.account
|
||||
)
|
||||
} catch {
|
||||
// handle error
|
||||
|
@ -110,7 +110,7 @@ extension ReportResultViewController {
|
|||
do {
|
||||
try await DataSourceFacade.responseToUserMuteAction(
|
||||
dependency: self,
|
||||
user: self.viewModel.user
|
||||
account: self.viewModel.account
|
||||
)
|
||||
} catch {
|
||||
// handle error
|
||||
|
@ -130,7 +130,7 @@ extension ReportResultViewController {
|
|||
do {
|
||||
try await DataSourceFacade.responseToUserBlockAction(
|
||||
dependency: self,
|
||||
user: self.viewModel.user
|
||||
user: self.viewModel.account
|
||||
)
|
||||
} catch {
|
||||
// handle error
|
||||
|
|
|
@ -23,7 +23,7 @@ class ReportResultViewModel: ObservableObject {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let user: ManagedObjectRecord<MastodonUser>
|
||||
let account: Mastodon.Entity.Account
|
||||
let isReported: Bool
|
||||
|
||||
var headline: String {
|
||||
|
@ -48,23 +48,19 @@ class ReportResultViewModel: ObservableObject {
|
|||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
account: Mastodon.Entity.Account,
|
||||
isReported: Bool
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.user = user
|
||||
self.account = account
|
||||
self.isReported = isReported
|
||||
// end init
|
||||
|
||||
Task { @MainActor in
|
||||
guard let user = user.object(in: context.managedObjectContext) else { return }
|
||||
guard let me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else { return }
|
||||
self.relationshipViewModel.user = user
|
||||
self.relationshipViewModel.me = me
|
||||
|
||||
self.avatarURL = user.avatarImageURL()
|
||||
self.username = user.acctWithDomain
|
||||
self.avatarURL = account.avatarImageURL()
|
||||
self.username = account.username
|
||||
|
||||
} // end Task
|
||||
}
|
||||
|
|
|
@ -68,19 +68,9 @@ extension ReportStatusViewModel.State {
|
|||
Task {
|
||||
let maxID = await viewModel.statusFetchedResultsController.records.last?.id
|
||||
|
||||
let managedObjectContext = viewModel.context.managedObjectContext
|
||||
let _userID: MastodonUser.ID? = try await managedObjectContext.perform {
|
||||
guard let user = viewModel.user.object(in: managedObjectContext) else { return nil }
|
||||
return user.id
|
||||
}
|
||||
guard let userID = _userID else {
|
||||
await enter(state: Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let response = try await viewModel.context.apiService.userTimeline(
|
||||
accountID: userID,
|
||||
accountID: viewModel.account.id,
|
||||
maxID: maxID,
|
||||
sinceID: nil,
|
||||
excludeReplies: true,
|
||||
|
|
|
@ -24,7 +24,7 @@ class ReportStatusViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let user: ManagedObjectRecord<MastodonUser>
|
||||
let account: Mastodon.Entity.Account
|
||||
let status: MastodonStatus?
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
@ -52,12 +52,12 @@ class ReportStatusViewModel {
|
|||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
account: Mastodon.Entity.Account,
|
||||
status: MastodonStatus?
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.user = user
|
||||
self.account = account
|
||||
self.status = status
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController()
|
||||
// end init
|
||||
|
|
|
@ -18,7 +18,7 @@ class ReportSupplementaryViewModel {
|
|||
// Input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let user: ManagedObjectRecord<MastodonUser>
|
||||
let account: Mastodon.Entity.Account
|
||||
let commentContext = ReportItem.CommentContext()
|
||||
|
||||
@Published var isSkip = false
|
||||
|
@ -31,11 +31,11 @@ class ReportSupplementaryViewModel {
|
|||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
account: Mastodon.Entity.Account
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.user = user
|
||||
self.account = account
|
||||
// end init
|
||||
|
||||
Publishers.CombineLatest(
|
||||
|
|
|
@ -70,10 +70,7 @@ extension SearchResultViewController {
|
|||
status: status
|
||||
)
|
||||
case .user(let user):
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: user
|
||||
)
|
||||
assertionFailure()
|
||||
case .hashtag(let tag):
|
||||
await DataSourceFacade.coordinateToHashtagScene(
|
||||
provider: self,
|
||||
|
|
|
@ -217,15 +217,8 @@ extension SettingsCoordinator: ServerDetailsViewControllerDelegate {
|
|||
|
||||
extension SettingsCoordinator: AboutInstanceViewControllerDelegate {
|
||||
@MainActor func showAdminAccount(_ viewController: AboutInstanceViewController, account: Mastodon.Entity.Account) {
|
||||
Task {
|
||||
let user = try await appContext.apiService.fetchUser(username: account.username, domain: authContext.mastodonAuthenticationBox.domain, authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
|
||||
let profileViewModel = ProfileViewModel(context: appContext, authContext: authContext, optionalMastodonUser: user)
|
||||
|
||||
_ = await MainActor.run {
|
||||
sceneCoordinator.present(scene: .profile(viewModel: profileViewModel), transition: .show)
|
||||
}
|
||||
}
|
||||
let profileViewModel = ProfileViewModel(context: appContext, authContext: authContext, account: account)
|
||||
sceneCoordinator.present(scene: .profile(viewModel: profileViewModel), transition: .show)
|
||||
}
|
||||
|
||||
func sendEmailToAdmin(_ viewController: AboutInstanceViewController, emailAddress: String) {
|
||||
|
|
|
@ -99,6 +99,12 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||
return MastodonUser.findOrFetch(in: context, matching: userPredicate)
|
||||
}
|
||||
|
||||
public func account() -> Mastodon.Entity.Account? {
|
||||
// store accounts
|
||||
#warning("TODO: Implement")
|
||||
return nil
|
||||
}
|
||||
|
||||
func updating(instance: Instance) -> Self {
|
||||
copy(instanceObjectIdURI: instance.objectID.uriRepresentation())
|
||||
}
|
||||
|
|
|
@ -34,19 +34,6 @@ extension APIService {
|
|||
authorization: authorization
|
||||
).singleOutput()
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
_ = Persistence.MastodonUser.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.MastodonUser.PersistContext(
|
||||
domain: domain,
|
||||
entity: response.value,
|
||||
cache: nil,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
@ -171,7 +158,7 @@ extension APIService {
|
|||
|
||||
extension APIService {
|
||||
public func fetchUser(username: String, domain: String, authenticationBox: MastodonAuthenticationBox)
|
||||
async throws -> MastodonUser? {
|
||||
async throws -> Mastodon.Entity.Account? {
|
||||
let query = Mastodon.API.Account.AccountLookupQuery(acct: "\(username)@\(domain)")
|
||||
let authorization = authenticationBox.userAuthorization
|
||||
|
||||
|
@ -182,21 +169,6 @@ extension APIService {
|
|||
authorization: authorization
|
||||
).singleOutput()
|
||||
|
||||
// user
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
var result: MastodonUser?
|
||||
try await managedObjectContext.performChanges {
|
||||
result = Persistence.MastodonUser.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.MastodonUser.PersistContext(
|
||||
domain: domain,
|
||||
entity: response.value,
|
||||
cache: nil,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
).user
|
||||
}
|
||||
|
||||
return result
|
||||
return response.value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,64 +145,10 @@ extension APIService {
|
|||
return response
|
||||
}
|
||||
|
||||
public func toggleShowReblogs(
|
||||
for user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
guard let user = user.object(in: managedObjectContext),
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
else { throw APIError.implicit(.badRequest) }
|
||||
|
||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
|
||||
let oldShowReblogs = me.showingReblogsBy.contains(user)
|
||||
let newShowReblogs = (oldShowReblogs == false)
|
||||
|
||||
do {
|
||||
let response = try await Mastodon.API.Account.follow(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
accountID: user.id,
|
||||
followQueryType: .follow(query: .init(reblogs: newShowReblogs)),
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
result = .success(response)
|
||||
} catch {
|
||||
result = .failure(error)
|
||||
}
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
Persistence.MastodonUser.update(
|
||||
mastodonUser: user,
|
||||
context: Persistence.MastodonUser.RelationshipContext(
|
||||
entity: response.value,
|
||||
me: me,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
case .failure:
|
||||
// rollback
|
||||
user.update(isShowingReblogs: oldShowReblogs, by: me)
|
||||
}
|
||||
}
|
||||
|
||||
return try result.get()
|
||||
}
|
||||
|
||||
public func toggleShowReblogs(
|
||||
for user: Mastodon.Entity.Account,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||
|
||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
|
||||
let relationship = try await Mastodon.API.Account.relationships(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
|
@ -213,20 +159,14 @@ extension APIService {
|
|||
let oldShowReblogs = relationship?.showingReblogs == true
|
||||
let newShowReblogs = (oldShowReblogs == false)
|
||||
|
||||
do {
|
||||
let response = try await Mastodon.API.Account.follow(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
accountID: user.id,
|
||||
followQueryType: .follow(query: .init(reblogs: newShowReblogs)),
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
let response = try await Mastodon.API.Account.follow(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
accountID: user.id,
|
||||
followQueryType: .follow(query: .init(reblogs: newShowReblogs)),
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
result = .success(response)
|
||||
} catch {
|
||||
result = .failure(error)
|
||||
}
|
||||
|
||||
return try result.get()
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import MastodonSDK
|
|||
extension APIService {
|
||||
|
||||
private struct MastodonMuteContext {
|
||||
let sourceUserID: MastodonUser.ID
|
||||
let targetUserID: MastodonUser.ID
|
||||
let targetUsername: String
|
||||
let isMuting: Bool
|
||||
|
@ -60,32 +59,22 @@ extension APIService {
|
|||
}
|
||||
|
||||
public func toggleMute(
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
authenticationBox: MastodonAuthenticationBox,
|
||||
account: Mastodon.Entity.Account
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
let muteContext: MastodonMuteContext = try await managedObjectContext.performChanges {
|
||||
let authentication = authenticationBox.authentication
|
||||
guard let relationship = try await Mastodon.API.Account.relationships(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
query: .init(ids: [account.id]),
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput().value.first else { throw APIError.implicit(.badRequest) }
|
||||
|
||||
guard
|
||||
let user = user.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
let isMuting = user.mutingBy.contains(me)
|
||||
|
||||
// toggle mute state
|
||||
user.update(isMuting: !isMuting, by: me)
|
||||
return MastodonMuteContext(
|
||||
sourceUserID: me.id,
|
||||
targetUserID: user.id,
|
||||
targetUsername: user.username,
|
||||
isMuting: isMuting
|
||||
)
|
||||
}
|
||||
let muteContext = MastodonMuteContext(
|
||||
targetUserID: account.id,
|
||||
targetUsername: account.username,
|
||||
isMuting: relationship.muting ?? false
|
||||
)
|
||||
|
||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
do {
|
||||
|
@ -96,7 +85,7 @@ extension APIService {
|
|||
accountID: muteContext.targetUserID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
try await getMutes(authenticationBox: authenticationBox)
|
||||
|
||||
result = .success(response)
|
||||
} else {
|
||||
let response = try await Mastodon.API.Account.mute(
|
||||
|
@ -105,35 +94,13 @@ extension APIService {
|
|||
accountID: muteContext.targetUserID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
try await getMutes(authenticationBox: authenticationBox)
|
||||
|
||||
result = .success(response)
|
||||
}
|
||||
} catch {
|
||||
result = .failure(error)
|
||||
}
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let user = user.object(in: managedObjectContext),
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let relationship = response.value
|
||||
Persistence.MastodonUser.update(
|
||||
mastodonUser: user,
|
||||
context: Persistence.MastodonUser.RelationshipContext(
|
||||
entity: relationship,
|
||||
me: me,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
case .failure:
|
||||
// rollback
|
||||
user.update(isMuting: muteContext.isMuting, by: me)
|
||||
}
|
||||
}
|
||||
|
||||
let response = try result.get()
|
||||
return response
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue