Continue refactoring of MastodonUser and Status (IOS-176, IOS-189)
This commit is contained in:
parent
36091e9628
commit
b00877d5da
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
|
@ -48,12 +46,11 @@ extension ReportSection {
|
|||
case .status(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ReportStatusTableViewCell.self), for: indexPath) as! ReportStatusTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let status = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: .init(value: status),
|
||||
viewModel: .init(value: record),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
|
@ -78,8 +75,7 @@ extension ReportSection {
|
|||
case .result(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ReportResultActionTableViewCell.self), for: indexPath) as! ReportResultActionTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
cell.avatarImageView.configure(configuration: .init(url: user.avatarImageURL()))
|
||||
cell.avatarImageView.configure(configuration: .init(url: record.avatarImageURL()))
|
||||
}
|
||||
return cell
|
||||
case .bottomLoader:
|
||||
|
|
|
@ -6,32 +6,10 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserBlockAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
let apiService = dependency.context.apiService
|
||||
let authBox = dependency.authContext.mastodonAuthenticationBox
|
||||
|
||||
_ = try await apiService.toggleBlock(
|
||||
user: user,
|
||||
authenticationBox: authBox
|
||||
)
|
||||
|
||||
try await dependency.context.apiService.getBlocked(
|
||||
authenticationBox: authBox
|
||||
)
|
||||
dependency.context.authenticationService.fetchFollowingAndBlockedAsync()
|
||||
}
|
||||
|
||||
static func responseToUserBlockAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: Mastodon.Entity.Account
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
import MastodonSDK
|
||||
|
@ -66,10 +65,8 @@ extension DataSourceFacade {
|
|||
previewContext: AttachmentPreviewContext
|
||||
) async throws {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let attachments: [MastodonAttachment] = try await managedObjectContext.perform {
|
||||
let status = status.reblog ?? status
|
||||
return status.mastodonAttachments
|
||||
}
|
||||
let status = status.reblog ?? status
|
||||
let attachments = status.mastodonAttachments
|
||||
|
||||
let thumbnails = await previewContext.thumbnails()
|
||||
|
||||
|
@ -150,20 +147,14 @@ extension DataSourceFacade {
|
|||
@MainActor
|
||||
static func coordinateToMediaPreviewScene(
|
||||
dependency: NeedsDependency & MediaPreviewableViewController,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
user: Mastodon.Entity.Account,
|
||||
previewContext: ImagePreviewContext
|
||||
) async throws {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
|
||||
var _avatarAssetURL: String?
|
||||
var _headerAssetURL: String?
|
||||
|
||||
try await managedObjectContext.perform {
|
||||
guard let user = user.object(in: managedObjectContext) else { return }
|
||||
_avatarAssetURL = user.avatar
|
||||
_headerAssetURL = user.header
|
||||
}
|
||||
|
||||
var _avatarAssetURL: String? = user.avatar
|
||||
var _headerAssetURL: String? = user.header
|
||||
|
||||
let thumbnail = await previewContext.thumbnail()
|
||||
|
||||
let source: MediaPreviewTransitionItem.Source = {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import UIKit
|
||||
|
||||
|
@ -86,21 +85,10 @@ extension DataSourceFacade {
|
|||
provider: DataSourceProvider & AuthContextProvider
|
||||
) async throws {
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let _ = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
let request = SearchHistory.sortedFetchRequest
|
||||
request.predicate = SearchHistory.predicate(
|
||||
domain: authenticationBox.domain,
|
||||
userID: authenticationBox.userID
|
||||
)
|
||||
let searchHistories = managedObjectContext.safeFetch(request)
|
||||
|
||||
for searchHistory in searchHistories {
|
||||
managedObjectContext.delete(searchHistory)
|
||||
}
|
||||
} // end try await managedObjectContext.performChanges { … }
|
||||
guard let _ = try? await authenticationBox.authentication.me() else { return }
|
||||
|
||||
#warning("re-implement search history")
|
||||
} // end func
|
||||
|
||||
}
|
||||
|
|
|
@ -78,12 +78,13 @@ extension AccountListViewModel {
|
|||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell
|
||||
if let activeAuthentication = AuthenticationServiceProvider.shared.authenticationSortedByActivation().first
|
||||
{
|
||||
AccountListViewModel.configure(
|
||||
in: managedObjectContext,
|
||||
cell: cell,
|
||||
authentication: record,
|
||||
activeAuthentication: activeAuthentication
|
||||
)
|
||||
Task { @MainActor in
|
||||
await AccountListViewModel.configure(
|
||||
in: managedObjectContext,
|
||||
cell: cell,
|
||||
authentication: record,
|
||||
activeAuthentication: activeAuthentication
|
||||
)}
|
||||
}
|
||||
return cell
|
||||
case .addAccount:
|
||||
|
@ -97,13 +98,14 @@ extension AccountListViewModel {
|
|||
diffableDataSource?.apply(snapshot)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func configure(
|
||||
in context: NSManagedObjectContext,
|
||||
cell: AccountListTableViewCell,
|
||||
authentication: MastodonAuthentication,
|
||||
activeAuthentication: MastodonAuthentication
|
||||
) {
|
||||
guard let user = authentication.user(in: context) else { return }
|
||||
) async {
|
||||
guard let user = try? await authentication.me() else { return }
|
||||
|
||||
// avatar
|
||||
cell.avatarButton.avatarImageView.configure(
|
||||
|
@ -112,7 +114,7 @@ extension AccountListViewModel {
|
|||
|
||||
// name
|
||||
do {
|
||||
let content = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojis.asDictionary)
|
||||
let content = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojis?.asDictionary ?? [:])
|
||||
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||
cell.nameLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
import UIKit
|
||||
import AVKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
import AlamofireImage
|
||||
|
@ -206,24 +204,25 @@ extension HomeTimelineViewController {
|
|||
viewModel.timelineIsEmpty
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] isEmpty in
|
||||
if isEmpty {
|
||||
self?.showEmptyView()
|
||||
|
||||
let userDoesntFollowPeople: Bool
|
||||
if let managedObjectContext = self?.context.managedObjectContext,
|
||||
let authContext = self?.authContext,
|
||||
let me = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext){
|
||||
userDoesntFollowPeople = me.followersCount == 0
|
||||
Task { @MainActor in
|
||||
if isEmpty {
|
||||
self?.showEmptyView()
|
||||
|
||||
let userDoesntFollowPeople: Bool
|
||||
if let authContext = self?.authContext,
|
||||
let me = try? await authContext.mastodonAuthenticationBox.authentication.me() {
|
||||
userDoesntFollowPeople = me.followersCount == 0
|
||||
} else {
|
||||
userDoesntFollowPeople = true
|
||||
}
|
||||
|
||||
if (self?.viewModel.presentedSuggestions == false) && userDoesntFollowPeople {
|
||||
self?.findPeopleButtonPressed(self)
|
||||
self?.viewModel.presentedSuggestions = true
|
||||
}
|
||||
} else {
|
||||
userDoesntFollowPeople = true
|
||||
self?.emptyView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if (self?.viewModel.presentedSuggestions == false) && userDoesntFollowPeople {
|
||||
self?.findPeopleButtonPressed(self)
|
||||
self?.viewModel.presentedSuggestions = true
|
||||
}
|
||||
} else {
|
||||
self?.emptyView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
|
@ -188,31 +186,36 @@ extension AuthenticationViewModel {
|
|||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||
let authorization = Mastodon.API.OAuth.Authorization(accessToken: userToken.accessToken)
|
||||
let managedObjectContext = context.backgroundManagedObjectContext
|
||||
|
||||
#warning("what happens if instancev2 is not reachable (errors out)??")
|
||||
return context.apiService.accountVerifyCredentials(
|
||||
domain: info.domain,
|
||||
authorization: authorization
|
||||
)
|
||||
.tryMap { response -> Mastodon.Response.Content<Mastodon.Entity.Account> in
|
||||
let account = response.value
|
||||
let mastodonUserRequest = MastodonUser.sortedFetchRequest
|
||||
mastodonUserRequest.predicate = MastodonUser.predicate(domain: info.domain, id: account.id)
|
||||
mastodonUserRequest.fetchLimit = 1
|
||||
guard let mastodonUser = try? managedObjectContext.fetch(mastodonUserRequest).first else {
|
||||
throw AuthenticationError.badCredentials
|
||||
}
|
||||
|
||||
.flatMap { response in
|
||||
Publishers.CombineLatest3(
|
||||
Just(response).setFailureType(to: Error.self).eraseToAnyPublisher(),
|
||||
Mastodon.API.Instance.instance(session: .shared, domain: info.domain).eraseToAnyPublisher(),
|
||||
Mastodon.API.V2.Instance.instance(session: .shared, domain: info.domain).eraseToAnyPublisher()
|
||||
).eraseToAnyPublisher()
|
||||
}
|
||||
.tryMap { (accountResponse, instanceV1Response, instanceV2Response) -> Mastodon.Response.Content<Mastodon.Entity.Account> in
|
||||
let account = accountResponse.value
|
||||
let instanceV1 = instanceV1Response.value
|
||||
let instanceV2 = instanceV2Response.value
|
||||
|
||||
AuthenticationServiceProvider.shared
|
||||
.authentications
|
||||
.insert(MastodonAuthentication.createFrom(domain: info.domain,
|
||||
userID: mastodonUser.id,
|
||||
username: mastodonUser.username,
|
||||
userID: account.id,
|
||||
username: account.username,
|
||||
appAccessToken: userToken.accessToken, // TODO: swap app token
|
||||
userAccessToken: userToken.accessToken,
|
||||
clientID: info.clientID,
|
||||
clientSecret: info.clientSecret), at: 0)
|
||||
clientSecret: info.clientSecret,
|
||||
instance: instanceV1,
|
||||
instanceV2: instanceV2), at: 0)
|
||||
|
||||
return response
|
||||
return accountResponse
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
import MastodonCore
|
||||
|
@ -19,7 +18,7 @@ final class ProfileAboutViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
@Published var user: MastodonUser?
|
||||
@Published var user: Mastodon.Entity.Account?
|
||||
@Published var isEditing = false
|
||||
@Published var accountForEdit: Mastodon.Entity.Account?
|
||||
|
||||
|
@ -28,7 +27,7 @@ final class ProfileAboutViewModel {
|
|||
let profileInfo = ProfileInfo()
|
||||
let profileInfoEditing = ProfileInfo()
|
||||
|
||||
@Published var fields: [MastodonField] = []
|
||||
@Published var fields: [Mastodon.Entity.Field] = []
|
||||
@Published var emojiMeta: MastodonContent.Emojis = [:]
|
||||
@Published var createdAt: Date = Date()
|
||||
|
||||
|
@ -38,18 +37,18 @@ final class ProfileAboutViewModel {
|
|||
|
||||
$user
|
||||
.compactMap { $0 }
|
||||
.flatMap { $0.publisher(for: \.emojis) }
|
||||
.compactMap { $0.emojis }
|
||||
.map { $0.asDictionary }
|
||||
.assign(to: &$emojiMeta)
|
||||
|
||||
$user
|
||||
.compactMap { $0 }
|
||||
.flatMap { $0.publisher(for: \.fields) }
|
||||
.compactMap { $0.fields }
|
||||
.assign(to: &$fields)
|
||||
|
||||
$user
|
||||
.compactMap { $0 }
|
||||
.flatMap { $0.publisher(for: \.createdAt) }
|
||||
.compactMap { $0.createdAt }
|
||||
.assign(to: &$createdAt)
|
||||
|
||||
Publishers.CombineLatest(
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
final class FollowedTagsTableViewCell: UITableViewCell {
|
||||
private var hashtagView: HashtagTimelineHeaderView!
|
||||
private let separatorLine = UIView.separatorLine
|
||||
private weak var viewModel: FollowedTagsViewModel?
|
||||
private weak var hashtag: Tag?
|
||||
private var hashtag: Mastodon.Entity.Tag?
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
@ -67,7 +67,7 @@ private extension FollowedTagsTableViewCell {
|
|||
}
|
||||
|
||||
extension FollowedTagsTableViewCell {
|
||||
func populate(with tag: Tag) {
|
||||
func populate(with tag: Mastodon.Entity.Tag) {
|
||||
self.hashtag = tag
|
||||
hashtagView.update(HashtagTimelineHeaderView.Data.from(tag))
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ extension FollowedTagsViewModel {
|
|||
}
|
||||
|
||||
enum Item: Hashable {
|
||||
case hashtag(Tag)
|
||||
case hashtag(Mastodon.Entity.Tag)
|
||||
}
|
||||
|
||||
func tableViewDiffableDataSource(
|
||||
|
|
|
@ -8,14 +8,11 @@
|
|||
import os
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
|
||||
final class FollowedTagsViewModel: NSObject {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
let fetchedResultsController: FollowedTagsFetchedResultController
|
||||
|
||||
private weak var tableView: UITableView?
|
||||
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>?
|
||||
|
@ -24,22 +21,18 @@ final class FollowedTagsViewModel: NSObject {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
|
||||
@Published var records = [Mastodon.Entity.Tag]()
|
||||
|
||||
// output
|
||||
let presentHashtagTimeline = PassthroughSubject<HashtagTimelineViewModel, Never>()
|
||||
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.fetchedResultsController = FollowedTagsFetchedResultController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
user: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)! // fixme:
|
||||
)
|
||||
|
||||
super.init()
|
||||
|
||||
self.fetchedResultsController
|
||||
.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
@ -71,15 +64,17 @@ extension FollowedTagsViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
func followOrUnfollow(_ tag: Tag) {
|
||||
func followOrUnfollow(_ tag: Mastodon.Entity.Tag) {
|
||||
Task { @MainActor in
|
||||
switch tag.following {
|
||||
case true:
|
||||
case .none:
|
||||
break
|
||||
case .some(true):
|
||||
_ = try? await context.apiService.unfollowTag(
|
||||
for: tag.name,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
case false:
|
||||
case .some(false):
|
||||
_ = try? await context.apiService.followTag(
|
||||
for: tag.name,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
|
@ -94,7 +89,7 @@ extension FollowedTagsViewModel: UITableViewDelegate {
|
|||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let object = fetchedResultsController.records[indexPath.row]
|
||||
let object = records[indexPath.row]
|
||||
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
||||
context: self.context,
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import PhotosUI
|
||||
import AlamofireImage
|
||||
import CropViewController
|
||||
|
@ -270,12 +269,11 @@ extension ProfileHeaderViewController {
|
|||
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,
|
||||
user: user,
|
||||
previewContext: DataSourceFacade.ImagePreviewContext(
|
||||
imageView: button.avatarImageView,
|
||||
containerView: .profileAvatar(profileHeaderView)
|
||||
|
@ -286,12 +284,11 @@ extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
|
|||
|
||||
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,
|
||||
user: user,
|
||||
previewContext: DataSourceFacade.ImagePreviewContext(
|
||||
imageView: imageView,
|
||||
containerView: .profileBanner(profileHeaderView)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import Kanna
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
|
@ -26,7 +25,7 @@ final class ProfileHeaderViewModel {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
|
||||
@Published var user: MastodonUser?
|
||||
@Published var user: Mastodon.Entity.Account?
|
||||
@Published var relationshipActionOptionSet: RelationshipActionOptionSet = .none
|
||||
|
||||
@Published var isMyself = false
|
||||
|
|
|
@ -7,49 +7,33 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension ProfileHeaderView {
|
||||
func configuration(user: MastodonUser) {
|
||||
func configuration(user: Mastodon.Entity.Account) {
|
||||
// header
|
||||
user.publisher(for: \.header)
|
||||
.map { _ in user.headerImageURL() }
|
||||
.assign(to: \.headerImageURL, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.headerImageURL = URL(string: user.header)
|
||||
|
||||
// avatar
|
||||
user.publisher(for: \.avatar)
|
||||
.map { _ in user.avatarImageURL() }
|
||||
.assign(to: \.avatarImageURL, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.avatarImageURL = user.avatarImageURL()
|
||||
|
||||
// emojiMeta
|
||||
user.publisher(for: \.emojis)
|
||||
.map { $0.asDictionary }
|
||||
.assign(to: \.emojiMeta, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.emojiMeta = user.emojiMeta
|
||||
|
||||
// name
|
||||
user.publisher(for: \.displayName)
|
||||
.map { _ in user.displayNameWithFallback }
|
||||
.assign(to: \.name, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.name = user.displayNameWithFallback
|
||||
|
||||
// username
|
||||
viewModel.acct = user.acctWithDomain
|
||||
// bio
|
||||
user.publisher(for: \.note)
|
||||
.assign(to: \.note, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.note = user.note
|
||||
|
||||
// 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)
|
||||
viewModel.statusesCount = user.statusesCount
|
||||
|
||||
viewModel.followingCount = user.followingCount
|
||||
|
||||
viewModel.followersCount = user.followersCount
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@ import MastodonAsset
|
|||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
import CoreDataStack
|
||||
import TabBarPager
|
||||
import XLPagerTabStrip
|
||||
import MastodonSDK
|
||||
|
||||
protocol ProfileViewModelEditable {
|
||||
var isEdited: Bool { get }
|
||||
|
@ -237,7 +237,7 @@ extension ProfileViewController {
|
|||
items.append(self.favoriteBarButtonItem)
|
||||
items.append(self.bookmarkBarButtonItem)
|
||||
|
||||
if self.currentInstance?.canFollowTags == true {
|
||||
if self.currentInstance?.version?.majorServerVersion(greaterThanOrEquals: 4) ?? false == true {
|
||||
items.append(self.followedTagsBarButtonItem)
|
||||
}
|
||||
|
||||
|
@ -400,7 +400,6 @@ extension ProfileViewController {
|
|||
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)),
|
||||
|
@ -408,9 +407,14 @@ extension ProfileViewController {
|
|||
.reportUser(.init(name: name)),
|
||||
.shareUser(.init(name: name)),
|
||||
]
|
||||
|
||||
let relationship = try await context.apiService.relationship(
|
||||
forAccounts: [user],
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
).value.first
|
||||
|
||||
if let me = self.viewModel?.me, me.following.contains(user) {
|
||||
let showReblogs = me.showingReblogsBy.contains(user)
|
||||
if let relationship, relationship.following {
|
||||
let showReblogs = relationship.showingReblogs == true
|
||||
let context = MastodonMenu.HideReblogsActionContext(showReblogs: showReblogs)
|
||||
menuActions.insert(.hideReblogs(context), at: 1)
|
||||
}
|
||||
|
@ -525,11 +529,10 @@ 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
|
||||
user: user
|
||||
)
|
||||
guard let activityViewController = _activityViewController else { return }
|
||||
_ = self.coordinator.present(
|
||||
|
@ -799,11 +802,10 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
|||
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
|
||||
user: user
|
||||
)
|
||||
}
|
||||
case .muting:
|
||||
|
@ -815,13 +817,12 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
|||
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
|
||||
user: user
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -838,13 +839,12 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
|||
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
|
||||
user: user
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -886,14 +886,12 @@ 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,
|
||||
author: user,
|
||||
statusViewModel: nil,
|
||||
button: nil,
|
||||
barButtonItem: self.moreMenuBarButtonItem
|
||||
|
@ -936,7 +934,7 @@ extension ProfileViewController: PagerTabStripNavigateable {
|
|||
}
|
||||
|
||||
private extension ProfileViewController {
|
||||
var currentInstance: Instance? {
|
||||
authContext.mastodonAuthenticationBox.authentication.instance(in: context.managedObjectContext)
|
||||
var currentInstance: Mastodon.Entity.V2.Instance? {
|
||||
authContext.mastodonAuthenticationBox.authentication.instanceV2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import SafariServices
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
class MainTabBarController: UITabBarController {
|
||||
|
||||
|
@ -131,7 +131,6 @@ class MainTabBarController: UITabBarController {
|
|||
private(set) var isReadyForWizardAvatarButton = false
|
||||
|
||||
// output
|
||||
var avatarURLObserver: AnyCancellable?
|
||||
@Published var avatarURL: URL?
|
||||
|
||||
// haptic feedback
|
||||
|
@ -262,14 +261,8 @@ extension MainTabBarController {
|
|||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
if let user = self.authContext?.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) {
|
||||
self.avatarURLObserver = user.publisher(for: \.avatar)
|
||||
.sink { [weak self, weak user] _ in
|
||||
guard let self = self else { return }
|
||||
guard let user = user else { return }
|
||||
guard user.managedObjectContext != nil else { return }
|
||||
self.avatarURL = user.avatarImageURL()
|
||||
}
|
||||
if let user = self.authContext?.mastodonAuthenticationBox.inMemoryCache.meAccount {
|
||||
self.avatarURL = user.avatarImageURL()
|
||||
|
||||
// a11y
|
||||
let _profileTabItem = self.tabBar.items?.first { item in item.tag == Tab.me.tag }
|
||||
|
@ -281,8 +274,6 @@ extension MainTabBarController {
|
|||
self?.updateUserAccount()
|
||||
}
|
||||
.store(in: &self.disposeBag)
|
||||
} else {
|
||||
self.avatarURLObserver = nil
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
@ -457,16 +448,7 @@ extension MainTabBarController {
|
|||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
if let user = authContext.mastodonAuthenticationBox.authentication.user(
|
||||
in: context.managedObjectContext
|
||||
) {
|
||||
user.update(
|
||||
property: .init(
|
||||
entity: profileResponse.value,
|
||||
domain: authContext.mastodonAuthenticationBox.domain
|
||||
)
|
||||
)
|
||||
}
|
||||
authContext.mastodonAuthenticationBox.inMemoryCache.meAccount = profileResponse.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ extension SidebarViewModel {
|
|||
let imageURL: URL? = {
|
||||
switch item {
|
||||
case .me:
|
||||
let user = self.authContext?.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext)
|
||||
let user = self.authContext?.mastodonAuthenticationBox.inMemoryCache.meAccount
|
||||
return user?.avatarImageURL()
|
||||
default:
|
||||
return nil
|
||||
|
@ -132,7 +132,7 @@ extension SidebarViewModel {
|
|||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
case .me:
|
||||
guard let user = self.authContext?.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else { return }
|
||||
guard let user = self.authContext?.mastodonAuthenticationBox.inMemoryCache.meAccount else { return }
|
||||
let currentUserDisplayName = user.displayNameWithFallback
|
||||
cell.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName)
|
||||
default:
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonAsset
|
||||
import MastodonSDK
|
||||
|
||||
enum SearchHistorySection: Hashable {
|
||||
case main
|
||||
|
@ -28,20 +28,18 @@ extension SearchHistorySection {
|
|||
configuration: Configuration
|
||||
) -> UICollectionViewDiffableDataSource<SearchHistorySection, SearchHistoryItem> {
|
||||
|
||||
let userCellRegister = UICollectionView.CellRegistration<SearchHistoryUserCollectionViewCell, ManagedObjectRecord<MastodonUser>> { cell, indexPath, item in
|
||||
let userCellRegister = UICollectionView.CellRegistration<SearchHistoryUserCollectionViewCell, Mastodon.Entity.Account> { cell, indexPath, item in
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = item.object(in: context.managedObjectContext) else { return }
|
||||
cell.condensedUserView.configure(with: user)
|
||||
cell.condensedUserView.configure(with: item)
|
||||
}
|
||||
}
|
||||
|
||||
let hashtagCellRegister = UICollectionView.CellRegistration<UICollectionViewListCell, ManagedObjectRecord<Tag>> { cell, indexPath, item in
|
||||
let hashtagCellRegister = UICollectionView.CellRegistration<UICollectionViewListCell, Mastodon.Entity.Tag> { cell, indexPath, item in
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let hashtag = item.object(in: context.managedObjectContext) else { return }
|
||||
var contentConfiguration = cell.defaultContentConfiguration()
|
||||
contentConfiguration.image = UIImage(systemName: "magnifyingglass")
|
||||
contentConfiguration.imageProperties.tintColor = Asset.Colors.Brand.blurple.color
|
||||
contentConfiguration.text = "#" + hashtag.name
|
||||
contentConfiguration.text = "#" + item.name
|
||||
cell.contentConfiguration = contentConfiguration
|
||||
}
|
||||
|
||||
|
|
|
@ -44,13 +44,12 @@ extension SearchResultSection {
|
|||
case .user(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as! UserTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: UserTableViewCell.ViewModel(user: user,
|
||||
viewModel: UserTableViewCell.ViewModel(user: record,
|
||||
followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(),
|
||||
blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(),
|
||||
followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher()),
|
||||
|
@ -61,12 +60,11 @@ extension SearchResultSection {
|
|||
case .status(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let status = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .status(status)),
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .status(record)),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
|
@ -126,7 +124,7 @@ extension SearchResultSection {
|
|||
configuration: Configuration
|
||||
) {
|
||||
cell.configure(
|
||||
me: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext),
|
||||
me: authContext.mastodonAuthenticationBox.inMemoryCache.meAccount,
|
||||
tableView: tableView,
|
||||
viewModel: viewModel,
|
||||
delegate: configuration.userTableViewCellDelegate
|
||||
|
|
|
@ -136,13 +136,13 @@ extension SearchResultViewModel.State {
|
|||
|
||||
// reset data source when the search is refresh
|
||||
if offset == nil {
|
||||
viewModel.userFetchedResultsController.userIDs = []
|
||||
viewModel.statusFetchedResultsController.statusIDs = []
|
||||
viewModel.statusRecords = []
|
||||
viewModel.userRecords = []
|
||||
viewModel.hashtags = []
|
||||
}
|
||||
|
||||
viewModel.userFetchedResultsController.append(userIDs: userIDs)
|
||||
viewModel.statusFetchedResultsController.append(statusIDs: statusIDs)
|
||||
viewModel.userRecords.append(contentsOf: response.value.accounts)
|
||||
viewModel.statusRecords.append(contentsOf: response.value.statuses)
|
||||
|
||||
var hashtags = viewModel.hashtags
|
||||
for hashtag in response.value.hashtags where !hashtags.contains(hashtag) {
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
import MastodonMeta
|
||||
|
@ -20,7 +18,7 @@ final class MastodonStatusThreadViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
@Published private(set) var deletedObjectIDs: Set<NSManagedObjectID> = Set()
|
||||
@Published private(set) var deletedObjectIDs: Set<Mastodon.Entity.Status.ID> = Set()
|
||||
|
||||
// output
|
||||
@Published var __ancestors: [StatusItem] = []
|
||||
|
@ -41,7 +39,7 @@ final class MastodonStatusThreadViewModel {
|
|||
let newItems = items.filter { item in
|
||||
switch item {
|
||||
case .thread(let thread):
|
||||
return !deletedObjectIDs.contains(thread.record.objectID)
|
||||
return !deletedObjectIDs.contains(thread.record.id)
|
||||
default:
|
||||
assertionFailure()
|
||||
return false
|
||||
|
@ -60,7 +58,7 @@ final class MastodonStatusThreadViewModel {
|
|||
let newItems = items.filter { item in
|
||||
switch item {
|
||||
case .thread(let thread):
|
||||
return !deletedObjectIDs.contains(thread.record.objectID)
|
||||
return !deletedObjectIDs.contains(thread.record.id)
|
||||
default:
|
||||
assertionFailure()
|
||||
return false
|
||||
|
@ -81,14 +79,16 @@ extension MastodonStatusThreadViewModel {
|
|||
nodes: [Node]
|
||||
) {
|
||||
let ids = nodes.map { $0.statusID }
|
||||
var dictionary: [Status.ID: Status] = [:]
|
||||
var dictionary: [Mastodon.Entity.Status.ID: Mastodon.Entity.Status] = [:]
|
||||
do {
|
||||
let request = Status.sortedFetchRequest
|
||||
request.predicate = Status.predicate(domain: domain, ids: ids)
|
||||
let statuses = try self.context.managedObjectContext.fetch(request)
|
||||
for status in statuses {
|
||||
dictionary[status.id] = status
|
||||
}
|
||||
// let request = Status.sortedFetchRequest
|
||||
// request.predicate = Status.predicate(domain: domain, ids: ids)
|
||||
// let statuses = try self.context.managedObjectContext.fetch(request)
|
||||
|
||||
#warning("figure out what this does")
|
||||
// for status in statuses {
|
||||
// dictionary[status.id] = status
|
||||
// }
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
@ -98,9 +98,9 @@ extension MastodonStatusThreadViewModel {
|
|||
guard let status = dictionary[node.statusID] else { continue }
|
||||
let isLast = i == nodes.count - 1
|
||||
|
||||
let record = ManagedObjectRecord<Status>(objectID: status.objectID)
|
||||
// let record = ManagedObjectRecord<Status>(objectID: status.objectID)
|
||||
let context = StatusItem.Thread.Context(
|
||||
status: record,
|
||||
status: status,
|
||||
displayUpperConversationLink: !isLast,
|
||||
displayBottomConversationLink: true
|
||||
)
|
||||
|
@ -119,14 +119,15 @@ extension MastodonStatusThreadViewModel {
|
|||
let childrenIDs = nodes
|
||||
.map { node in [node.statusID, node.children.first?.statusID].compactMap { $0 } }
|
||||
.flatMap { $0 }
|
||||
var dictionary: [Status.ID: Status] = [:]
|
||||
var dictionary: [Mastodon.Entity.Status.ID: Mastodon.Entity.Status] = [:]
|
||||
do {
|
||||
let request = Status.sortedFetchRequest
|
||||
request.predicate = Status.predicate(domain: domain, ids: childrenIDs)
|
||||
let statuses = try self.context.managedObjectContext.fetch(request)
|
||||
for status in statuses {
|
||||
dictionary[status.id] = status
|
||||
}
|
||||
// let request = Status.sortedFetchRequest
|
||||
// request.predicate = Status.predicate(domain: domain, ids: childrenIDs)
|
||||
// let statuses = try self.context.managedObjectContext.fetch(request)
|
||||
#warning("what is this???")
|
||||
// for status in statuses {
|
||||
// dictionary[status.id] = status
|
||||
// }
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
@ -135,9 +136,9 @@ extension MastodonStatusThreadViewModel {
|
|||
for node in nodes {
|
||||
guard let status = dictionary[node.statusID] else { continue }
|
||||
// first tier
|
||||
let record = ManagedObjectRecord<Status>(objectID: status.objectID)
|
||||
// let record = ManagedObjectRecord<Status>(objectID: status.objectID)
|
||||
let context = StatusItem.Thread.Context(
|
||||
status: record
|
||||
status: status
|
||||
)
|
||||
let item = StatusItem.thread(.leaf(context: context))
|
||||
newItems.append(item)
|
||||
|
@ -145,9 +146,9 @@ extension MastodonStatusThreadViewModel {
|
|||
// second tier
|
||||
if let child = node.children.first {
|
||||
guard let secondaryStatus = dictionary[child.statusID] else { continue }
|
||||
let secondaryRecord = ManagedObjectRecord<Status>(objectID: secondaryStatus.objectID)
|
||||
// let secondaryRecord = ManagedObjectRecord<Status>(objectID: secondaryStatus.objectID)
|
||||
let secondaryContext = StatusItem.Thread.Context(
|
||||
status: secondaryRecord,
|
||||
status: secondaryStatus,
|
||||
displayUpperConversationLink: true
|
||||
)
|
||||
let secondaryItem = StatusItem.thread(.leaf(context: secondaryContext))
|
||||
|
@ -263,7 +264,7 @@ extension MastodonStatusThreadViewModel.Node {
|
|||
}
|
||||
|
||||
extension MastodonStatusThreadViewModel {
|
||||
func delete(objectIDs: [NSManagedObjectID]) {
|
||||
func delete(objectIDs: [Mastodon.Entity.Status.ID]) {
|
||||
var set = deletedObjectIDs
|
||||
for objectID in objectIDs {
|
||||
set.insert(objectID)
|
||||
|
|
|
@ -16,24 +16,22 @@ extension Account {
|
|||
@MainActor
|
||||
static func fetch(in managedObjectContext: NSManagedObjectContext) async throws -> [Account] {
|
||||
// get accounts
|
||||
let accounts: [Account] = try await managedObjectContext.perform {
|
||||
let results = AuthenticationServiceProvider.shared.authentications
|
||||
let accounts = results.compactMap { mastodonAuthentication -> Account? in
|
||||
guard let user = mastodonAuthentication.user(in: managedObjectContext) else {
|
||||
return nil
|
||||
}
|
||||
let account = Account(
|
||||
identifier: mastodonAuthentication.identifier.uuidString,
|
||||
display: user.displayNameWithFallback,
|
||||
subtitle: user.acctWithDomain,
|
||||
image: user.avatarImageURL().flatMap { INImage(url: $0) }
|
||||
)
|
||||
account.name = user.displayNameWithFallback
|
||||
account.username = user.acctWithDomain
|
||||
return account
|
||||
let results = AuthenticationServiceProvider.shared.authentications
|
||||
var accounts = [Account]()
|
||||
for mastodonAuthentication in results {
|
||||
guard let user = try? await mastodonAuthentication.me() else {
|
||||
continue
|
||||
}
|
||||
return accounts
|
||||
} // end managedObjectContext.perform
|
||||
let account = Account(
|
||||
identifier: mastodonAuthentication.identifier.uuidString,
|
||||
display: user.displayNameWithFallback,
|
||||
subtitle: user.acctWithDomain,
|
||||
image: user.avatarImageURL().flatMap { INImage(url: $0) }
|
||||
)
|
||||
account.name = user.displayNameWithFallback
|
||||
account.username = user.acctWithDomain
|
||||
accounts.append(account)
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
|
|
@ -23,13 +23,20 @@ public class AuthenticationServiceProvider: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func update(instance: Instance, where domain: String) {
|
||||
func update(instance: Mastodon.Entity.Instance, where domain: String) {
|
||||
authentications = authentications.map { authentication in
|
||||
guard authentication.domain == domain else { return authentication }
|
||||
return authentication.updating(instance: instance)
|
||||
}
|
||||
}
|
||||
|
||||
func update(instanceV2: Mastodon.Entity.V2.Instance, where domain: String) {
|
||||
authentications = authentications.map { authentication in
|
||||
guard authentication.domain == domain else { return authentication }
|
||||
return authentication.updating(instanceV2: instanceV2)
|
||||
}
|
||||
}
|
||||
|
||||
func delete(authentication: MastodonAuthentication) {
|
||||
authentications.removeAll(where: { $0 == authentication })
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
public struct MastodonAuthentication: Codable, Hashable {
|
||||
|
@ -21,7 +20,9 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||
public private(set) var activedAt: Date
|
||||
|
||||
public private(set) var userID: String
|
||||
public private(set) var instanceObjectIdURI: URL?
|
||||
|
||||
public private(set) var instance: Mastodon.Entity.Instance?
|
||||
public private(set) var instanceV2: Mastodon.Entity.V2.Instance?
|
||||
|
||||
internal var persistenceIdentifier: String {
|
||||
"\(username)@\(domain)"
|
||||
|
@ -34,7 +35,9 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||
appAccessToken: String,
|
||||
userAccessToken: String,
|
||||
clientID: String,
|
||||
clientSecret: String
|
||||
clientSecret: String,
|
||||
instance: Mastodon.Entity.Instance?,
|
||||
instanceV2: Mastodon.Entity.V2.Instance?
|
||||
) -> Self {
|
||||
let now = Date()
|
||||
return MastodonAuthentication(
|
||||
|
@ -49,7 +52,8 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||
updatedAt: now,
|
||||
activedAt: now,
|
||||
userID: userID,
|
||||
instanceObjectIdURI: nil
|
||||
instance: instance,
|
||||
instanceV2: instanceV2
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -65,7 +69,8 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||
updatedAt: Date? = nil,
|
||||
activedAt: Date? = nil,
|
||||
userID: String? = nil,
|
||||
instanceObjectIdURI: URL? = nil
|
||||
instance: Mastodon.Entity.Instance? = nil,
|
||||
instanceV2: Mastodon.Entity.V2.Instance? = nil
|
||||
) -> Self {
|
||||
MastodonAuthentication(
|
||||
identifier: identifier ?? self.identifier,
|
||||
|
@ -79,31 +84,28 @@ public struct MastodonAuthentication: Codable, Hashable {
|
|||
updatedAt: updatedAt ?? self.updatedAt,
|
||||
activedAt: activedAt ?? self.activedAt,
|
||||
userID: userID ?? self.userID,
|
||||
instanceObjectIdURI: instanceObjectIdURI ?? self.instanceObjectIdURI
|
||||
instance: instance,
|
||||
instanceV2: instanceV2
|
||||
)
|
||||
}
|
||||
|
||||
public func instance(in context: NSManagedObjectContext) -> Instance? {
|
||||
guard let instanceObjectIdURI,
|
||||
let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: instanceObjectIdURI)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let instance = try? context.existingObject(with: objectID) as? Instance
|
||||
return instance
|
||||
func updating(instance: Mastodon.Entity.Instance) -> Self {
|
||||
copy(instance: instance)
|
||||
}
|
||||
|
||||
public func user(in context: NSManagedObjectContext) -> MastodonUser? {
|
||||
let userPredicate = MastodonUser.predicate(domain: domain, id: userID)
|
||||
return MastodonUser.findOrFetch(in: context, matching: userPredicate)
|
||||
}
|
||||
|
||||
func updating(instance: Instance) -> Self {
|
||||
copy(instanceObjectIdURI: instance.objectID.uriRepresentation())
|
||||
|
||||
func updating(instanceV2: Mastodon.Entity.V2.Instance) -> Self {
|
||||
copy(instanceV2: instanceV2)
|
||||
}
|
||||
|
||||
func updating(activatedAt: Date) -> Self {
|
||||
copy(activedAt: activatedAt)
|
||||
}
|
||||
|
||||
public func me() async throws -> Mastodon.Entity.Account {
|
||||
try await Mastodon.API.Account.lookupAccount(
|
||||
session: .shared, domain: domain,
|
||||
query: .init(acct: userID),
|
||||
authorization: Mastodon.API.OAuth.Authorization(accessToken: userAccessToken)
|
||||
).singleOutput().value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,13 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
private struct MastodonBlockContext {
|
||||
let sourceUserID: MastodonUser.ID
|
||||
let targetUserID: MastodonUser.ID
|
||||
let sourceUserID: Mastodon.Entity.Account.ID
|
||||
let targetUserID: Mastodon.Entity.Account.ID
|
||||
let targetUsername: String
|
||||
let isBlocking: Bool
|
||||
let isFollowing: Bool
|
||||
|
@ -41,113 +39,92 @@ extension APIService {
|
|||
limit: limit,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
let userIDs = response.value.map { $0.id }
|
||||
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
|
||||
|
||||
let fetchRequest = MastodonUser.fetchRequest()
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.includesPropertyValues = false
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let users = try managedObjectContext.fetch(fetchRequest) as! [MastodonUser]
|
||||
|
||||
for user in users {
|
||||
user.deleteStatusAndNotificationFeeds(in: managedObjectContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
public func toggleBlock(
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
let blockContext: MastodonBlockContext = try await managedObjectContext.performChanges {
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let user = user.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
let isBlocking = user.blockingBy.contains(me)
|
||||
let isFollowing = user.followingBy.contains(me)
|
||||
// toggle block state
|
||||
user.update(isBlocking: !isBlocking, by: me)
|
||||
// update follow state implicitly
|
||||
if !isBlocking {
|
||||
// will do block action. set to unfollow
|
||||
user.update(isFollowing: false, by: me)
|
||||
}
|
||||
|
||||
return MastodonBlockContext(
|
||||
sourceUserID: me.id,
|
||||
targetUserID: user.id,
|
||||
targetUsername: user.username,
|
||||
isBlocking: isBlocking,
|
||||
isFollowing: isFollowing
|
||||
)
|
||||
}
|
||||
|
||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
do {
|
||||
if blockContext.isBlocking {
|
||||
let response = try await Mastodon.API.Account.unblock(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
accountID: blockContext.targetUserID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
result = .success(response)
|
||||
} else {
|
||||
let response = try await Mastodon.API.Account.block(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
accountID: blockContext.targetUserID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
result = .success(response)
|
||||
}
|
||||
} catch {
|
||||
result = .failure(error)
|
||||
}
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let user = user.object(in: managedObjectContext),
|
||||
let me = 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(isBlocking: blockContext.isBlocking, by: me)
|
||||
user.update(isFollowing: blockContext.isFollowing, by: me)
|
||||
}
|
||||
}
|
||||
|
||||
let response = try result.get()
|
||||
return response
|
||||
}
|
||||
// public func toggleBlock(
|
||||
// user: Mastodon.Entity.Account,
|
||||
// authenticationBox: MastodonAuthenticationBox
|
||||
// ) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||
//
|
||||
//// let managedObjectContext = backgroundManagedObjectContext
|
||||
//// let blockContext: MastodonBlockContext = try await managedObjectContext.performChanges {
|
||||
//// let authentication = authenticationBox.authentication
|
||||
////
|
||||
// guard
|
||||
//// let user = user.object(in: managedObjectContext),
|
||||
// let me = authenticationBox.inMemoryCache.meAccount,
|
||||
// let relationship = try await Mastodon.API.Account.relationships(
|
||||
// session: session,
|
||||
// domain: authenticationBox.domain,
|
||||
// query: .init(ids: [user.id]),
|
||||
// authorization: authenticationBox.userAuthorization
|
||||
// ).singleOutput().value.first
|
||||
// else {
|
||||
// throw APIError.implicit(.badRequest)
|
||||
// }
|
||||
////
|
||||
//// let isBlocking = user.blockingBy.contains(me)
|
||||
//// let isFollowing = user.followingBy.contains(me)
|
||||
//// // toggle block state
|
||||
//// user.update(isBlocking: !isBlocking, by: me)
|
||||
//// // update follow state implicitly
|
||||
//// if !isBlocking {
|
||||
//// // will do block action. set to unfollow
|
||||
//// user.update(isFollowing: false, by: me)
|
||||
//// }
|
||||
////
|
||||
//// return MastodonBlockContext(
|
||||
//// sourceUserID: me.id,
|
||||
//// targetUserID: user.id,
|
||||
//// targetUsername: user.username,
|
||||
//// isBlocking: isBlocking,
|
||||
//// isFollowing: isFollowing
|
||||
//// )
|
||||
//// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// let blockContext = MastodonBlockContext(
|
||||
// sourceUserID: me.id,
|
||||
// targetUserID: user.id,
|
||||
// targetUsername: user.username,
|
||||
// isBlocking: !relationship.blocking,
|
||||
// isFollowing: {
|
||||
// if !relationship.blocking {
|
||||
// return false
|
||||
// }
|
||||
// return relationship.following
|
||||
// }()
|
||||
// )
|
||||
//
|
||||
// let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
// do {
|
||||
// if blockContext.isBlocking {
|
||||
// let response = try await Mastodon.API.Account.unblock(
|
||||
// session: session,
|
||||
// domain: authenticationBox.domain,
|
||||
// accountID: blockContext.targetUserID,
|
||||
// authorization: authenticationBox.userAuthorization
|
||||
// ).singleOutput()
|
||||
// result = .success(response)
|
||||
// } else {
|
||||
// let response = try await Mastodon.API.Account.block(
|
||||
// session: session,
|
||||
// domain: authenticationBox.domain,
|
||||
// accountID: blockContext.targetUserID,
|
||||
// authorization: authenticationBox.userAuthorization
|
||||
// ).singleOutput()
|
||||
// result = .success(response)
|
||||
// }
|
||||
// } catch {
|
||||
// result = .failure(error)
|
||||
// }
|
||||
//
|
||||
// let response = try result.get()
|
||||
// return response
|
||||
// }
|
||||
|
||||
public func toggleBlock(
|
||||
user: Mastodon.Entity.Account,
|
||||
|
@ -178,21 +155,3 @@ extension APIService {
|
|||
return response
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
func deleteStatusAndNotificationFeeds(in context: NSManagedObjectContext) {
|
||||
statuses.map {
|
||||
$0.feeds
|
||||
.union($0.reblogFrom.map { $0.feeds }.flatMap { $0 })
|
||||
.union($0.notifications.map { $0.feeds }.flatMap { $0 })
|
||||
}
|
||||
.flatMap { $0 }
|
||||
.forEach(context.delete)
|
||||
|
||||
notifications.map {
|
||||
$0.feeds
|
||||
}
|
||||
.flatMap { $0 }
|
||||
.forEach(context.delete)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,111 +7,109 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
private struct MastodonFollowContext {
|
||||
let sourceUserID: MastodonUser.ID
|
||||
let targetUserID: MastodonUser.ID
|
||||
let sourceUserID: Mastodon.Entity.Account.ID
|
||||
let targetUserID: Mastodon.Entity.Account.ID
|
||||
let isFollowing: Bool
|
||||
let isPending: Bool
|
||||
let needsUnfollow: Bool
|
||||
}
|
||||
|
||||
/// Toggle friendship between target MastodonUser and current MastodonUser
|
||||
///
|
||||
/// Following / Following pending <-> Unfollow
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - mastodonUser: target MastodonUser
|
||||
/// - activeMastodonAuthenticationBox: `AuthenticationService.MastodonAuthenticationBox`
|
||||
/// - Returns: publisher for `Relationship`
|
||||
public func toggleFollow(
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
let _followContext: MastodonFollowContext? = try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return nil }
|
||||
guard let user = user.object(in: managedObjectContext) else { return nil }
|
||||
|
||||
let isFollowing = user.followingBy.contains(me)
|
||||
let isPending = user.followRequestedBy.contains(me)
|
||||
let needsUnfollow = isFollowing || isPending
|
||||
|
||||
if needsUnfollow {
|
||||
// unfollow
|
||||
user.update(isFollowing: false, by: me)
|
||||
user.update(isFollowRequested: false, by: me)
|
||||
} else {
|
||||
// follow
|
||||
if user.locked {
|
||||
user.update(isFollowing: false, by: me)
|
||||
user.update(isFollowRequested: true, by: me)
|
||||
} else {
|
||||
user.update(isFollowing: true, by: me)
|
||||
user.update(isFollowRequested: false, by: me)
|
||||
}
|
||||
}
|
||||
let context = MastodonFollowContext(
|
||||
sourceUserID: me.id,
|
||||
targetUserID: user.id,
|
||||
isFollowing: isFollowing,
|
||||
isPending: isPending,
|
||||
needsUnfollow: needsUnfollow
|
||||
)
|
||||
return context
|
||||
}
|
||||
|
||||
guard let followContext = _followContext else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
// request follow or unfollow
|
||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
do {
|
||||
let response = try await Mastodon.API.Account.follow(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
accountID: followContext.targetUserID,
|
||||
followQueryType: followContext.needsUnfollow ? .unfollow : .follow(query: .init()),
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
result = .success(response)
|
||||
} catch {
|
||||
result = .failure(error)
|
||||
}
|
||||
|
||||
// update friendship state
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext),
|
||||
let user = user.object(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(isFollowing: followContext.isFollowing, by: me)
|
||||
user.update(isFollowRequested: followContext.isPending, by: me)
|
||||
}
|
||||
}
|
||||
|
||||
let response = try result.get()
|
||||
return response
|
||||
}
|
||||
// /// Toggle friendship between target MastodonUser and current MastodonUser
|
||||
// ///
|
||||
// /// Following / Following pending <-> Unfollow
|
||||
// ///
|
||||
// /// - Parameters:
|
||||
// /// - mastodonUser: target MastodonUser
|
||||
// /// - activeMastodonAuthenticationBox: `AuthenticationService.MastodonAuthenticationBox`
|
||||
// /// - Returns: publisher for `Relationship`
|
||||
// public func toggleFollow(
|
||||
// user: ManagedObjectRecord<MastodonUser>,
|
||||
// authenticationBox: MastodonAuthenticationBox
|
||||
// ) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||||
//
|
||||
// let managedObjectContext = backgroundManagedObjectContext
|
||||
// let _followContext: MastodonFollowContext? = try await managedObjectContext.performChanges {
|
||||
// guard let me = authenticationBox.inMemoryCache.meAccount else { return nil }
|
||||
// guard let user = user.object(in: managedObjectContext) else { return nil }
|
||||
//
|
||||
// let isFollowing = user.followingBy.contains(me)
|
||||
// let isPending = user.followRequestedBy.contains(me)
|
||||
// let needsUnfollow = isFollowing || isPending
|
||||
//
|
||||
// if needsUnfollow {
|
||||
// // unfollow
|
||||
// user.update(isFollowing: false, by: me)
|
||||
// user.update(isFollowRequested: false, by: me)
|
||||
// } else {
|
||||
// // follow
|
||||
// if user.locked {
|
||||
// user.update(isFollowing: false, by: me)
|
||||
// user.update(isFollowRequested: true, by: me)
|
||||
// } else {
|
||||
// user.update(isFollowing: true, by: me)
|
||||
// user.update(isFollowRequested: false, by: me)
|
||||
// }
|
||||
// }
|
||||
// let context = MastodonFollowContext(
|
||||
// sourceUserID: me.id,
|
||||
// targetUserID: user.id,
|
||||
// isFollowing: isFollowing,
|
||||
// isPending: isPending,
|
||||
// needsUnfollow: needsUnfollow
|
||||
// )
|
||||
// return context
|
||||
// }
|
||||
//
|
||||
// guard let followContext = _followContext else {
|
||||
// throw APIError.implicit(.badRequest)
|
||||
// }
|
||||
//
|
||||
// // request follow or unfollow
|
||||
// let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
// do {
|
||||
// let response = try await Mastodon.API.Account.follow(
|
||||
// session: session,
|
||||
// domain: authenticationBox.domain,
|
||||
// accountID: followContext.targetUserID,
|
||||
// followQueryType: followContext.needsUnfollow ? .unfollow : .follow(query: .init()),
|
||||
// authorization: authenticationBox.userAuthorization
|
||||
// ).singleOutput()
|
||||
// result = .success(response)
|
||||
// } catch {
|
||||
// result = .failure(error)
|
||||
// }
|
||||
//
|
||||
// // update friendship state
|
||||
// try await managedObjectContext.performChanges {
|
||||
// guard let me = authenticationBox.authentication.user(in: managedObjectContext),
|
||||
// let user = user.object(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(isFollowing: followContext.isFollowing, by: me)
|
||||
// user.update(isFollowRequested: followContext.isPending, by: me)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let response = try result.get()
|
||||
// return response
|
||||
// }
|
||||
|
||||
public func toggleFollow(
|
||||
user: Mastodon.Entity.Account,
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
@ -25,28 +23,7 @@ extension APIService {
|
|||
query: query,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(
|
||||
domain: authenticationBox.domain,
|
||||
id: authenticationBox.userID
|
||||
)
|
||||
request.fetchLimit = 1
|
||||
guard let user = managedObjectContext.safeFetch(request).first else { return }
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
|
||||
Persistence.MastodonUser.update(
|
||||
mastodonUser: user,
|
||||
context: Persistence.MastodonUser.RelationshipContext(
|
||||
entity: response.value,
|
||||
me: me,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
@ -32,27 +30,7 @@ extension APIService {
|
|||
query: query,
|
||||
authorization: authorization
|
||||
).singleOutput()
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
for entity in response.value {
|
||||
let result = Persistence.MastodonUser.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.MastodonUser.PersistContext(
|
||||
domain: domain,
|
||||
entity: entity,
|
||||
cache: nil,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
|
||||
let user = result.user
|
||||
me?.update(isFollowing: true, by: user)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
@ -33,30 +31,7 @@ extension APIService {
|
|||
query: query,
|
||||
authorization: authorization
|
||||
).singleOutput()
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
for entity in response.value {
|
||||
let result = Persistence.MastodonUser.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.MastodonUser.PersistContext(
|
||||
domain: domain,
|
||||
entity: entity,
|
||||
cache: nil,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
|
||||
if let me = me {
|
||||
let user = result.user
|
||||
user.update(isFollowing: true, by: me)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
|
|
@ -7,15 +7,13 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
private struct MastodonMuteContext {
|
||||
let sourceUserID: MastodonUser.ID
|
||||
let targetUserID: MastodonUser.ID
|
||||
let sourceUserID: Mastodon.Entity.Account.ID
|
||||
let targetUserID: Mastodon.Entity.Account.ID
|
||||
let targetUsername: String
|
||||
let isMuting: Bool
|
||||
}
|
||||
|
@ -41,21 +39,6 @@ extension APIService {
|
|||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
let userIDs = response.value.map { $0.id }
|
||||
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
|
||||
|
||||
let fetchRequest = MastodonUser.fetchRequest()
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.includesPropertyValues = false
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let users = try managedObjectContext.fetch(fetchRequest) as! [MastodonUser]
|
||||
|
||||
for user in users {
|
||||
user.deleteStatusAndNotificationFeeds(in: managedObjectContext)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
|
|
@ -170,21 +170,7 @@ extension APIService {
|
|||
notificationID: notificationID,
|
||||
authorization: authorization
|
||||
).singleOutput()
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
_ = Persistence.Notification.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Notification.PersistContext(
|
||||
domain: domain,
|
||||
entity: response.value,
|
||||
me: me,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
@ -27,7 +25,7 @@ extension APIService {
|
|||
authorization: authorization
|
||||
).singleOutput()
|
||||
|
||||
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
||||
return response
|
||||
} // end func
|
||||
|
||||
public func followTag(
|
||||
|
@ -44,7 +42,7 @@ extension APIService {
|
|||
authorization: authorization
|
||||
).singleOutput()
|
||||
|
||||
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
||||
return response
|
||||
} // end func
|
||||
|
||||
public func unfollowTag(
|
||||
|
@ -61,31 +59,6 @@ extension APIService {
|
|||
authorization: authorization
|
||||
).singleOutput()
|
||||
|
||||
return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox)
|
||||
return response
|
||||
} // end func
|
||||
}
|
||||
|
||||
fileprivate extension APIService {
|
||||
func persistTag(
|
||||
from response: Mastodon.Response.Content<Mastodon.Entity.Tag>,
|
||||
domain: String,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Tag> {
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
_ = Persistence.Tag.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Tag.PersistContext(
|
||||
domain: domain,
|
||||
entity: response.value,
|
||||
me: me,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
public final class InstanceService {
|
||||
|
@ -16,7 +14,6 @@ public final class InstanceService {
|
|||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
// input
|
||||
let backgroundManagedObjectContext: NSManagedObjectContext
|
||||
weak var apiService: APIService?
|
||||
weak var authenticationService: AuthenticationService?
|
||||
|
||||
|
@ -26,7 +23,6 @@ public final class InstanceService {
|
|||
apiService: APIService,
|
||||
authenticationService: AuthenticationService
|
||||
) {
|
||||
self.backgroundManagedObjectContext = apiService.backgroundManagedObjectContext
|
||||
self.apiService = apiService
|
||||
self.authenticationService = authenticationService
|
||||
|
||||
|
@ -68,57 +64,13 @@ extension InstanceService {
|
|||
}
|
||||
|
||||
private func updateInstance(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.Instance>) -> AnyPublisher<Void, Error> {
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
return managedObjectContext.performChanges {
|
||||
// get instance
|
||||
let (instance, _) = APIService.CoreData.createOrMergeInstance(
|
||||
into: managedObjectContext,
|
||||
domain: domain,
|
||||
entity: response.value,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
|
||||
// update instance
|
||||
AuthenticationServiceProvider.shared.update(instance: instance, where: domain)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
AuthenticationServiceProvider.shared.update(instance: response.value, where: domain)
|
||||
return Just(Void()).setFailureType(to: Error.self).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
private func updateInstanceV2(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.V2.Instance>) -> AnyPublisher<Void, Error> {
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
return managedObjectContext.performChanges {
|
||||
// get instance
|
||||
let (instance, _) = APIService.CoreData.createOrMergeInstance(
|
||||
in: managedObjectContext,
|
||||
context: .init(
|
||||
domain: domain,
|
||||
entity: response.value,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
|
||||
// update instance
|
||||
AuthenticationServiceProvider.shared.update(instance: instance, where: domain)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
AuthenticationServiceProvider.shared.update(instanceV2: response.value, where: domain)
|
||||
return Just(Void()).setFailureType(to: Error.self).eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import MastodonCommon
|
||||
import MastodonLocalization
|
||||
|
@ -96,32 +94,29 @@ extension NotificationService {
|
|||
|
||||
extension NotificationService {
|
||||
public func unreadApplicationShortcutItems() async throws -> [UIApplicationShortcutItem] {
|
||||
guard let authenticationService = self.authenticationService else { return [] }
|
||||
let managedObjectContext = authenticationService.managedObjectContext
|
||||
return try await managedObjectContext.perform {
|
||||
var items: [UIApplicationShortcutItem] = []
|
||||
for authentication in AuthenticationServiceProvider.shared.authentications {
|
||||
guard let user = authentication.user(in: managedObjectContext) else { continue }
|
||||
let accessToken = authentication.userAccessToken
|
||||
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken)
|
||||
guard count > 0 else { continue }
|
||||
|
||||
let title = "@\(user.acctWithDomain)"
|
||||
let subtitle = L10n.A11y.Plural.Count.Unread.notification(count)
|
||||
|
||||
let item = UIApplicationShortcutItem(
|
||||
type: NotificationService.unreadShortcutItemIdentifier,
|
||||
localizedTitle: title,
|
||||
localizedSubtitle: subtitle,
|
||||
icon: nil,
|
||||
userInfo: [
|
||||
"accessToken": accessToken as NSSecureCoding
|
||||
]
|
||||
)
|
||||
items.append(item)
|
||||
}
|
||||
return items
|
||||
// guard let authenticationService = self.authenticationService else { return [] }
|
||||
var items: [UIApplicationShortcutItem] = []
|
||||
for authentication in AuthenticationServiceProvider.shared.authentications {
|
||||
guard let user = try? await authentication.me() else { continue }
|
||||
let accessToken = authentication.userAccessToken
|
||||
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken)
|
||||
guard count > 0 else { continue }
|
||||
|
||||
let title = "@\(user.acctWithDomain)"
|
||||
let subtitle = L10n.A11y.Plural.Count.Unread.notification(count)
|
||||
|
||||
let item = UIApplicationShortcutItem(
|
||||
type: NotificationService.unreadShortcutItemIdentifier,
|
||||
localizedTitle: title,
|
||||
localizedSubtitle: subtitle,
|
||||
icon: nil,
|
||||
userInfo: [
|
||||
"accessToken": accessToken as NSSecureCoding
|
||||
]
|
||||
)
|
||||
items.append(item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -98,6 +98,15 @@ extension Mastodon.Entity.Account {
|
|||
}
|
||||
}
|
||||
|
||||
public var domainFromAcct: String {
|
||||
if !acct.contains("@") {
|
||||
return domain!
|
||||
} else {
|
||||
let domain = acct.split(separator: "@").last
|
||||
return String(domain!)
|
||||
}
|
||||
}
|
||||
|
||||
public func acctWithDomainIfMissing(_ localDomain: String) -> String {
|
||||
guard acct.contains("@") else {
|
||||
return "\(acct)@\(localDomain)"
|
||||
|
|
|
@ -172,3 +172,21 @@ extension Mastodon.Entity.Instance.Configuration {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Instance: Hashable {
|
||||
public static func == (lhs: Mastodon.Entity.Instance, rhs: Mastodon.Entity.Instance) -> Bool {
|
||||
lhs.uri == rhs.uri
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(uri)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Instance.Configuration: Hashable {
|
||||
public static func == (lhs: Mastodon.Entity.Instance.Configuration, rhs: Mastodon.Entity.Instance.Configuration) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {}
|
||||
}
|
||||
|
|
|
@ -110,3 +110,13 @@ extension Mastodon.Entity.V2.Instance {
|
|||
public let account: Mastodon.Entity.Account?
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.V2.Instance: Hashable {
|
||||
public static func == (lhs: Mastodon.Entity.V2.Instance, rhs: Mastodon.Entity.V2.Instance) -> Bool {
|
||||
lhs.domain == rhs.domain
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(domain)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ extension Mastodon.Entity {
|
|||
case voted
|
||||
case ownVotes = "own_votes"
|
||||
case options
|
||||
case isVoting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,6 @@ extension Mastodon.Entity {
|
|||
|
||||
case visibility
|
||||
case sensitive
|
||||
case sensitiveToggled
|
||||
|
||||
case spoilerText = "spoiler_text"
|
||||
case mediaAttachments = "media_attachments"
|
||||
|
|
|
@ -153,21 +153,17 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||
self.authContext = authContext
|
||||
self.destination = destination
|
||||
self.composeContext = composeContext
|
||||
|
||||
self.visibility = {
|
||||
// default private when user locked
|
||||
var visibility: Mastodon.Entity.Status.Visibility = {
|
||||
guard let author = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else {
|
||||
guard let author = authContext.mastodonAuthenticationBox.inMemoryCache.meAccount else {
|
||||
return .public
|
||||
}
|
||||
return author.locked ? .private : .public
|
||||
}()
|
||||
// set visibility for reply post
|
||||
if case .reply(let status) = destination {
|
||||
// context.managedObjectContext.performAndWait {
|
||||
// guard let status = record.object(in: context.managedObjectContext) else {
|
||||
// assertionFailure()
|
||||
// return
|
||||
// }
|
||||
let repliedStatusVisibility = status.visibility
|
||||
switch repliedStatusVisibility {
|
||||
case .public, .unlisted:
|
||||
|
@ -225,7 +221,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||
// assertionFailure()
|
||||
// return
|
||||
// }
|
||||
let author = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
let author = authContext.mastodonAuthenticationBox.inMemoryCache.meAccount
|
||||
|
||||
var mentionAccts: [String] = []
|
||||
if author?.id != status.account.id {
|
||||
|
@ -258,11 +254,8 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
|||
|
||||
// set limit
|
||||
let _configuration: Mastodon.Entity.Instance.Configuration? = {
|
||||
var configuration: Mastodon.Entity.Instance.Configuration? = nil
|
||||
context.managedObjectContext.performAndWait {
|
||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
||||
configuration = authentication.instance(in: context.managedObjectContext)?.configuration
|
||||
}
|
||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
||||
var configuration: Mastodon.Entity.Instance.Configuration? = authentication.instance?.configuration
|
||||
return configuration
|
||||
}()
|
||||
if let configuration = _configuration {
|
||||
|
@ -319,7 +312,7 @@ extension ComposeContentViewModel {
|
|||
$authContext
|
||||
.sink { [weak self] authContext in
|
||||
guard let self = self else { return }
|
||||
guard let user = authContext.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else { return }
|
||||
guard let user = authContext.mastodonAuthenticationBox.inMemoryCache.meAccount else { return }
|
||||
self.avatarURL = user.avatarImageURL()
|
||||
self.name = user.nameMetaContent ?? PlaintextMetaContent(string: user.displayNameWithFallback)
|
||||
self.username = user.acctWithDomain
|
||||
|
@ -563,10 +556,7 @@ extension ComposeContentViewModel {
|
|||
|
||||
// author
|
||||
let managedObjectContext = self.context.managedObjectContext
|
||||
var _author: ManagedObjectRecord<MastodonUser>?
|
||||
managedObjectContext.performAndWait {
|
||||
_author = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext)?.asRecord
|
||||
}
|
||||
var _author = authContext.mastodonAuthenticationBox.inMemoryCache.meAccount
|
||||
guard let author = _author else {
|
||||
throw AppError.badAuthentication
|
||||
}
|
||||
|
@ -619,10 +609,7 @@ extension ComposeContentViewModel {
|
|||
|
||||
// author
|
||||
let managedObjectContext = self.context.managedObjectContext
|
||||
var _author: ManagedObjectRecord<MastodonUser>?
|
||||
managedObjectContext.performAndWait {
|
||||
_author = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext)?.asRecord
|
||||
}
|
||||
var _author = authContext.mastodonAuthenticationBox.inMemoryCache.meAccount
|
||||
guard let author = _author else {
|
||||
throw AppError.badAuthentication
|
||||
}
|
||||
|
@ -818,3 +805,28 @@ extension ComposeContentViewModel: AttachmentViewModelDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
public var nameMetaContent: MastodonMetaContent? {
|
||||
do {
|
||||
let content = MastodonContent(content: displayNameWithFallback, emojis: emojis?.asDictionary ?? [:])
|
||||
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||
return metaContent
|
||||
} catch {
|
||||
assertionFailure()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var bioMetaContent: MastodonMetaContent? {
|
||||
do {
|
||||
let content = MastodonContent(content: note, emojis: emojis?.asDictionary ?? [:])
|
||||
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||
return metaContent
|
||||
} catch {
|
||||
assertionFailure()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
import Combine
|
||||
|
@ -11,7 +9,7 @@ public final class MastodonEditStatusPublisher: NSObject, ProgressReporting {
|
|||
|
||||
// Input
|
||||
public let statusID: Mastodon.Entity.Status.ID
|
||||
public let author: ManagedObjectRecord<MastodonUser>
|
||||
public let author: Mastodon.Entity.Account
|
||||
|
||||
// content warning
|
||||
public let isContentWarningComposing: Bool
|
||||
|
@ -41,7 +39,7 @@ public final class MastodonEditStatusPublisher: NSObject, ProgressReporting {
|
|||
|
||||
public init(
|
||||
statusID: Mastodon.Entity.Status.ID,
|
||||
author: ManagedObjectRecord<MastodonUser>,
|
||||
author: Mastodon.Entity.Account,
|
||||
isContentWarningComposing: Bool,
|
||||
contentWarning: String,
|
||||
content: String,
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
|
@ -17,7 +15,7 @@ public final class MastodonStatusPublisher: NSObject, ProgressReporting {
|
|||
// Input
|
||||
|
||||
// author
|
||||
public let author: ManagedObjectRecord<MastodonUser>
|
||||
public let author: Mastodon.Entity.Account?
|
||||
// refer
|
||||
public let replyTo: Mastodon.Entity.Status?
|
||||
// content warning
|
||||
|
@ -47,7 +45,7 @@ public final class MastodonStatusPublisher: NSObject, ProgressReporting {
|
|||
public var reactor: StatusPublisherReactor?
|
||||
|
||||
public init(
|
||||
author: ManagedObjectRecord<MastodonUser>,
|
||||
author: Mastodon.Entity.Account,
|
||||
replyTo: Mastodon.Entity.Status?,
|
||||
isContentWarningComposing: Bool,
|
||||
contentWarning: String,
|
||||
|
|
|
@ -220,7 +220,7 @@ extension NotificationView.ViewModel {
|
|||
)
|
||||
)
|
||||
.sink { [weak self] authorName, isMuting, isBlocking, isMyselfIsTranslatedIsFollowed in
|
||||
guard let name = authorName?.string, let self, let context = self.context, let authContext = self.authContext else {
|
||||
guard let name = authorName?.string, let self, let authContext = self.authContext else {
|
||||
notificationView.menuButton.menu = nil
|
||||
return
|
||||
}
|
||||
|
@ -228,8 +228,7 @@ extension NotificationView.ViewModel {
|
|||
let (isMyself, isTranslated, isFollowed) = isMyselfIsTranslatedIsFollowed
|
||||
|
||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
||||
let instance = authentication.instance(in: context.managedObjectContext)
|
||||
let isTranslationEnabled = instance?.isTranslationEnabled ?? false
|
||||
let isTranslationEnabled = authentication.instanceV2?.configuration?.translation?.enabled ?? false
|
||||
|
||||
let menuContext = NotificationView.AuthorMenuContext(
|
||||
name: name,
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Meta
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
|
@ -668,14 +666,13 @@ extension StatusView.ViewModel {
|
|||
let (isMuting, isBlocking, isBookmark, isFollowed) = tupleTwo
|
||||
let (translatedFromLanguage, language) = tupleThree
|
||||
|
||||
guard let name = authorName?.string, let context = self.context, let authContext = self.authContext else {
|
||||
guard let name = authorName?.string, let authContext = self.authContext else {
|
||||
statusView.authorView.menuButton.menu = nil
|
||||
return
|
||||
}
|
||||
|
||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
||||
let instance = authentication.instance(in: context.managedObjectContext)
|
||||
let isTranslationEnabled = instance?.isTranslationEnabled ?? false
|
||||
let isTranslationEnabled = authentication.instanceV2?.configuration?.translation?.enabled ?? false
|
||||
|
||||
let menuContext = StatusAuthorView.AuthorMenuContext(
|
||||
name: name,
|
||||
|
|
|
@ -10,6 +10,11 @@ import Combine
|
|||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
|
||||
enum RelationshipError: Error {
|
||||
case FailedToResolveUser
|
||||
}
|
||||
|
||||
public enum RelationshipAction: Int, CaseIterable {
|
||||
case showReblogs
|
||||
|
@ -127,8 +132,39 @@ public final class RelationshipViewModel {
|
|||
relationshipUpdatePublisher
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] user, me, _ in
|
||||
guard let self = self else { return }
|
||||
.compactMap { user, me, _ -> Optional<(Mastodon.Entity.Account, Mastodon.Entity.Account, MastodonAuthentication)> in
|
||||
guard let user, let me else { return nil }
|
||||
guard let authBox = AuthenticationServiceProvider.shared.authenticationSortedByActivation().first else { return nil }
|
||||
return (user, me, authBox)
|
||||
}
|
||||
.flatMap { (user, me, authBox) in
|
||||
return Mastodon.API.Account.relationships(
|
||||
session: .shared,
|
||||
domain: authBox.domain,
|
||||
query: .init(ids: [user.id]),
|
||||
authorization: Mastodon.API.OAuth.Authorization(accessToken: authBox.userAccessToken)
|
||||
).eraseToAnyPublisher()
|
||||
}
|
||||
.sink { completion in
|
||||
// no-op
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self, let relationship = response.value.first else { return }
|
||||
isMyself = relationship.id == me?.id
|
||||
isFollowingBy = relationship.followedBy
|
||||
isFollowing = relationship.following
|
||||
isMuting = relationship.muting == true
|
||||
isBlockingBy = relationship.blockedBy == true
|
||||
isBlocking = relationship.blocking
|
||||
showReblogs = relationship.showingReblogs == true
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
|
||||
|
||||
|
||||
// .sink { [weak self] relationship in
|
||||
|
||||
// guard let self = self else { return }
|
||||
// self.update(user: user, me: me)
|
||||
|
||||
// guard let user = user, let me = me else {
|
||||
|
@ -149,8 +185,7 @@ public final class RelationshipViewModel {
|
|||
// guard let self = self else { return }
|
||||
// self.relationshipUpdatePublisher.send()
|
||||
// }
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -82,10 +82,10 @@ private extension FollowersCountWidgetProvider {
|
|||
return completion(.unconfigured)
|
||||
}
|
||||
|
||||
let meAcctDomain = try? await authBox.authentication.me().acctWithDomain
|
||||
|
||||
guard
|
||||
let desiredAccount = configuration.account ?? authBox.authentication.user(
|
||||
in: WidgetExtension.appContext.managedObjectContext
|
||||
)?.acctWithDomain
|
||||
let desiredAccount = configuration.account ?? meAcctDomain
|
||||
else {
|
||||
return completion(.unconfigured)
|
||||
}
|
||||
|
|
|
@ -86,9 +86,7 @@ private extension MultiFollowersCountWidgetProvider {
|
|||
|
||||
if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) {
|
||||
desiredAccounts = configuredAccounts
|
||||
} else if let currentlyLoggedInAccount = authBox.authentication.user(
|
||||
in: WidgetExtension.appContext.managedObjectContext
|
||||
)?.acctWithDomain {
|
||||
} else if let currentlyLoggedInAccount = try? await authBox.authentication.me().acctWithDomain {
|
||||
desiredAccounts = [currentlyLoggedInAccount]
|
||||
} else {
|
||||
return completion(.unconfigured)
|
||||
|
|
Loading…
Reference in New Issue