sendbird-uikit-ios/Sources/ViewModel/SelectUser/SBUBaseSelectUserViewModel....

461 lines
17 KiB
Swift

//
// SBUBaseSelectUserViewModel.swift
// SendbirdUIKit
//
// Created by Tez Park on 2021/09/29.
// Copyright © 2021 Sendbird, Inc. All rights reserved.
//
import UIKit
import SendbirdChatSDK
public protocol SBUBaseSelectUserViewModelDelegate: SBUCommonViewModelDelegate {
/// Called when the user list has been changed.
func baseSelectedUserViewModel(
_ viewModel: SBUBaseSelectUserViewModel,
didChangeUserList users: [SBUUser]?,
needsToReload: Bool
)
/// Called when the selected user list has been updated.
func baseSelectedUserViewModel(
_ viewModel: SBUBaseSelectUserViewModel,
didUpdateSelectedUsers selectedUsers: [SBUUser]?
)
}
public protocol SBUBaseSelectUserViewModelDataSource: AnyObject {
/// When creating and using a user list directly, overriding this function and return the next user list.
/// Make this function return the next list each time it is called.
/// - Returns: next user list
func nextUserList() -> [SBUUser]?
}
open class SBUBaseSelectUserViewModel: NSObject {
// MARK: - Constants
static let limit: UInt = 20
// MARK: - Property (Public)
public internal(set) var channel: BaseChannel?
public internal(set) var channelURL: String?
public internal(set) var channelType: ChannelType = .group
@SBUAtomic public internal(set) var userList: [SBUUser] = []
@SBUAtomic public internal(set) var selectedUserList: Set<SBUUser> = []
public var userListQuery: ApplicationUserListQuery?
public var memberListQuery: MemberListQuery?
public var participantListQuery: ParticipantListQuery?
public internal(set) var inviteListType: ChannelInviteListType = .users
public private(set) var joinedUserIds: Set<String> = []
// MARK: - Property (Private)
weak var baseDelegate: SBUBaseSelectUserViewModelDelegate?
weak var baseDataSource: SBUBaseSelectUserViewModelDataSource?
internal var customUserListQuery: ApplicationUserListQuery?
internal var customMemberListQuery: MemberListQuery?
internal var customParticipantListQuery: ParticipantListQuery?
@SBUAtomic private(set) var customizedUsers: [SBUUser]?
internal var useCustomizedUsers = false
@SBUAtomic private var isLoading = false
// MARK: - Life Cycle
public init(
channel: BaseChannel? = nil,
channelURL: String? = nil,
channelType: ChannelType = .group,
users: [SBUUser]? = nil,
inviteListType: ChannelInviteListType,
userListQuery: ApplicationUserListQuery? = nil,
memberListQuery: MemberListQuery? = nil,
participantListQuery: ParticipantListQuery? = nil,
delegate: SBUBaseSelectUserViewModelDelegate? = nil,
dataSource: SBUBaseSelectUserViewModelDataSource? = nil
) {
self.baseDelegate = delegate
self.baseDataSource = dataSource
super.init()
if let channel = channel {
self.channel = channel
self.channelURL = channel.channelURL
} else if let channelURL = channelURL {
self.channelURL = channelURL
}
self.channelType = channelType
self.inviteListType = inviteListType
self.customUserListQuery = userListQuery
self.customMemberListQuery = memberListQuery
self.customParticipantListQuery = participantListQuery
self.customizedUsers = users
self.useCustomizedUsers = (users?.count ?? 0) > 0
guard let channelURL = self.channelURL else { return }
self.loadChannel(channelURL: channelURL, type: channelType)
}
// MARK: - Channel related
public func loadChannel(channelURL: String, type: ChannelType) {
self.baseDelegate?.shouldUpdateLoadingState(true)
SendbirdUI.connectIfNeeded { [weak self] _, error in
guard let self = self else { return }
if let error = error {
self.baseDelegate?.shouldUpdateLoadingState(false)
self.baseDelegate?.didReceiveError(error, isBlocker: false)
} else {
let completionHandler: ((BaseChannel?, SBError?) -> Void) = {
[weak self] channel, error in
guard let self = self else { return }
if let error = error {
self.baseDelegate?.shouldUpdateLoadingState(false)
self.baseDelegate?.didReceiveError(error, isBlocker: false)
} else if let channel = channel {
self.channel = channel
self.loadNextUserList(reset: true, users: self.customizedUsers)
}
}
switch type {
case .group:
GroupChannel.getChannel(url: channelURL, completionHandler: completionHandler)
case .open:
OpenChannel.getChannel(url: channelURL, completionHandler: completionHandler)
default:
break
}
}
}
}
// MARK: - Prepare datas
func prepareDatas() {
guard self.inviteListType == .users else { return }
guard let channel = self.channel as? GroupChannel else { return }
prepareJoinedUserIds(channel.members)
}
func prepareJoinedUserIds(_ members: [User]) {
let joinedMemberList = members.sbu_convertUserList()
if joinedMemberList.count > 0 {
self.joinedUserIds = Set(joinedMemberList.sbu_getUserIds())
}
}
// MARK: - List handling
/// Load user list.
///
/// If want using your custom user list, filled `users` with your custom user list.
///
/// - Parameters:
/// - reset: `true` is reset user list and load new list
/// - users: customized `SBUUser` array for add to user list
public func loadNextUserList(reset: Bool, users: [SBUUser]? = nil) {
if self.isLoading { return }
self.isLoading = true
if reset {
self.userListQuery = nil
self.memberListQuery = nil
self.participantListQuery = nil
self.userList = []
self.prepareDatas()
SBULog.info("[Request] User List")
} else {
SBULog.info("[Request] Next user List")
}
if let users = users {
// Customized user list
SBULog.info("\(users.count) customized users have been added.")
self.isLoading = false
self.appendUsersWithFiltering(users: users)
} else if !self.useCustomizedUsers {
switch self.inviteListType {
case .users:
self.loadNextApplicationUserList()
case .operators:
if self.channelType == .group {
self.loadNextChannelMemberList()
} else if self.channelType == .open {
self.loadNextChannelParticipantList()
}
default:
break
}
}
}
/// This function loads application user list.
///
/// If you want to call a list of users, use the `loadNextUserList(reset:users:)` function.
/// - Warning: Use this function only when you need to call `ApplicationUserList` alone.
private func loadNextApplicationUserList() {
if self.userListQuery == nil {
if self.customUserListQuery != nil {
self.userListQuery = self.customUserListQuery
} else {
let params = ApplicationUserListQueryParams()
params.limit = SBUBaseSelectUserViewModel.limit
self.userListQuery = SendbirdChat.createApplicationUserListQuery(params: params)
}
}
guard self.userListQuery?.hasNext == true else {
self.isLoading = false
self.baseDelegate?.shouldUpdateLoadingState(false)
SBULog.info("All users have been loaded.")
return
}
self.userListQuery?.loadNextPage(completionHandler: { [weak self] users, error in
guard let self = self else { return }
defer {
self.isLoading = false
self.baseDelegate?.shouldUpdateLoadingState(false)
}
if let error = error {
self.baseDelegate?.didReceiveError(error, isBlocker: false)
return
}
guard let users = users?.sbu_convertUserList() else { return }
SBULog.info("[Response] \(users.count) users")
guard !users.isEmpty else { return }
self.appendUsersWithFiltering(users: users)
})
}
private func appendUsersWithFiltering(users: [SBUUser]) {
defer {
self.isLoading = false
self.baseDelegate?.shouldUpdateLoadingState(false)
}
// Super,Broadcast channel does not contain all information in joined members.
if let channel = channel as? GroupChannel,
(channel.isBroadcast || channel.isSuper) {
self.userList += users
self.baseDelegate?.baseSelectedUserViewModel(
self,
didChangeUserList: self.userList,
needsToReload: true
)
return
}
guard !self.joinedUserIds.isEmpty else {
self.userList += users
self.baseDelegate?.baseSelectedUserViewModel(
self,
didChangeUserList: self.userList,
needsToReload: true
)
return
}
let filteredUsers = users.filter { joinedUserIds.contains($0.userId) == false }
if filteredUsers.isEmpty {
let nextUserList = (self.baseDataSource?.nextUserList()?.count ?? 0) > 0
? self.baseDataSource?.nextUserList()
: nil
self.loadNextUserList(
reset: false,
users: self.useCustomizedUsers ? nextUserList : nil
)
} else {
self.userList += filteredUsers
self.baseDelegate?.baseSelectedUserViewModel(
self,
didChangeUserList: self.userList,
needsToReload: true
)
}
}
/// This function loads channel member list.
///
/// If you want to call a list of users, use the `loadNextUserList(reset:users:)` function.
/// - Warning: Use this function only when you need to call `MemberList` alone.
private func loadNextChannelMemberList() {
if self.memberListQuery == nil {
if self.customMemberListQuery != nil {
self.memberListQuery = self.customMemberListQuery
} else {
if let channel = self.channel as? GroupChannel {
let params = MemberListQueryParams()
params.limit = SBUBaseSelectUserViewModel.limit
params.operatorFilter = .nonOperator
self.memberListQuery = channel.createMemberListQuery(params: params)
} else {
let error = SBError(domain: "Cannot create the memberListQuery.", code: -1, userInfo: nil)
self.baseDelegate?.shouldUpdateLoadingState(false)
self.baseDelegate?.didReceiveError(error)
return
}
}
}
guard self.memberListQuery?.hasNext == true else {
self.isLoading = false
self.baseDelegate?.shouldUpdateLoadingState(false)
SBULog.info("All members have been loaded.")
return
}
self.memberListQuery?.loadNextPage(completionHandler: {
[weak self] members, error in
guard let self = self else { return }
defer {
self.isLoading = false
self.baseDelegate?.shouldUpdateLoadingState(false)
}
if let error = error {
self.baseDelegate?.didReceiveError(error, isBlocker: false)
return
}
guard let members = members?.sbu_convertUserList() else { return }
SBULog.info("[Response] \(members.count) members")
guard !members.isEmpty else { return }
self.userList += members
self.baseDelegate?.baseSelectedUserViewModel(
self,
didChangeUserList: self.userList,
needsToReload: true
)
})
}
/// This function loads channel participant list.
///
/// If you want to call a list of users, use the `loadNextUserList(reset:users:)` function.
/// - Warning: Use this function only when you need to call `MemberList` alone.
private func loadNextChannelParticipantList() {
if self.participantListQuery == nil {
if self.customParticipantListQuery != nil {
self.participantListQuery = self.customParticipantListQuery
} else {
if let channel = self.channel as? OpenChannel {
let params = ParticipantListQueryParams()
params.limit = SBUBaseSelectUserViewModel.limit
self.participantListQuery = channel.createParticipantListQuery(params: params)
} else {
let error = SBError(domain: "Cannot create the participantListQuery.", code: -1, userInfo: nil)
self.baseDelegate?.shouldUpdateLoadingState(false)
self.baseDelegate?.didReceiveError(error)
return
}
}
}
guard self.participantListQuery?.hasNext == true else {
self.isLoading = false
self.baseDelegate?.shouldUpdateLoadingState(false)
SBULog.info("All participants have been loaded.")
return
}
self.participantListQuery?.loadNextPage(completionHandler: { [weak self, weak channel] users, error in
guard let self = self, let channel = channel else { return }
defer {
self.isLoading = false
self.baseDelegate?.shouldUpdateLoadingState(false)
}
if let error = error {
self.baseDelegate?.didReceiveError(error, isBlocker: false)
return
}
guard let users = users?.sbu_convertUserList() else { return }
SBULog.info("[Response] \(users.count) participants")
guard !users.isEmpty else { return }
self.userList += users.sbu_updateOperatorStatus(channel: channel)
self.baseDelegate?.baseSelectedUserViewModel(
self,
didChangeUserList: self.userList,
needsToReload: true
)
})
}
/// This function pre-loads user list.
///
/// When a part of the Tableview is displayed, then this function is load the next user list.
///
/// - Parameter indexPath: TableView's indexpath
public func preLoadNextUserList(indexPath: IndexPath) {
var queryCheck = false
switch self.inviteListType {
case .users:
queryCheck = (self.userListQuery?.hasNext == true && self.userListQuery != nil)
case .operators:
if self.channelType == .group {
queryCheck = (self.memberListQuery?.hasNext == true && self.memberListQuery != nil)
} else if self.channelType == .open {
queryCheck = (self.participantListQuery?.hasNext == true && self.participantListQuery != nil)
}
default:
break
}
if self.userList.count > 0,
(self.useCustomizedUsers || queryCheck),
indexPath.row == (self.userList.count - Int(SBUBaseSelectUserViewModel.limit)/2),
!self.isLoading {
let nextUserList = self.baseDataSource?.nextUserList()
self.loadNextUserList(
reset: false,
users: self.useCustomizedUsers ? nextUserList : nil
)
}
}
/// This function resets the user list.
public func resetUserList() {
self.loadNextUserList(reset: true, users: self.customizedUsers)
}
// MARK: - Select user
/// This function selects or deselects user.
/// - Parameter user: `SBUUser` object
public func selectUser(user: SBUUser) {
if let index = self.selectedUserList.firstIndex(where: { $0.userId == user.userId }) {
self.selectedUserList.remove(at: index)
} else {
self.selectedUserList.insert(user)
}
SBULog.info("Selected user: \(user)")
self.baseDelegate?.baseSelectedUserViewModel(
self,
didUpdateSelectedUsers: Array(self.selectedUserList)
)
}
}