RoomCreationTableViewController.swift 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. //
  2. // SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
  3. // SPDX-License-Identifier: GPL-3.0-or-later
  4. //
  5. import UIKit
  6. enum RoomCreationSection: Int {
  7. case kRoomNameSection = 0
  8. case kRoomDescriptionSection
  9. case kRoomParticipantsSection
  10. case kRoomVisibilitySection
  11. }
  12. enum RoomVisibilityOption: Int {
  13. case kAllowGuestsOption = 0
  14. case kPasswordProtectionOption
  15. case kOpenConversationOption
  16. case kOpenConversationGuestsOption
  17. }
  18. @objcMembers class RoomCreationTableViewController: UITableViewController,
  19. UINavigationControllerDelegate,
  20. UIImagePickerControllerDelegate,
  21. UITextFieldDelegate,
  22. AvatarEditViewDelegate,
  23. AddParticipantsTableViewControllerDelegate,
  24. EmojiAvatarPickerViewControllerDelegate,
  25. RoomDescriptionTableViewCellDelegate,
  26. TOCropViewControllerDelegate {
  27. var account: TalkAccount
  28. var headerView = AvatarEditView()
  29. var createButton = UIBarButtonItem()
  30. var modifyingView = UIActivityIndicatorView()
  31. var imagePicker: UIImagePickerController?
  32. var participantsSectionHeaderView = HeaderWithButton()
  33. var roomName = ""
  34. var roomDescription = ""
  35. var roomParticipants: [NCUser] = []
  36. var isPublic = false
  37. var roomPassword = ""
  38. var isOpenConversation = false
  39. var isOpenForGuests = false
  40. var selectedAvatarImage: UIImage?
  41. var selectedEmoji: String?
  42. var selectedEmojiBackgroundColor: String?
  43. var selectedEmojiImage: UIImage?
  44. let kRoomNameTextFieldTag = 99
  45. let kPasswordTextFieldTag = 98
  46. var setPasswordAction = UIAlertAction()
  47. var roomCreationGroup = DispatchGroup()
  48. var roomCreationErrors: [String] = []
  49. init(account: TalkAccount) {
  50. self.account = account
  51. self.headerView = AvatarEditView()
  52. super.init(style: .insetGrouped)
  53. self.headerView.delegate = self
  54. self.headerView.scopeButton.isHidden = true
  55. self.headerView.nameLabel.isHidden = true
  56. }
  57. required init?(coder: NSCoder) {
  58. fatalError("init(coder:) has not been implemented")
  59. }
  60. override func viewDidLoad() {
  61. super.viewDidLoad()
  62. self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: NCAppBranding.themeTextColor()]
  63. self.navigationController?.navigationBar.tintColor = NCAppBranding.themeTextColor()
  64. self.navigationController?.navigationBar.barTintColor = NCAppBranding.themeColor()
  65. self.navigationController?.navigationBar.isTranslucent = false
  66. self.navigationItem.title = NSLocalizedString("New conversation", comment: "")
  67. let appearance = UINavigationBarAppearance()
  68. appearance.configureWithOpaqueBackground()
  69. appearance.titleTextAttributes = [.foregroundColor: NCAppBranding.themeTextColor()]
  70. appearance.backgroundColor = NCAppBranding.themeColor()
  71. self.navigationItem.standardAppearance = appearance
  72. self.navigationItem.compactAppearance = appearance
  73. self.navigationItem.scrollEdgeAppearance = appearance
  74. self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancelButtonPressed))
  75. self.navigationItem.leftBarButtonItem?.tintColor = NCAppBranding.themeTextColor()
  76. self.tableView.register(UINib(nibName: kTextInputTableViewCellNibName, bundle: nil), forCellReuseIdentifier: kTextInputCellIdentifier)
  77. self.tableView.register(UINib(nibName: RoomDescriptionTableViewCell.nibName, bundle: nil), forCellReuseIdentifier: RoomDescriptionTableViewCell.identifier)
  78. self.tableView.register(UINib(nibName: kContactsTableCellNibName, bundle: nil), forCellReuseIdentifier: kContactCellIdentifier)
  79. self.tableView.tableHeaderView = self.headerView
  80. self.tableView.keyboardDismissMode = .onDrag
  81. self.participantsSectionHeaderView.button.setTitle(NSLocalizedString("Edit", comment: "Edit a message or room participants"), for: .normal)
  82. self.participantsSectionHeaderView.button.addTarget(self, action: #selector(editParticipantsButtonPressed), for: .touchUpInside)
  83. self.createButton = UIBarButtonItem(title: NSLocalizedString("Create", comment: ""), style: .done, target: self, action: #selector(createRoom))
  84. self.createButton.accessibilityHint = NSLocalizedString("Double tap to end editing profile", comment: "")
  85. self.createButton.isEnabled = false
  86. self.navigationItem.rightBarButtonItem = self.createButton
  87. self.modifyingView.color = NCAppBranding.themeTextColor()
  88. self.headerView.editView.isHidden = !NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityConversationAvatars, forAccountId: self.account.accountId)
  89. // Need to have an explicit size here for the header view
  90. let size = self.headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
  91. self.headerView.frame = CGRect(origin: .zero, size: size)
  92. self.updateHeaderView()
  93. }
  94. func cancelButtonPressed() {
  95. self.dismiss(animated: true, completion: nil)
  96. }
  97. func updateHeaderView() {
  98. if self.selectedAvatarImage != nil {
  99. self.headerView.avatarImageView.image = self.selectedAvatarImage
  100. } else if self.selectedEmojiImage != nil {
  101. self.headerView.avatarImageView.image = self.selectedEmojiImage
  102. } else {
  103. self.headerView.avatarImageView.setGroupAvatar()
  104. }
  105. self.headerView.trashButton.isHidden = self.selectedAvatarImage == nil && self.selectedEmojiImage == nil
  106. }
  107. func getRoomCreationSections() -> [Int] {
  108. var sections = [Int]()
  109. // Room name section
  110. sections.append(RoomCreationSection.kRoomNameSection.rawValue)
  111. // Room description section
  112. if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityRoomDescription, forAccountId: self.account.accountId) {
  113. sections.append(RoomCreationSection.kRoomDescriptionSection.rawValue)
  114. }
  115. // Room participants section
  116. sections.append(RoomCreationSection.kRoomParticipantsSection.rawValue)
  117. // Room visibility section
  118. sections.append(RoomCreationSection.kRoomVisibilitySection.rawValue)
  119. return sections
  120. }
  121. func getRoomVisibilityOptions() -> [Int] {
  122. var options = [Int]()
  123. // Allow guest option
  124. options.append(RoomVisibilityOption.kAllowGuestsOption.rawValue)
  125. // Password protection option
  126. if self.isPublic {
  127. options.append(RoomVisibilityOption.kPasswordProtectionOption.rawValue)
  128. }
  129. // Open conversation option
  130. options.append(RoomVisibilityOption.kOpenConversationOption.rawValue)
  131. // Open conversation for guests option
  132. if self.isOpenConversation {
  133. options.append(RoomVisibilityOption.kOpenConversationGuestsOption.rawValue)
  134. }
  135. return options
  136. }
  137. func showModifyingView() {
  138. modifyingView.startAnimating()
  139. self.headerView.changeButtonState(to: false)
  140. self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: modifyingView)
  141. self.tableView.isUserInteractionEnabled = false
  142. }
  143. func removeModifyingView() {
  144. modifyingView.stopAnimating()
  145. self.headerView.changeButtonState(to: true)
  146. self.navigationItem.rightBarButtonItem = createButton
  147. self.tableView.isUserInteractionEnabled = true
  148. }
  149. func removeSelectedAvatar() {
  150. self.selectedAvatarImage = nil
  151. self.selectedEmoji = nil
  152. self.selectedEmojiBackgroundColor = nil
  153. self.selectedEmojiImage = nil
  154. }
  155. func allowGuestValueChanged(_ sender: Any?) {
  156. if let optionSwitch = sender as? UISwitch {
  157. self.isPublic = optionSwitch.isOn
  158. self.updateVisibilitySection()
  159. }
  160. }
  161. func openConversationValueChanged(_ sender: Any?) {
  162. if let optionSwitch = sender as? UISwitch {
  163. self.isOpenConversation = optionSwitch.isOn
  164. self.updateVisibilitySection()
  165. }
  166. }
  167. func openForGuestsValueChanged(_ sender: Any?) {
  168. if let optionSwitch = sender as? UISwitch {
  169. self.isOpenForGuests = optionSwitch.isOn
  170. self.updateVisibilitySection()
  171. }
  172. }
  173. func updateVisibilitySection() {
  174. let sections = self.getRoomCreationSections()
  175. if let index = sections.firstIndex(of: RoomCreationSection.kRoomVisibilitySection.rawValue) {
  176. self.tableView.reloadSections([index], with: .automatic)
  177. }
  178. }
  179. // MARK: - Room creation
  180. func createRoom() {
  181. self.showModifyingView()
  182. self.roomCreationErrors = []
  183. // Create conversation
  184. let roomType: NCRoomType = self.isPublic ? .public : .group
  185. NCAPIController.sharedInstance().createRoom(forAccount: self.account, withInvite: nil, ofType: roomType, andName: self.roomName) { room, error in
  186. if let error {
  187. NCUtils.log(String(format: "Failed to create room. Error: %@", error.localizedDescription))
  188. self.roomCreationErrors.append(error.localizedDescription)
  189. self.removeModifyingView()
  190. self.presentRoomCreationFailedErrorDialog()
  191. } else if let room {
  192. self.setAdditionalRoomSettings(token: room.token)
  193. }
  194. }
  195. }
  196. func setAdditionalRoomSettings(token: String) {
  197. self.roomCreationGroup = DispatchGroup()
  198. self.roomCreationErrors = []
  199. // Room avatar
  200. if self.selectedAvatarImage != nil {
  201. self.roomCreationGroup.enter()
  202. NCAPIController.sharedInstance().setAvatarForRoomWithToken(token, image: self.selectedAvatarImage, account: self.account) { error in
  203. if let error {
  204. NCUtils.log(String(format: "Failed to set room avatar. Error: %@", error.localizedDescription))
  205. self.roomCreationErrors.append(error.localizedDescription)
  206. }
  207. self.roomCreationGroup.leave()
  208. }
  209. } else if self.selectedEmojiImage != nil {
  210. self.roomCreationGroup.enter()
  211. NCAPIController.sharedInstance().setEmojiAvatarForRoomWithToken(token, withEmoji: self.selectedEmoji, andColor: self.selectedEmojiBackgroundColor, account: self.account) { error in
  212. if let error {
  213. NCUtils.log(String(format: "Failed to set room emoji avatar. Error: %@", error.localizedDescription))
  214. self.roomCreationErrors.append(error.localizedDescription)
  215. }
  216. self.roomCreationGroup.leave()
  217. }
  218. }
  219. // Room description
  220. if !self.roomDescription.isEmpty {
  221. self.roomCreationGroup.enter()
  222. NCAPIController.sharedInstance().setRoomDescription(self.roomDescription, forRoom: token, forAccount: account) { error in
  223. if let error {
  224. NCUtils.log(String(format: "Failed to set room description. Error: %@", error.localizedDescription))
  225. self.roomCreationErrors.append(error.localizedDescription)
  226. }
  227. self.roomCreationGroup.leave()
  228. }
  229. }
  230. // Room participants
  231. for participant in roomParticipants {
  232. self.roomCreationGroup.enter()
  233. NCAPIController.sharedInstance().addParticipant(participant.userId, ofType: participant.source as String?, toRoom: token, for: account) { error in
  234. if let error {
  235. NCUtils.log(String(format: "Failed to add participant. Error: %@", error.localizedDescription))
  236. self.roomCreationErrors.append(error.localizedDescription)
  237. }
  238. self.roomCreationGroup.leave()
  239. }
  240. }
  241. // Room password
  242. if !self.roomPassword.isEmpty {
  243. self.roomCreationGroup.enter()
  244. NCAPIController.sharedInstance().setPassword(self.roomPassword, toRoom: token, for: self.account) { error, _ in
  245. if let error {
  246. NCUtils.log(String(format: "Failed to set room password. Error: %@", error.localizedDescription))
  247. self.roomCreationErrors.append(error.localizedDescription)
  248. }
  249. self.roomCreationGroup.leave()
  250. }
  251. }
  252. // Room listable scope
  253. if self.isOpenConversation {
  254. self.roomCreationGroup.enter()
  255. let listableScope: NCRoomListableScope = self.isOpenForGuests ? .everyone : .regularUsersOnly
  256. NCAPIController.sharedInstance().setListableScope(listableScope, forRoom: token, for: self.account) { error in
  257. if let error {
  258. NCUtils.log(String(format: "Failed to set listable scope. Error: %@", error.localizedDescription))
  259. self.roomCreationErrors.append(error.localizedDescription)
  260. }
  261. self.roomCreationGroup.leave()
  262. }
  263. }
  264. self.roomCreationGroup.notify(queue: .main) {
  265. self.removeModifyingView()
  266. if self.roomCreationErrors.isEmpty {
  267. NotificationCenter.default.post(name: NSNotification.Name.NCRoomCreated, object: self, userInfo: ["token": token])
  268. } else {
  269. self.presentRoomCreationFailedErrorDialog()
  270. }
  271. }
  272. }
  273. func presentRoomCreationFailedErrorDialog() {
  274. let alert = UIAlertController(title: NSLocalizedString("Conversation creation failed", comment: ""),
  275. message: self.roomCreationErrors.joined(separator: "\n"),
  276. preferredStyle: .alert)
  277. alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default))
  278. self.present(alert, animated: true)
  279. }
  280. // MARK: - Room participants
  281. func editParticipantsButtonPressed() {
  282. if let editParticipantsVC = AddParticipantsTableViewController(participants: self.roomParticipants) {
  283. editParticipantsVC.delegate = self
  284. self.present(NCNavigationController(rootViewController: editParticipantsVC), animated: true)
  285. }
  286. }
  287. // MARK: - Room password
  288. func presentRoomPasswordOptions() {
  289. let alertTitle = self.roomPassword.isEmpty ? NSLocalizedString("Set password", comment: "") : NSLocalizedString("Set new password", comment: "")
  290. let passwordDialog = UIAlertController(title: alertTitle, message: nil, preferredStyle: .alert)
  291. passwordDialog.addTextField { [weak self] textField in
  292. guard let self else { return }
  293. textField.tag = self.kPasswordTextFieldTag
  294. textField.placeholder = NSLocalizedString("Password", comment: "")
  295. textField.isSecureTextEntry = true
  296. textField.delegate = self
  297. }
  298. let actionTitle = self.roomPassword.isEmpty ? NSLocalizedString("OK", comment: "") : NSLocalizedString("Change password", comment: "")
  299. self.setPasswordAction = UIAlertAction(title: actionTitle, style: .default) { _ in
  300. self.roomPassword = passwordDialog.textFields?[0].text?.trimmingCharacters(in: .whitespaces) ?? ""
  301. self.updateVisibilitySection()
  302. }
  303. self.setPasswordAction.isEnabled = false
  304. passwordDialog.addAction(self.setPasswordAction)
  305. if !self.roomPassword.isEmpty {
  306. passwordDialog.addAction(UIAlertAction(title: NSLocalizedString("Remove password", comment: ""), style: .destructive, handler: { _ in
  307. self.roomPassword = ""
  308. self.updateVisibilitySection()
  309. }))
  310. }
  311. passwordDialog.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel))
  312. self.present(passwordDialog, animated: true)
  313. }
  314. // MARK: - TableView
  315. override func numberOfSections(in tableView: UITableView) -> Int {
  316. return self.getRoomCreationSections().count
  317. }
  318. override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  319. return 40
  320. }
  321. override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  322. let sections = getRoomCreationSections()
  323. let roomCreationSection = sections[section]
  324. if roomCreationSection == RoomCreationSection.kRoomParticipantsSection.rawValue {
  325. self.participantsSectionHeaderView.label.text = NSLocalizedString("Participants", comment: "").uppercased()
  326. self.participantsSectionHeaderView.button.isHidden = self.roomParticipants.isEmpty
  327. return participantsSectionHeaderView
  328. }
  329. return nil
  330. }
  331. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  332. let sections = getRoomCreationSections()
  333. let roomCreationSection = sections[section]
  334. if roomCreationSection == RoomCreationSection.kRoomParticipantsSection.rawValue {
  335. return self.roomParticipants.isEmpty ? 1 : self.roomParticipants.count
  336. } else if roomCreationSection == RoomCreationSection.kRoomVisibilitySection.rawValue {
  337. return self.getRoomVisibilityOptions().count
  338. }
  339. return 1
  340. }
  341. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  342. let sections = getRoomCreationSections()
  343. let roomCreationSection = sections[indexPath.section]
  344. if roomCreationSection == RoomCreationSection.kRoomNameSection.rawValue {
  345. let textInputCell = tableView.dequeueReusableCell(withIdentifier: kTextInputCellIdentifier) as? TextInputTableViewCell ??
  346. TextInputTableViewCell(style: .default, reuseIdentifier: kTextInputCellIdentifier)
  347. textInputCell.textField.autocapitalizationType = .sentences
  348. textInputCell.textField.tag = kRoomNameTextFieldTag
  349. textInputCell.textField.delegate = self
  350. textInputCell.textField.text = self.roomName
  351. textInputCell.textField.becomeFirstResponder()
  352. textInputCell.selectionStyle = .none
  353. return textInputCell
  354. } else if roomCreationSection == RoomCreationSection.kRoomDescriptionSection.rawValue {
  355. let descriptionCell = tableView.dequeueReusableCell(withIdentifier: RoomDescriptionTableViewCell.identifier) as? RoomDescriptionTableViewCell ??
  356. RoomDescriptionTableViewCell(style: .default, reuseIdentifier: RoomDescriptionTableViewCell.identifier)
  357. descriptionCell.textView?.text = self.roomDescription
  358. descriptionCell.textView?.isEditable = true
  359. descriptionCell.delegate = self
  360. descriptionCell.characterLimit = 500
  361. descriptionCell.selectionStyle = .none
  362. return descriptionCell
  363. } else if roomCreationSection == RoomCreationSection.kRoomParticipantsSection.rawValue {
  364. if self.roomParticipants.isEmpty {
  365. let addParticipantCell = tableView.dequeueReusableCell(withIdentifier: "AddParticipantCellIdentifier") ?? UITableViewCell(style: .default, reuseIdentifier: "AllowGuestsCellIdentifier")
  366. addParticipantCell.textLabel?.text = NSLocalizedString("Add participants", comment: "")
  367. addParticipantCell.imageView?.image = UIImage(systemName: "person.badge.plus")
  368. addParticipantCell.imageView?.tintColor = .secondaryLabel
  369. addParticipantCell.imageView?.contentMode = .scaleAspectFit
  370. return addParticipantCell
  371. } else {
  372. let participant = self.roomParticipants[indexPath.row]
  373. let participantCell = tableView.dequeueReusableCell(withIdentifier: kContactCellIdentifier) as? ContactsTableViewCell ??
  374. ContactsTableViewCell(style: .default, reuseIdentifier: kContactCellIdentifier)
  375. participantCell.labelTitle.text = participant.name
  376. let participantType = participant.source as String
  377. participantCell.contactImage.setActorAvatar(forId: participant.userId, withType: participantType, withDisplayName: participant.name, withRoomToken: nil)
  378. return participantCell
  379. }
  380. } else if roomCreationSection == RoomCreationSection.kRoomVisibilitySection.rawValue {
  381. let options = getRoomVisibilityOptions()
  382. let option = options[indexPath.row]
  383. var roomVisibilityOptionCell = UITableViewCell()
  384. switch option {
  385. case RoomVisibilityOption.kAllowGuestsOption.rawValue:
  386. roomVisibilityOptionCell = tableView.dequeueReusableCell(withIdentifier: "AllowGuestsCellIdentifier") ?? UITableViewCell(style: .default, reuseIdentifier: "AllowGuestsCellIdentifier")
  387. roomVisibilityOptionCell.textLabel?.text = NSLocalizedString("Allow guests", comment: "")
  388. let optionSwicth = UISwitch()
  389. optionSwicth.isOn = self.isPublic
  390. optionSwicth.addTarget(self, action: #selector(allowGuestValueChanged(_:)), for: .valueChanged)
  391. roomVisibilityOptionCell.accessoryView = optionSwicth
  392. roomVisibilityOptionCell.imageView?.image = UIImage(systemName: "link")
  393. case RoomVisibilityOption.kPasswordProtectionOption.rawValue:
  394. roomVisibilityOptionCell = tableView.dequeueReusableCell(withIdentifier: "SetPasswordCellIdentifier") ?? UITableViewCell(style: .default, reuseIdentifier: "SetPasswordCellIdentifier")
  395. roomVisibilityOptionCell.textLabel?.text = self.roomPassword.isEmpty ? NSLocalizedString("Set password", comment: "") : NSLocalizedString("Change password", comment: "")
  396. roomVisibilityOptionCell.imageView?.image = self.roomPassword.isEmpty ? UIImage(systemName: "lock.open") : UIImage(systemName: "lock")
  397. case RoomVisibilityOption.kOpenConversationOption.rawValue:
  398. roomVisibilityOptionCell = tableView.dequeueReusableCell(withIdentifier: "OpenConversationCellIdentifier") ?? UITableViewCell(style: .default, reuseIdentifier: "OpenConversationCellIdentifier")
  399. roomVisibilityOptionCell.textLabel?.text = NSLocalizedString("Open conversation to registered users", comment: "")
  400. let optionSwicth = UISwitch()
  401. optionSwicth.isOn = self.isOpenConversation
  402. optionSwicth.addTarget(self, action: #selector(openConversationValueChanged(_:)), for: .valueChanged)
  403. roomVisibilityOptionCell.accessoryView = optionSwicth
  404. roomVisibilityOptionCell.imageView?.image = UIImage(systemName: "list.bullet")
  405. case RoomVisibilityOption.kOpenConversationGuestsOption.rawValue:
  406. roomVisibilityOptionCell = tableView.dequeueReusableCell(withIdentifier: "OpenConversationGuestsCellIdentifier") ?? UITableViewCell(style: .default, reuseIdentifier: "OpenConversationGuestsCellIdentifier")
  407. roomVisibilityOptionCell.textLabel?.text = NSLocalizedString("Also open to guest app users", comment: "")
  408. let optionSwicth = UISwitch()
  409. optionSwicth.isOn = self.isOpenForGuests
  410. optionSwicth.addTarget(self, action: #selector(openForGuestsValueChanged(_:)), for: .valueChanged)
  411. roomVisibilityOptionCell.accessoryView = optionSwicth
  412. roomVisibilityOptionCell.imageView?.image = UIImage(systemName: "list.bullet")
  413. roomVisibilityOptionCell.imageView?.isHidden = true
  414. default:
  415. break
  416. }
  417. roomVisibilityOptionCell.selectionStyle = .none
  418. roomVisibilityOptionCell.imageView?.tintColor = .secondaryLabel
  419. roomVisibilityOptionCell.imageView?.contentMode = .scaleAspectFit
  420. roomVisibilityOptionCell.textLabel?.numberOfLines = 0
  421. return roomVisibilityOptionCell
  422. }
  423. return UITableViewCell()
  424. }
  425. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  426. let sections = getRoomCreationSections()
  427. let roomCreationSection = sections[section]
  428. if roomCreationSection == RoomCreationSection.kRoomNameSection.rawValue {
  429. return NSLocalizedString("Name", comment: "")
  430. } else if roomCreationSection == RoomCreationSection.kRoomDescriptionSection.rawValue {
  431. return NSLocalizedString("Description", comment: "")
  432. } else if roomCreationSection == RoomCreationSection.kRoomVisibilitySection.rawValue {
  433. return NSLocalizedString("Visibility", comment: "Conversation visibility settings")
  434. }
  435. return nil
  436. }
  437. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  438. let sections = getRoomCreationSections()
  439. let roomCreationSection = sections[indexPath.section]
  440. if roomCreationSection == RoomCreationSection.kRoomParticipantsSection.rawValue {
  441. if self.roomParticipants.isEmpty {
  442. self.editParticipantsButtonPressed()
  443. }
  444. } else if roomCreationSection == RoomCreationSection.kRoomVisibilitySection.rawValue {
  445. let options = getRoomVisibilityOptions()
  446. let option = options[indexPath.row]
  447. if option == RoomVisibilityOption.kPasswordProtectionOption.rawValue {
  448. self.presentRoomPasswordOptions()
  449. }
  450. }
  451. tableView.deselectRow(at: indexPath, animated: true)
  452. }
  453. // MARK: - AddParticipantsTableViewController Delegate
  454. func addParticipantsTableViewController(_ viewController: AddParticipantsTableViewController!, wantsToAdd participants: [NCUser]!) {
  455. self.roomParticipants = participants
  456. let sections = self.getRoomCreationSections()
  457. if let index = sections.firstIndex(of: RoomCreationSection.kRoomParticipantsSection.rawValue) {
  458. self.tableView.reloadSections([index], with: .automatic)
  459. }
  460. }
  461. // MARK: - Present camera/photo library
  462. func checkAndPresentCamera() {
  463. // https://stackoverflow.com/a/20464727/2512312
  464. let mediaType: String = AVMediaType.video.rawValue
  465. let authStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType(mediaType))
  466. if authStatus == AVAuthorizationStatus.authorized {
  467. self.presentCamera()
  468. return
  469. } else if authStatus == AVAuthorizationStatus.notDetermined {
  470. AVCaptureDevice.requestAccess(for: AVMediaType(mediaType)) { granted in
  471. if granted {
  472. self.presentCamera()
  473. }
  474. }
  475. return
  476. }
  477. let alertTitle = NSLocalizedString("Could not access camera", comment: "")
  478. let alertMessage = NSLocalizedString("Camera access is not allowed. Check your settings.", comment: "")
  479. NCUserInterfaceController.sharedInstance().presentAlert(withTitle: alertTitle, withMessage: alertMessage)
  480. }
  481. func presentCamera() {
  482. DispatchQueue.main.async {
  483. self.imagePicker = UIImagePickerController()
  484. if let imagePicker = self.imagePicker {
  485. imagePicker.sourceType = .camera
  486. imagePicker.delegate = self
  487. self.present(imagePicker, animated: true)
  488. }
  489. }
  490. }
  491. func presentPhotoLibrary() {
  492. DispatchQueue.main.async {
  493. self.imagePicker = UIImagePickerController()
  494. if let imagePicker = self.imagePicker {
  495. imagePicker.sourceType = .photoLibrary
  496. imagePicker.delegate = self
  497. self.present(imagePicker, animated: true)
  498. }
  499. }
  500. }
  501. func presentEmojiAvatarPicker() {
  502. DispatchQueue.main.async {
  503. let emojiAvatarPickerVC = EmojiAvatarPickerViewController()
  504. emojiAvatarPickerVC.delegate = self
  505. let emojiAvatarPickerNC = UINavigationController(rootViewController: emojiAvatarPickerVC)
  506. self.present(emojiAvatarPickerNC, animated: true, completion: nil)
  507. }
  508. }
  509. // MARK: - UIImagePickerController Delegate
  510. func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
  511. let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String
  512. if mediaType == "public.image" {
  513. let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
  514. self.dismiss(animated: true) {
  515. if let image = image {
  516. let cropViewController = TOCropViewController(croppingStyle: TOCropViewCroppingStyle.circular, image: image)
  517. cropViewController.delegate = self
  518. self.present(cropViewController, animated: true)
  519. }
  520. }
  521. }
  522. }
  523. func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
  524. self.dismiss(animated: true, completion: nil)
  525. }
  526. // MARK: - RoomDescriptionTableViewCellDelegate
  527. func roomDescriptionCellTextViewDidChange(_ cell: RoomDescriptionTableViewCell) {
  528. DispatchQueue.main.async {
  529. self.tableView?.beginUpdates()
  530. self.tableView?.endUpdates()
  531. self.roomDescription = cell.textView?.text ?? ""
  532. }
  533. }
  534. func roomDescriptionCellDidEndEditing(_ cell: RoomDescriptionTableViewCell) {
  535. self.roomDescription = cell.textView?.text ?? ""
  536. }
  537. func roomDescriptionCellDidExceedLimit(_ cell: RoomDescriptionTableViewCell) {
  538. NotificationPresenter.shared().present(
  539. text: NSLocalizedString("Description cannot be longer than 500 characters", comment: ""),
  540. dismissAfterDelay: 3.0,
  541. includedStyle: .warning)
  542. }
  543. // MARK: - TOCROPViewControllerDelegate
  544. func cropViewController(_ cropViewController: TOCropViewController, didCropTo image: UIImage, with cropRect: CGRect, angle: Int) {
  545. // Fixes weird iOS 13 bug: https://github.com/TimOliver/TOCropViewController/issues/365
  546. cropViewController.transitioningDelegate = nil
  547. cropViewController.dismiss(animated: true) {
  548. self.removeSelectedAvatar()
  549. self.selectedAvatarImage = image
  550. self.updateHeaderView()
  551. }
  552. }
  553. func cropViewController(_ cropViewController: TOCropViewController, didFinishCancelled cancelled: Bool) {
  554. // Fixes weird iOS 13 bug: https://github.com/TimOliver/TOCropViewController/issues/365
  555. cropViewController.transitioningDelegate = nil
  556. cropViewController.dismiss(animated: true)
  557. }
  558. // MARK: - AvaterEditViewDelegate
  559. func avatarEditViewPresentCamera(_ controller: AvatarEditView?) {
  560. self.checkAndPresentCamera()
  561. }
  562. func avatarEditViewPresentPhotoLibrary(_ controller: AvatarEditView?) {
  563. self.presentPhotoLibrary()
  564. }
  565. func avatarEditViewPresentEmojiAvatarPicker(_ controller: AvatarEditView?) {
  566. self.presentEmojiAvatarPicker()
  567. }
  568. func avatarEditViewRemoveAvatar(_ controller: AvatarEditView?) {
  569. self.removeSelectedAvatar()
  570. self.updateHeaderView()
  571. }
  572. // MARK: - EmojiAvatarPickerViewControllerDelegate
  573. func didSelectEmoji(emoji: NSString, color: NSString, image: UIImage) {
  574. self.removeSelectedAvatar()
  575. self.selectedEmoji = emoji as String
  576. self.selectedEmojiBackgroundColor = color as String
  577. self.selectedEmojiImage = image
  578. self.updateHeaderView()
  579. }
  580. // MARK: - UITextField delegate
  581. func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  582. textField.resignFirstResponder()
  583. return true
  584. }
  585. func textFieldShouldClear(_ textField: UITextField) -> Bool {
  586. if textField.tag == kRoomNameTextFieldTag {
  587. textField.resignFirstResponder()
  588. textField.becomeFirstResponder()
  589. self.roomName = ""
  590. self.createButton.isEnabled = false
  591. }
  592. return true
  593. }
  594. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  595. let currentText = textField.text ?? ""
  596. guard let stringRange = Range(range, in: currentText) else { return false }
  597. let updatedText = currentText.replacingCharacters(in: stringRange, with: string).trimmingCharacters(in: .whitespaces)
  598. if textField.tag == kRoomNameTextFieldTag {
  599. self.roomName = updatedText
  600. self.createButton.isEnabled = !updatedText.isEmpty
  601. } else if textField.tag == kPasswordTextFieldTag {
  602. let hasAllowedLength = updatedText.count <= 200
  603. self.setPasswordAction.isEnabled = hasAllowedLength && !updatedText.isEmpty
  604. return hasAllowedLength
  605. }
  606. return true
  607. }
  608. func textFieldDidEndEditing(_ textField: UITextField) {
  609. if textField.tag == kRoomNameTextFieldTag, let text = textField.text {
  610. let roomName = text.trimmingCharacters(in: CharacterSet.whitespaces)
  611. self.roomName = roomName
  612. self.createButton.isEnabled = !roomName.isEmpty
  613. }
  614. }
  615. }