wip
This commit is contained in:
parent
b32a85aebc
commit
f1e3f1a7fa
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
struct AccountListItemsInfo: Codable, Hashable, FetchableRecord {
|
||||||
|
let accountList: AccountList
|
||||||
|
let accountInfos: [AccountInfo]
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountListItemsInfo {
|
||||||
|
static func addingIncludes<T: DerivableRequest>(_ request: T) -> T where T.RowDecoder == AccountList {
|
||||||
|
request.including(all: AccountInfo.addingIncludes(AccountList.accounts).forKey(CodingKeys.accountInfos))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func request(_ request: QueryInterfaceRequest<AccountList>) -> QueryInterfaceRequest<Self> {
|
||||||
|
addingIncludes(request).asRequest(of: self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
struct AccountListJoin: ContentDatabaseRecord {
|
||||||
|
let accountListId: AccountList.Id
|
||||||
|
let accountId: Account.Id
|
||||||
|
let order: Int
|
||||||
|
|
||||||
|
static let account = belongsTo(AccountRecord.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountListJoin {
|
||||||
|
enum Columns {
|
||||||
|
static let accountListId = Column(CodingKeys.accountListId)
|
||||||
|
static let accountId = Column(CodingKeys.accountId)
|
||||||
|
static let order = Column(CodingKeys.order)
|
||||||
|
}
|
||||||
|
}
|
|
@ -252,6 +252,22 @@ extension ContentDatabase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migrator.registerMigration("1.0.0") { db in
|
||||||
|
try db.create(table: "accountList") { t in
|
||||||
|
t.column("id", .text).primaryKey(onConflict: .replace)
|
||||||
|
}
|
||||||
|
|
||||||
|
try db.create(table: "accountListJoin") { t in
|
||||||
|
t.column("accountListId", .text).indexed().notNull()
|
||||||
|
.references("accountList", onDelete: .cascade)
|
||||||
|
t.column("accountId", .text).indexed().notNull()
|
||||||
|
.references("accountRecord", onDelete: .cascade)
|
||||||
|
t.column("order", .integer).notNull()
|
||||||
|
|
||||||
|
t.primaryKey(["accountListId", "accountId", "order"], onConflict: .replace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return migrator
|
return migrator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,12 +269,39 @@ public extension ContentDatabase {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(accounts: [Account]) -> AnyPublisher<Never, Error> {
|
func insert(accounts: [Account], listId: AccountList.Id? = nil) -> AnyPublisher<Never, Error> {
|
||||||
databaseWriter.writePublisher {
|
databaseWriter.writePublisher {
|
||||||
|
var order: Int?
|
||||||
|
|
||||||
|
if let listId = listId {
|
||||||
|
try AccountList(id: listId).save($0)
|
||||||
|
order = try Int.fetchOne(
|
||||||
|
$0,
|
||||||
|
AccountListJoin.filter(AccountListJoin.Columns.accountListId == listId)
|
||||||
|
.select(max(AccountListJoin.Columns.order)))
|
||||||
|
?? 0
|
||||||
|
}
|
||||||
|
|
||||||
for account in accounts {
|
for account in accounts {
|
||||||
try account.save($0)
|
try account.save($0)
|
||||||
|
|
||||||
|
if let listId = listId, let presentOrder = order {
|
||||||
|
try AccountListJoin(accountListId: listId, accountId: account.id, order: presentOrder).save($0)
|
||||||
|
|
||||||
|
order = presentOrder + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.ignoreOutput()
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(id: Account.Id, from listId: AccountList.Id) -> AnyPublisher<Never, Error> {
|
||||||
|
databaseWriter.writePublisher(
|
||||||
|
updates: AccountListJoin.filter(
|
||||||
|
AccountListJoin.Columns.accountId == id
|
||||||
|
&& AccountListJoin.Columns.accountListId == listId)
|
||||||
|
.deleteAll)
|
||||||
.ignoreOutput()
|
.ignoreOutput()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
@ -494,6 +521,19 @@ public extension ContentDatabase {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func accountListPublisher(
|
||||||
|
id: AccountList.Id,
|
||||||
|
configuration: CollectionItem.AccountConfiguration) -> AnyPublisher<[CollectionSection], Error> {
|
||||||
|
ValueObservation.tracking(
|
||||||
|
AccountListItemsInfo.request(AccountList.filter(AccountList.Columns.id == id)).fetchOne)
|
||||||
|
.removeDuplicates()
|
||||||
|
.publisher(in: databaseWriter)
|
||||||
|
.map { $0?.accountInfos.map { CollectionItem.account(.init(info: $0), configuration, nil) } }
|
||||||
|
.replaceNil(with: [])
|
||||||
|
.map { [CollectionSection(items: $0)] }
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
func listsPublisher() -> AnyPublisher<[Timeline], Error> {
|
func listsPublisher() -> AnyPublisher<[Timeline], Error> {
|
||||||
ValueObservation.tracking(TimelineRecord.filter(TimelineRecord.Columns.listId != nil)
|
ValueObservation.tracking(TimelineRecord.filter(TimelineRecord.Columns.listId != nil)
|
||||||
.order(TimelineRecord.Columns.listTitle.asc)
|
.order(TimelineRecord.Columns.listTitle.asc)
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
public struct AccountList: ContentDatabaseRecord, Hashable {
|
||||||
|
let id: Id
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AccountList {
|
||||||
|
typealias Id = String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountList {
|
||||||
|
enum Columns {
|
||||||
|
static let id = Column(CodingKeys.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static let accountListJoins = hasMany(AccountListJoin.self)
|
||||||
|
static let accounts = hasMany(
|
||||||
|
AccountRecord.self,
|
||||||
|
through: accountListJoins.order(AccountListJoin.Columns.order),
|
||||||
|
using: AccountListJoin.account)
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ public struct AccountListService {
|
||||||
public let navigationService: NavigationService
|
public let navigationService: NavigationService
|
||||||
public let canRefresh = false
|
public let canRefresh = false
|
||||||
|
|
||||||
private let accountsSubject = CurrentValueSubject<[Account], Error>([])
|
private let listId = UUID().uuidString
|
||||||
private let endpoint: AccountsEndpoint
|
private let endpoint: AccountsEndpoint
|
||||||
private let mastodonAPIClient: MastodonAPIClient
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
private let contentDatabase: ContentDatabase
|
private let contentDatabase: ContentDatabase
|
||||||
|
@ -29,10 +29,7 @@ public struct AccountListService {
|
||||||
self.mastodonAPIClient = mastodonAPIClient
|
self.mastodonAPIClient = mastodonAPIClient
|
||||||
self.contentDatabase = contentDatabase
|
self.contentDatabase = contentDatabase
|
||||||
self.titleComponents = titleComponents
|
self.titleComponents = titleComponents
|
||||||
sections = accountsSubject
|
sections = contentDatabase.accountListPublisher(id: listId, configuration: endpoint.configuration)
|
||||||
.map { [.init(items: $0.map { CollectionItem.account($0, endpoint.configuration, nil) })] } // TODO: revisit
|
|
||||||
.removeDuplicates()
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
||||||
accountIdsForRelationships = accountIdsForRelationshipsSubject.eraseToAnyPublisher()
|
accountIdsForRelationships = accountIdsForRelationshipsSubject.eraseToAnyPublisher()
|
||||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
|
@ -40,8 +37,8 @@ public struct AccountListService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension AccountListService {
|
public extension AccountListService {
|
||||||
func remove(id: Account.Id) {
|
func remove(id: Account.Id) -> AnyPublisher<Never, Error> {
|
||||||
accountsSubject.value.removeAll { $0.id == id }
|
contentDatabase.remove(id: id, from: listId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,15 +46,13 @@ extension AccountListService: CollectionService {
|
||||||
public func request(maxId: String?, minId: String?, search: Search?) -> AnyPublisher<Never, Error> {
|
public func request(maxId: String?, minId: String?, search: Search?) -> AnyPublisher<Never, Error> {
|
||||||
mastodonAPIClient.pagedRequest(endpoint, maxId: maxId, minId: minId)
|
mastodonAPIClient.pagedRequest(endpoint, maxId: maxId, minId: minId)
|
||||||
.handleEvents(receiveOutput: {
|
.handleEvents(receiveOutput: {
|
||||||
let presentIds = Set(accountsSubject.value.map(\.id))
|
accountIdsForRelationshipsSubject.send(Set($0.result.map(\.id)))
|
||||||
accountsSubject.value.append(contentsOf: $0.result.filter { !presentIds.contains($0.id) })
|
|
||||||
|
|
||||||
guard let maxId = $0.info.maxId else { return }
|
guard let maxId = $0.info.maxId else { return }
|
||||||
|
|
||||||
nextPageMaxIdSubject.send(maxId)
|
nextPageMaxIdSubject.send(maxId)
|
||||||
accountIdsForRelationshipsSubject.send(Set($0.result.map(\.id)))
|
|
||||||
})
|
})
|
||||||
.flatMap { contentDatabase.insert(accounts: $0.result) }
|
.flatMap { contentDatabase.insert(accounts: $0.result, listId: listId) }
|
||||||
.ignoreOutput()
|
.ignoreOutput()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,6 +336,8 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
|
|
||||||
public func applyAccountListEdit(viewModel: AccountViewModel, edit: CollectionItemEvent.AccountListEdit) {
|
public func applyAccountListEdit(viewModel: AccountViewModel, edit: CollectionItemEvent.AccountListEdit) {
|
||||||
(collectionService as? AccountListService)?.remove(id: viewModel.id)
|
(collectionService as? AccountListService)?.remove(id: viewModel.id)
|
||||||
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
switch edit {
|
switch edit {
|
||||||
case .acceptFollowRequest, .rejectFollowRequest:
|
case .acceptFollowRequest, .rejectFollowRequest:
|
||||||
|
|
Loading…
Reference in New Issue