Skip to content
Snippets Groups Projects
RequestsReceivedViewModel.swift 9.15 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import UIKit
import Shared
Bruno Muniz's avatar
Bruno Muniz committed
import Combine
import Defaults
import XXModels
import XXClient
import DrawerFeature
import ReportingFeature
Bruno Muniz's avatar
Bruno Muniz committed
import CombineSchedulers
Bruno Muniz's avatar
Bruno Muniz committed
import XXMessengerClient
Bruno Muniz's avatar
Bruno Muniz committed

import struct XXModels.Group

struct RequestReceived: Hashable, Equatable {
Bruno Muniz's avatar
Bruno Muniz committed
  var request: Request?
  var isHidden: Bool
  var leader: String?
Bruno Muniz's avatar
Bruno Muniz committed
final class RequestsReceivedViewModel {
Bruno Muniz's avatar
Bruno Muniz committed
  @Dependency(\.app.dbManager) var dbManager
  @Dependency(\.app.messenger) var messenger
  @Dependency(\.app.hudManager) var hudManager
  @Dependency(\.reportingStatus) var reportingStatus
Bruno Muniz's avatar
Bruno Muniz committed
  
  @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool

  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<XXModels.Contact, Never> {
    contactConfirmationSubject.eraseToAnyPublisher()
  }
  
  private var cancellables = Set<AnyCancellable>()
  private let updateSubject = CurrentValueSubject<Void, Never>(())
  private let verifyingSubject = PassthroughSubject<Void, Never>()
  private let groupConfirmationSubject = PassthroughSubject<Group, Never>()
  private let contactConfirmationSubject = PassthroughSubject<XXModels.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: [
        .friend,
        .hidden,
        .verified,
        .verificationFailed,
        .verificationInProgress
      ],
      isBlocked: reportingStatus.isEnabled() ? false : nil,
      isBanned: reportingStatus.isEnabled() ? false : nil
    )
    
    let groupStream = try! dbManager.getDB()
Bruno Muniz's avatar
Bruno Muniz committed
      .fetchGroupsPublisher(groupsQuery)
      .replaceError(with: [])
    
    let contactsStream = try! dbManager.getDB()
Bruno Muniz's avatar
Bruno Muniz committed
      .fetchContactsPublisher(contactsQuery)
      .replaceError(with: [])
    
    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,
            isHidden: contact.authStatus == .hidden,
            leader: nil
          )
        }
      }
      
      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)
        return snapshot
      }
      
      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
Bruno Muniz's avatar
Bruno Muniz committed
        guard let self else { return }
Bruno Muniz's avatar
Bruno Muniz committed
        
        do {
          contact.authStatus = .verificationInProgress
          try self.dbManager.getDB().saveContact(contact)
Bruno Muniz's avatar
Bruno Muniz committed
          
          print(">>> [messenger.verifyContact] will start")
          
          if try self.messenger.verifyContact(XXClient.Contact.live(contact.marshaled!)) {
            print(">>> [messenger.verifyContact] verified")
            
            contact.authStatus = .verified
            contact = try self.dbManager.getDB().saveContact(contact)
Bruno Muniz's avatar
Bruno Muniz committed
          } else {
            print(">>> [messenger.verifyContact] is fake")
            
            try self.dbManager.getDB().deleteContact(contact)
Bruno Muniz's avatar
Bruno Muniz committed
          }
        } catch {
          print(">>> [messenger.verifyContact] thrown an exception: \(error.localizedDescription)")
          
          contact.authStatus = .verificationFailed
          _ = try? self.dbManager.getDB().saveContact(contact)
Bruno Muniz's avatar
Bruno Muniz committed
        }
      }
    } else if request.status == .verifying {
      verifyingSubject.send()
Bruno Muniz's avatar
Bruno Muniz committed
  }
  
  func didRequestHide(group: Group) {
    if var group = try? dbManager.getDB().fetchGroups(.init(id: [group.id])).first {
Bruno Muniz's avatar
Bruno Muniz committed
      group.authStatus = .hidden
      _ = try? dbManager.getDB().saveGroup(group)
Bruno Muniz's avatar
Bruno Muniz committed
  }
  
  func didRequestAccept(group: Group) {
Bruno Muniz's avatar
Bruno Muniz committed
    
    backgroundScheduler.schedule { [weak self] in
Bruno Muniz's avatar
Bruno Muniz committed
      guard let self else { return }
Bruno Muniz's avatar
Bruno Muniz committed
      
      do {
Bruno Muniz's avatar
Bruno Muniz committed
        try self.messenger.groupChat()!.joinGroup(serializedGroupData: group.serialized)

Bruno Muniz's avatar
Bruno Muniz committed
        var group = group
        group.authStatus = .participating
        try self.dbManager.getDB().saveGroup(group)
Bruno Muniz's avatar
Bruno Muniz committed
        
        self.hudManager.hide()
Bruno Muniz's avatar
Bruno Muniz committed
        self.groupConfirmationSubject.send(group)
      } catch {
        self.hudManager.show(.init(error: error))
Bruno Muniz's avatar
Bruno Muniz committed
      }
Bruno Muniz's avatar
Bruno Muniz committed
  }
  
  func fetchMembers(
    _ group: Group,
    _ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void
  ) {
    if let info = try? dbManager.getDB().fetchGroupInfos(.init(groupId: group.id)).first {
      try! dbManager.getDB().fetchContactsPublisher(.init(id: Set(info.members.map(\.id))))
Bruno Muniz's avatar
Bruno Muniz committed
        .replaceError(with: [])
Bruno Muniz's avatar
Bruno Muniz committed
        .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
              )
Bruno Muniz's avatar
Bruno Muniz committed
          
          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
              )
Bruno Muniz's avatar
Bruno Muniz committed
          
          completion(.success(withUsername + withoutUsername))
        }.store(in: &cancellables)
Bruno Muniz's avatar
Bruno Muniz committed
  }
  
  func didRequestHide(contact: XXModels.Contact) {
    if var contact = try? dbManager.getDB().fetchContacts(.init(id: [contact.id])).first {
Bruno Muniz's avatar
Bruno Muniz committed
      contact.authStatus = .hidden
      _ = try? dbManager.getDB().saveContact(contact)
Bruno Muniz's avatar
Bruno Muniz committed
  }
  
  func didRequestAccept(contact: XXModels.Contact, nickname: String? = nil) {
Bruno Muniz's avatar
Bruno Muniz committed
    
    var contact = contact
    contact.authStatus = .confirming
    contact.nickname = nickname ?? contact.username
    
    backgroundScheduler.schedule { [weak self] in
Bruno Muniz's avatar
Bruno Muniz committed
      guard let self else { return }
Bruno Muniz's avatar
Bruno Muniz committed
      
      do {
        try self.dbManager.getDB().saveContact(contact)
Bruno Muniz's avatar
Bruno Muniz committed
        
        let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: .live(contact.marshaled!))
        contact.authStatus = .friend
        try self.dbManager.getDB().saveContact(contact)
Bruno Muniz's avatar
Bruno Muniz committed
        
        self.hudManager.hide()
Bruno Muniz's avatar
Bruno Muniz committed
        self.contactConfirmationSubject.send(contact)
      } catch {
        contact.authStatus = .confirmationFailed
        _ = try? self.dbManager.getDB().saveContact(contact)
        self.hudManager.show(.init(error: error))
Bruno Muniz's avatar
Bruno Muniz committed
      }
Bruno Muniz's avatar
Bruno Muniz committed
  }
  
  func groupChatWith(group: Group) -> GroupInfo {
    guard let info = try? dbManager.getDB().fetchGroupInfos(.init(groupId: group.id)).first else {
Bruno Muniz's avatar
Bruno Muniz committed
      fatalError()
Bruno Muniz's avatar
Bruno Muniz committed
    
    return info
  }