mv-experiment #1
|
@ -16,27 +16,11 @@ enum ClientState: Equatable {
|
||||||
final class Client: ObservableObject {
|
final class Client: ObservableObject {
|
||||||
@Published private(set) var state: ClientState = .enabled(.disconnected)
|
@Published private(set) var state: ClientState = .enabled(.disconnected)
|
||||||
@Published private(set) var credentials: Credentials
|
@Published private(set) var credentials: Credentials
|
||||||
|
@Published private(set) var rosters: [Roster] = []
|
||||||
var rosters: [Roster] {
|
|
||||||
get async {
|
|
||||||
// Fetching only non-locally-deleted rosters and only for active credentials
|
|
||||||
if credentials.isActive {
|
|
||||||
let creds = credentials
|
|
||||||
let rosters = try? await Database.shared.dbQueue.read { db in
|
|
||||||
try Roster
|
|
||||||
.filter(Column("bareJid") == creds.bareJid)
|
|
||||||
.filter(Column("locallyDeleted") == false)
|
|
||||||
.fetchAll(db)
|
|
||||||
}
|
|
||||||
return rosters ?? []
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var connection: XMPPClient
|
private var connection: XMPPClient
|
||||||
private var connectionCancellable: AnyCancellable?
|
private var connectionCancellable: AnyCancellable?
|
||||||
|
private var rostersCancellable: AnyCancellable?
|
||||||
|
|
||||||
private var rosterManager = ClientMartinRosterManager()
|
private var rosterManager = ClientMartinRosterManager()
|
||||||
|
|
||||||
|
@ -51,6 +35,18 @@ final class Client: ObservableObject {
|
||||||
self.state = .disabled
|
self.state = .disabled
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
rostersCancellable = ValueObservation
|
||||||
|
.tracking { db in
|
||||||
|
try Roster
|
||||||
|
.filter(Column("bareJid") == self.credentials.bareJid)
|
||||||
|
.filter(Column("locallyDeleted") == false)
|
||||||
|
.fetchAll(db)
|
||||||
|
}
|
||||||
|
.publisher(in: Database.shared.dbQueue)
|
||||||
|
.catch { _ in Just([]) }
|
||||||
|
.sink { rosters in
|
||||||
|
self.rosters = rosters
|
||||||
|
}
|
||||||
switch state {
|
switch state {
|
||||||
case .connected:
|
case .connected:
|
||||||
self.state = .enabled(.connected)
|
self.state = .enabled(.connected)
|
||||||
|
|
|
@ -8,19 +8,20 @@ final class ClientsStore: ObservableObject {
|
||||||
|
|
||||||
@Published private(set) var ready = false
|
@Published private(set) var ready = false
|
||||||
@Published private(set) var clients: [Client] = []
|
@Published private(set) var clients: [Client] = []
|
||||||
|
@Published private(set) var actualRosters: [Roster] = []
|
||||||
|
|
||||||
private let credentialsObservation = ValueObservation.tracking(Credentials.fetchAll)
|
private var credentialsCancellable: AnyCancellable?
|
||||||
|
private var rostersCancellable: AnyCancellable?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
Task {
|
credentialsCancellable = ValueObservation
|
||||||
do {
|
.tracking { db in
|
||||||
for try await creds in credentialsObservation.values(in: Database.shared.dbQueue) {
|
try Credentials.fetchAll(db)
|
||||||
processCredentials(creds)
|
|
||||||
if !ready {
|
|
||||||
ready = true
|
|
||||||
}
|
}
|
||||||
}
|
.publisher(in: Database.shared.dbQueue)
|
||||||
} catch {}
|
.catch { _ in Just([]) }
|
||||||
|
.sink { [weak self] creds in
|
||||||
|
self?.processCredentials(creds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +38,12 @@ final class ClientsStore: ObservableObject {
|
||||||
var updatedClients = clients.filter { credentialsJids.contains($0.credentials.bareJid) }
|
var updatedClients = clients.filter { credentialsJids.contains($0.credentials.bareJid) }
|
||||||
updatedClients.append(contentsOf: newClients)
|
updatedClients.append(contentsOf: newClients)
|
||||||
clients = updatedClients
|
clients = updatedClients
|
||||||
|
|
||||||
|
if !ready {
|
||||||
|
ready = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resubscribeRosters()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func client(for credentials: Credentials) -> Client? {
|
private func client(for credentials: Credentials) -> Client? {
|
||||||
|
@ -69,16 +76,6 @@ extension ClientsStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ClientsStore {
|
extension ClientsStore {
|
||||||
var actualRosters: [Roster] {
|
|
||||||
get async {
|
|
||||||
var allRosters: [Roster] = []
|
|
||||||
for client in clients {
|
|
||||||
allRosters.append(contentsOf: await client.rosters)
|
|
||||||
}
|
|
||||||
return allRosters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRoster(_ credentials: Credentials, contactJID: String, name: String?, groups: [String]) async throws {
|
func addRoster(_ credentials: Credentials, contactJID: String, name: String?, groups: [String]) async throws {
|
||||||
// check that roster exist in db as locally deleted and undelete it
|
// check that roster exist in db as locally deleted and undelete it
|
||||||
let deletedLocally = try await Roster.fetchDeletedLocally()
|
let deletedLocally = try await Roster.fetchDeletedLocally()
|
||||||
|
@ -93,4 +90,22 @@ extension ClientsStore {
|
||||||
}
|
}
|
||||||
try await client.addRoster(contactJID, name: name, groups: groups)
|
try await client.addRoster(contactJID, name: name, groups: groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func resubscribeRosters() {
|
||||||
|
let clientsJids = clients
|
||||||
|
.filter { $0.state != .disabled }
|
||||||
|
.map { $0.credentials.bareJid }
|
||||||
|
|
||||||
|
rostersCancellable = ValueObservation.tracking { db in
|
||||||
|
try Roster
|
||||||
|
.filter(clientsJids.contains(Column("bareJid")))
|
||||||
|
.filter(Column("locallyDeleted") == false)
|
||||||
|
.fetchAll(db)
|
||||||
|
}
|
||||||
|
.publisher(in: Database.shared.dbQueue)
|
||||||
|
.catch { _ in Just([]) }
|
||||||
|
.sink { [weak self] rosters in
|
||||||
|
self?.actualRosters = rosters
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,6 @@ struct ContactsScreen: View {
|
||||||
@Environment(\.router) var router
|
@Environment(\.router) var router
|
||||||
@EnvironmentObject var clientsStore: ClientsStore
|
@EnvironmentObject var clientsStore: ClientsStore
|
||||||
|
|
||||||
@State private var rosters: [Roster] = []
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
// Background color
|
// Background color
|
||||||
|
@ -28,9 +26,9 @@ struct ContactsScreen: View {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Contacts list
|
// Contacts list
|
||||||
if !rosters.isEmpty {
|
if !clientsStore.actualRosters.isEmpty {
|
||||||
List {
|
List {
|
||||||
ForEach(rosters) { roster in
|
ForEach(clientsStore.actualRosters) { roster in
|
||||||
ContactsScreenRow(
|
ContactsScreenRow(
|
||||||
roster: roster
|
roster: roster
|
||||||
)
|
)
|
||||||
|
@ -43,9 +41,9 @@ struct ContactsScreen: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task {
|
// .task {
|
||||||
rosters = await clientsStore.actualRosters
|
// rosters = await clientsStore.actualRosters
|
||||||
}
|
// }
|
||||||
// .alert(isPresented: $isErrorAlertPresented) {
|
// .alert(isPresented: $isErrorAlertPresented) {
|
||||||
// Alert(
|
// Alert(
|
||||||
// title: Text(L10n.Global.Error.title),
|
// title: Text(L10n.Global.Error.title),
|
||||||
|
|
Loading…
Reference in a new issue