Newer
Older
import HUD
import UIKit
import Models
import Shared
import Combine
import CombineSchedulers
import DependencyInjection
struct RequestReceived: Hashable, Equatable {
var request: Request?
var isHidden: Bool
var leader: String?
}
@Dependency var e2e: E2E
@Dependency var database: Database
@Dependency var groupManager: GroupChat
@Dependency var userDiscovery: UserDiscovery
@Dependency var reportingStatus: ReportingStatus
@KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool
var hudPublisher: AnyPublisher<HUDStatus, Never> {
hudSubject.eraseToAnyPublisher()
}
var verifyingPublisher: AnyPublisher<Void, Never> {
verifyingSubject.eraseToAnyPublisher()
}
var itemsPublisher: AnyPublisher<NSDiffableDataSourceSnapshot<Section, RequestReceived>, Never> {
itemsSubject.eraseToAnyPublisher()
}
var groupConfirmationPublisher: AnyPublisher<Group, Never> {
groupConfirmationSubject.eraseToAnyPublisher()
}
var contactConfirmationPublisher: AnyPublisher<Contact, Never> {
contactConfirmationSubject.eraseToAnyPublisher()
private let updateSubject = CurrentValueSubject<Void, Never>(())
private let verifyingSubject = PassthroughSubject<Void, Never>()
private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
private let groupConfirmationSubject = PassthroughSubject<Group, Never>()
private let contactConfirmationSubject = PassthroughSubject<Contact, Never>()
private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, RequestReceived>, Never>(.init())
var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
init() {
let groupsQuery = Group.Query(
authStatus: [
.hidden,
.pending
isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
isLeaderBanned: reportingStatus.isEnabled() ? false : nil
let contactsQuery = Contact.Query(
authStatus: [
.hidden,
.verified,
.verificationFailed,
.verificationInProgress
isBlocked: reportingStatus.isEnabled() ? false : nil,
isBanned: reportingStatus.isEnabled() ? false : nil
let groupStream = database.fetchGroupsPublisher(groupsQuery).assertNoFailure()
let contactsStream = database.fetchContactsPublisher(contactsQuery).assertNoFailure()
Publishers.CombineLatest3(
groupStream,
contactsStream,
updateSubject.eraseToAnyPublisher()
)
.subscribe(on: DispatchQueue.main)
.receive(on: DispatchQueue.global())
.map { [unowned self] data -> NSDiffableDataSourceSnapshot<Section, RequestReceived> in
var snapshot = NSDiffableDataSourceSnapshot<Section, RequestReceived>()
snapshot.appendSections([.appearing, .hidden])
let contactsFilteringFriends = data.1.filter { $0.authStatus != .friend }
let requests = data.0.map(Request.group) + contactsFilteringFriends.map(Request.contact)
let receivedRequests = requests.map { request -> RequestReceived in
switch request {
case let .group(group):
func leaderName() -> String {
if let leader = data.1.first(where: { $0.id == group.leaderId }) {
return (leader.nickname ?? leader.username) ?? "Leader is not a friend"
} else {
return "[Error retrieving leader]"
}
}
return RequestReceived(
request: request,
isHidden: group.authStatus == .hidden,
leader: leaderName()
)
case let .contact(contact):
return RequestReceived(
request: request,
if self.isShowingHiddenRequests {
snapshot.appendItems(receivedRequests.filter(\.isHidden), toSection: .hidden)
}
guard receivedRequests.filter({ $0.isHidden == false }).count > 0 else {
snapshot.appendItems([RequestReceived(isHidden: false)], toSection: .appearing)
snapshot.appendItems(receivedRequests.filter { $0.isHidden == false }, toSection: .appearing)
return snapshot
}.sink(
receiveCompletion: { _ in },
receiveValue: { [unowned self] in itemsSubject.send($0) }
).store(in: &cancellables)
}
func didToggleHiddenRequestsSwitcher() {
isShowingHiddenRequests.toggle()
updateSubject.send()
}
func didTapStateButtonFor(request: Request) {
guard case var .contact(contact) = request else { return }
if request.status == .failedToVerify {
backgroundScheduler.schedule { [weak self] in
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
guard let self = self else { return }
do {
contact.authStatus = .verificationInProgress
try self.database.saveContact(contact)
if contact.email == nil && contact.phone == nil {
let _ = try LookupUD.live(
e2eId: self.e2e.getId(),
udContact: self.userDiscovery.getContact(),
lookupId: contact.id,
callback: .init(handle: {
switch $0 {
case .success(let data):
let ownershipResult = try! self.e2e.verifyOwnership(
receivedContact: contact.marshaled!,
verifiedContact: data,
e2eId: self.e2e.getId()
)
if ownershipResult == true {
contact.authStatus = .verified
_ = try? self.database.saveContact(contact)
} else {
_ = try? self.database.deleteContact(contact)
}
case .failure(let error):
print("^^^ \(#file):\(#line) \(error.localizedDescription)")
contact.authStatus = .verificationFailed
_ = try? self.database.saveContact(contact)
}
})
)
}
} catch {
print("^^^ \(#file):\(#line) \(error.localizedDescription)")
contact.authStatus = .verificationFailed
_ = try? self.database.saveContact(contact)
}
} else if request.status == .verifying {
verifyingSubject.send()
if var group = try? database.fetchGroups(.init(id: [group.id])).first {
backgroundScheduler.schedule { [weak self] in
guard let self = self else { return }
let trackedId = try self.groupManager
.getGroup(groupId: group.id)
.getTrackedID()
try self.groupManager.joinGroup(trackedGroupId: trackedId)
var group = group
group.authStatus = .participating
try self.database.saveGroup(group)
self.hudSubject.send(.none)
self.groupConfirmationSubject.send(group)
self.hudSubject.send(.error(.init(with: error)))
func fetchMembers(
_ group: Group,
_ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void
) {
if let info = try? database.fetchGroupInfos(.init(groupId: group.id)).first {
database.fetchContactsPublisher(.init(id: Set(info.members.map(\.id))))
.assertNoFailure()
.sink { members in
let withUsername = members
.filter { $0.username != nil }
.map {
DrawerTableCellModel(
id: $0.id,
title: $0.nickname ?? $0.username!,
image: $0.photo,
isCreator: $0.id == group.leaderId,
isConnection: $0.authStatus == .friend
)
}
let withoutUsername = members
.filter { $0.username == nil }
.map {
DrawerTableCellModel(
id: $0.id,
title: "Fetching username...",
image: $0.photo,
isCreator: $0.id == group.leaderId,
isConnection: $0.authStatus == .friend
)
}
completion(.success(withUsername + withoutUsername))
}.store(in: &cancellables)
}
if var contact = try? database.fetchContacts(.init(id: [contact.id])).first {
_ = try? database.saveContact(contact)
func didRequestAccept(contact: Contact, nickname: String? = nil) {
contact.nickname = nickname ?? contact.username
backgroundScheduler.schedule { [weak self] in
guard let self = self else { return }
try self.database.saveContact(contact)
let _ = try self.e2e.confirmReceivedRequest(partnerContact: contact.id)
contact.authStatus = .friend
try self.database.saveContact(contact)
self.hudSubject.send(.none)
self.contactConfirmationSubject.send(contact)
contact.authStatus = .confirmationFailed
_ = try? self.database.saveContact(contact)
self.hudSubject.send(.error(.init(with: error)))
func groupChatWith(group: Group) -> GroupInfo {
guard let info = try? database.fetchGroupInfos(.init(groupId: group.id)).first else {