sendbird-uikit-ios/Sources/View/SelectUser/CreateChannel/SBUCreateOpenChannelViewCon...

417 lines
16 KiB
Swift

//
// SBUCreateOpenChannelViewController.swift
// SendbirdUIKit
//
// Created by Tez Park on 2022/08/24.
// Copyright © 2022 Sendbird, Inc. All rights reserved.
//
import UIKit
import SendbirdChatSDK
import PhotosUI
import MobileCoreServices
open class SBUCreateOpenChannelViewController: SBUBaseViewController, SBUActionSheetDelegate, PHPickerViewControllerDelegate, UIImagePickerControllerDelegate, SBUSelectablePhotoViewDelegate, SBUCreateOpenChannelModuleHeaderDelegate, SBUCreateOpenChannelModuleProfileInputDelegate, SBUCommonViewModelDelegate, SBUCreateOpenChannelViewModelDelegate, SBUAlertViewDelegate {
// MARK: - UI properties (Public)
public var headerComponent: SBUCreateOpenChannelModule.Header?
public var profileInputComponent: SBUCreateOpenChannelModule.ProfileInput?
// Theme
@SBUThemeWrapper(theme: SBUTheme.createOpenChannelTheme)
public var theme: SBUCreateOpenChannelTheme
// MARK: - Logic properties (Public)
public var viewModel: SBUCreateOpenChannelViewModel?
// MARK: - Constant
private let actionSheetIdPicker = 2
// MARK: - Lifecycle
@available(*, unavailable, renamed: "SBUCreateOpenChannelViewController(channel:)")
required public init?(coder: NSCoder) {
super.init(coder: coder)
fatalError()
}
@available(*, unavailable, renamed: "SBUCreateOpenChannelViewController(channel:)")
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
fatalError()
}
required public init() {
super.init(nibName: nil, bundle: nil)
SBULog.info("")
self.createViewModel()
self.headerComponent = SBUModuleSet.createOpenChannelModule.headerComponent
self.profileInputComponent = SBUModuleSet.createOpenChannelModule.profileInputComponent
}
open override func viewDidLoad() {
super.viewDidLoad()
}
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.updateStyles()
}
open override var preferredStatusBarStyle: UIStatusBarStyle {
theme.statusBarStyle
}
deinit {
SBULog.info("")
self.viewModel = nil
self.headerComponent = nil
self.profileInputComponent = nil
}
// MARK: - ViewModel
open func createViewModel() {
self.viewModel = SBUCreateOpenChannelViewModel(delegate: self)
}
// MARK: - Sendbird UIKit Life cycle
open override func setupViews() {
// Header component
self.headerComponent?.configure(delegate: self, theme: self.theme)
self.navigationItem.titleView = self.headerComponent?.titleView
self.navigationItem.leftBarButtonItem = self.headerComponent?.leftBarButton
self.navigationItem.rightBarButtonItem = self.headerComponent?.rightBarButton
// Body component
self.profileInputComponent?.configure(
delegate: self,
theme: self.theme
)
if let profileInputComponent = self.profileInputComponent {
self.view.addSubview(profileInputComponent)
}
}
open override func setupLayouts() {
self.profileInputComponent?.sbu_constraint(equalTo: self.view, left: 0, right: 0, top: 0, bottom: 0)
}
open override func setupStyles() {
self.setupNavigationBar(
backgroundColor: self.theme.navigationBarTintColor,
shadowColor: self.theme.navigationBarShadowColor
)
self.headerComponent?.setupStyles(theme: self.theme)
self.profileInputComponent?.setupStyles(theme: self.theme)
self.view.backgroundColor = theme.backgroundColor
}
open override func updateStyles() {
self.setupStyles()
}
// MARK: - Actions
/// This function creates open channel with profileInputComponent informations.
public func createChannel() {
let channelName = self.profileInputComponent?.getChannelName() ?? ""
let coverImage: UIImage? = self.profileInputComponent?.getChannelCoverImage()
self.createChannel(channelName: channelName, coverImage: coverImage)
}
/// This function creates open channel.
/// - Parameters:
/// - channelName: Channel name
/// - coverImage: Channel cover image
public func createChannel(channelName: String, coverImage: UIImage?) {
self.viewModel?.createChannel(channelName: channelName, coverImage: coverImage)
}
/// This function shows the channel image selection menu.
public func selectChannelImage(needRemoveItem: Bool) {
let removeItem = SBUActionSheetItem(
title: SBUStringSet.RemovePhoto,
color: theme.actionSheetRemoveTextColor,
tag: MediaResourceType.delete.rawValue,
completionHandler: nil
)
let cameraItem = SBUActionSheetItem(
title: SBUStringSet.Camera,
color: theme.actionSheetTextColor,
image: SBUIconSetType.iconCamera.image(
with: theme.actionSheetItemColor,
to: SBUIconSetType.Metric.iconActionSheetItem
),
tag: MediaResourceType.camera.rawValue,
completionHandler: nil
)
let libraryItem = SBUActionSheetItem(
title: SBUStringSet.PhotoVideoLibrary,
color: theme.actionSheetTextColor,
image: SBUIconSetType.iconPhoto.image(
with: theme.actionSheetItemColor,
to: SBUIconSetType.Metric.iconActionSheetItem
),
tag: MediaResourceType.library.rawValue,
completionHandler: nil
)
let cancelItem = SBUActionSheetItem(
title: SBUStringSet.Cancel,
color: theme.actionSheetItemColor,
completionHandler: nil
)
let items = needRemoveItem ? [removeItem, cameraItem, libraryItem] : [cameraItem, libraryItem]
SBUActionSheet.show(
items: items,
cancelItem: cancelItem,
identifier: actionSheetIdPicker,
delegate: self
)
}
/// This function shows image picker for changing channel image.
/// - Parameter type: Media resource type (`MediaResourceType`)
open func showChannelImagePicker(with type: MediaResourceType) {
switch type {
case .camera:
SBUPermissionManager.shared.requestCameraAccess(for: .video) { [weak self] in
guard let self = self else { return }
self.showCamera()
} onDenied: { [weak self] in
guard let self = self else { return }
self.showPermissionAlert(forType: .camera)
}
case .library:
SBUPermissionManager.shared.requestPhotoAccessIfNeeded { status in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
switch status {
case .all:
self.showPhotoLibraryPicker()
case .limited:
self.showLimitedPhotoLibraryPicker()
default:
self.showPermissionAlert()
}
}
}
case .delete:
self.profileInputComponent?.updateChannelImage(nil)
default: break
}
}
/// Presents `UIImagePickerController` for using camera.
open func showCamera() {
let sourceType: UIImagePickerController.SourceType = .camera
let mediaType: [String] = [
String(kUTTypeImage)
]
if UIImagePickerController.isSourceTypeAvailable(sourceType) {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.sourceType = sourceType
imagePickerController.mediaTypes = mediaType
self.present(imagePickerController, animated: true, completion: nil)
}
}
/// Presents `UIImagePickerController`. If `SBUGlobals.UsingPHPicker`is `true`, it presents `PHPickerViewController` in iOS 14 or later.
/// - NOTE: If you want to use customized `PHPickerConfiguration`, please override this method.
open func showPhotoLibraryPicker() {
if #available(iOS 14, *), SBUGlobals.isPHPickerEnabled {
var configuration = PHPickerConfiguration()
configuration.filter = .any(of: [.images])
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
self.present(picker, animated: true, completion: nil)
return
}
let sourceType: UIImagePickerController.SourceType = .photoLibrary
let mediaType: [String] = [
String(kUTTypeImage),
]
if UIImagePickerController.isSourceTypeAvailable(sourceType) {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.sourceType = sourceType
imagePickerController.mediaTypes = mediaType
self.present(imagePickerController, animated: true, completion: nil)
}
}
open func showLimitedPhotoLibraryPicker() {
let selectablePhotoVC = SBUSelectablePhotoViewController(mediaType: .image)
selectablePhotoVC.delegate = self
let nav = UINavigationController(rootViewController: selectablePhotoVC)
self.present(nav, animated: true, completion: nil)
}
open func showPermissionAlert(forType permissionType: SBUPermissionManager.PermissionType = .photoLibrary) {
SBUPermissionManager.shared.showPermissionAlert(
forType: permissionType,
alertViewDelegate: self
)
}
// MARK: - Keyboard
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
// MARK: - Common
/// This function dismisses `ViewController` and moves to created channel.
/// - Parameters:
/// - channel: Created channel
/// - messageListParams: messageListParams
open func dismissAndMoveToChannel(_ channel: BaseChannel,
messageListParams: MessageListParams?) {
SendbirdUI.moveToChannel(
channelURL: channel.channelURL,
messageListParams: messageListParams
)
}
// MARK: - Error handling
private func errorHandler(_ error: SBError) {
self.errorHandler(error.localizedDescription, error.code)
}
open override func errorHandler(_ message: String?, _ code: NSInteger? = nil) {
SBULog.error("Did receive error: \(message ?? "")")
}
// MARK: - SBUCreateOpenChannelModuleHeaderDelegate
open func createOpenChannelModule(_ headerComponent: SBUCreateOpenChannelModule.Header,
didUpdateTitleView titleView: UIView?) {
self.navigationItem.titleView = titleView
}
open func createOpenChannelModule(_ headerComponent: SBUCreateOpenChannelModule.Header,
didUpdateLeftItem leftItem: UIBarButtonItem?) {
self.navigationItem.leftBarButtonItem = leftItem
}
open func createOpenChannelModule(_ headerComponent: SBUCreateOpenChannelModule.Header,
didUpdateRightItem rightItem: UIBarButtonItem?) {
self.navigationItem.rightBarButtonItem = rightItem
}
open func createOpenChannelModule(_ headerComponent: SBUCreateOpenChannelModule.Header,
didTapLeftItem leftItem: UIBarButtonItem) {
self.onClickBack()
}
open func createOpenChannelModule(_ headerComponent: SBUCreateOpenChannelModule.Header,
didTapRightItem rightItem: UIBarButtonItem) {
self.createChannel()
}
// MARK: - SBUCreateOpenChannelModuleProfileInputDelegate
open func createOpenChannelModule(
_ profileInputComponent: SBUCreateOpenChannelModule.ProfileInput,
shouldChangeChannelName string: String
) {
let trimmedText = string.trimmingCharacters(in: .whitespacesAndNewlines)
let needCreateButtonEnabled = !trimmedText.isEmpty
self.headerComponent?.enableRightBarButton(needCreateButtonEnabled)
}
open func createOpenChannelModuleDidSelectChannelImage(
_ profileInputComponent: SBUCreateOpenChannelModule.ProfileInput,
needRemoveItem: Bool
) {
self.selectChannelImage(needRemoveItem: needRemoveItem)
}
// MARK: - SBUCommonViewModelDelegate
open func shouldUpdateLoadingState(_ isLoading: Bool) {
self.showLoading(isLoading)
}
open func didReceiveError(_ error: SBError?, isBlocker: Bool) {
self.showLoading(false)
self.errorHandler(error?.localizedDescription)
}
// MARK: - SBUCreateOpenChannelViewModelDelegate
open func createOpenChannelViewModel(
_ viewModel: SBUCreateOpenChannelViewModel,
didCreateChannel channel: BaseChannel?
) {
guard let channelURL = channel?.channelURL else {
SBULog.error("[Failed] Create channel request: There is no channel url.")
return
}
SendbirdUI.moveToChannel(channelURL: channelURL, channelType: .open)
}
// MARK: SBUActionSheetDelegate
open func didSelectActionSheetItem(index: Int, identifier: Int) {
if identifier == actionSheetIdPicker {
let type = MediaResourceType.init(rawValue: index) ?? .unknown
self.showChannelImagePicker(with: type)
}
}
open func didDismissActionSheet() { }
// MARK: - SBUAlertViewDelegate
open func didDismissAlertView() { }
// MARK: - SBUSelectablePhotoViewDelegate
open func didTapSendImageData(_ data: Data, fileName: String? = nil, mimeType: String? = nil) {
guard let image = UIImage(data: data) else { return }
self.profileInputComponent?.updateChannelImage(image)
}
// MARK: - UIImagePickerViewControllerDelegate
open func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
picker.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
guard let originalImage = info[.originalImage] as? UIImage else { return }
self.profileInputComponent?.updateChannelImage(originalImage)
}
}
// MARK: - PHPickerViewControllerDelegate
/// Override this method to handle the `results` from `PHPickerViewController`.
/// As defaults, it doesn't support multi-selection and live photo.
/// - Important: To use this method, please assign self as delegate to `PHPickerViewController` object.
@available(iOS 14, *)
open func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
results.forEach {
let itemProvider = $0.itemProvider
// image
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
itemProvider.loadItem(forTypeIdentifier: UTType.image.identifier, options: [:]) { _, _ in
if itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] imageItem, _ in
guard let self = self else { return }
guard let originalImage = imageItem as? UIImage else { return }
self.profileInputComponent?.updateChannelImage(originalImage)
}
}
}
}
}
}
}