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