mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			535 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			535 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import UIKit
 | |
| import SessionMessagingKit
 | |
| import SessionUIKit
 | |
| 
 | |
| protocol SwipeActionOptimisticCell {
 | |
|     func optimisticUpdate(isMuted: Bool?, isBlocked: Bool?, isPinned: Bool?, hasUnread: Bool?)
 | |
| }
 | |
| 
 | |
| extension SwipeActionOptimisticCell {
 | |
|     public func optimisticUpdate(isMuted: Bool) {
 | |
|         optimisticUpdate(isMuted: isMuted, isBlocked: nil, isPinned: nil, hasUnread: nil)
 | |
|     }
 | |
|     
 | |
|     public func optimisticUpdate(isBlocked: Bool) {
 | |
|         optimisticUpdate(isMuted: nil, isBlocked: isBlocked, isPinned: nil, hasUnread: nil)
 | |
|     }
 | |
|     
 | |
|     public func optimisticUpdate(isPinned: Bool) {
 | |
|         optimisticUpdate(isMuted: nil, isBlocked: nil, isPinned: isPinned, hasUnread: nil)
 | |
|     }
 | |
|     
 | |
|     public func optimisticUpdate(hasUnread: Bool) {
 | |
|         optimisticUpdate(isMuted: nil, isBlocked: nil, isPinned: nil, hasUnread: hasUnread)
 | |
|     }
 | |
| }
 | |
| 
 | |
| public extension UIContextualAction {
 | |
|     enum SwipeAction {
 | |
|         case toggleReadStatus
 | |
|         case hide
 | |
|         case pin
 | |
|         case mute
 | |
|         case block
 | |
|         case leave
 | |
|         case delete
 | |
|     }
 | |
|     
 | |
|     static func configuration(for actions: [UIContextualAction]?) -> UISwipeActionsConfiguration? {
 | |
|         return actions.map { UISwipeActionsConfiguration(actions: $0) }
 | |
|     }
 | |
|     
 | |
|     static func generateSwipeActions(
 | |
|         _ actions: [SwipeAction],
 | |
|         for side: UIContextualAction.Side,
 | |
|         indexPath: IndexPath,
 | |
|         tableView: UITableView,
 | |
|         threadViewModel: SessionThreadViewModel,
 | |
|         viewController: UIViewController?
 | |
|     ) -> [UIContextualAction]? {
 | |
|         guard !actions.isEmpty else { return nil }
 | |
|         
 | |
|         let unswipeAnimationDelay: DispatchTimeInterval = .milliseconds(500)
 | |
|         
 | |
|         // Note: for some reason the `UISwipeActionsConfiguration` expects actions to be left-to-right
 | |
|         // for leading actions, but right-to-left for trailing actions...
 | |
|         let targetActions: [SwipeAction] = (side == .trailing ? actions.reversed() : actions)
 | |
|         let actionBackgroundColor: [ThemeValue] = [
 | |
|             .conversationButton_swipeDestructive,
 | |
|             .conversationButton_swipeSecondary,
 | |
|             .conversationButton_swipeTertiary
 | |
|         ]
 | |
|         
 | |
|         return targetActions
 | |
|             .enumerated()
 | |
|             .map { index, action -> UIContextualAction in
 | |
|                 // Even though we have to reverse the actions above, the indexes in the view hierarchy
 | |
|                 // are in the expected order
 | |
|                 let targetIndex: Int = (side == .trailing ? (targetActions.count - index) : index)
 | |
|                 let themeBackgroundColor: ThemeValue = actionBackgroundColor[
 | |
|                     index % actionBackgroundColor.count
 | |
|                 ]
 | |
|                 
 | |
|                 switch action {
 | |
|                     // MARK: -- toggleReadStatus
 | |
|                         
 | |
|                     case .toggleReadStatus:
 | |
|                         let isUnread: Bool = (
 | |
|                             threadViewModel.threadWasMarkedUnread == true ||
 | |
|                             (threadViewModel.threadUnreadCount ?? 0) > 0
 | |
|                         )
 | |
|                         
 | |
|                         return UIContextualAction(
 | |
|                             title: (isUnread ?
 | |
|                                 "MARK_AS_READ".localized() :
 | |
|                                 "MARK_AS_UNREAD".localized()
 | |
|                             ),
 | |
|                             icon: (isUnread ?
 | |
|                                 UIImage(systemName: "envelope.open") :
 | |
|                                 UIImage(systemName: "envelope.badge")
 | |
|                             ),
 | |
|                             themeTintColor: .white,
 | |
|                             themeBackgroundColor: .conversationButton_swipeRead,    // Always Custom
 | |
|                             side: side,
 | |
|                             actionIndex: targetIndex,
 | |
|                             indexPath: indexPath,
 | |
|                             tableView: tableView
 | |
|                         ) { _, _, completionHandler  in
 | |
|                             // Delay the change to give the cell "unswipe" animation some time to complete
 | |
|                             DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) {
 | |
|                                 switch isUnread {
 | |
|                                     case true: threadViewModel.markAsRead(
 | |
|                                         target: .threadAndInteractions(
 | |
|                                             interactionsBeforeInclusive: threadViewModel.interactionId
 | |
|                                         )
 | |
|                                     )
 | |
|                                         
 | |
|                                     case false: threadViewModel.markAsUnread()
 | |
|                                 }
 | |
|                             }
 | |
|                             completionHandler(true)
 | |
|                         }
 | |
|                         
 | |
|                     // MARK: -- hide
 | |
|                         
 | |
|                     case .hide:
 | |
|                         return UIContextualAction(
 | |
|                             title: "TXT_HIDE_TITLE".localized(),
 | |
|                             icon: UIImage(systemName: "eye.slash"),
 | |
|                             themeTintColor: .white,
 | |
|                             themeBackgroundColor: themeBackgroundColor,
 | |
|                             side: side,
 | |
|                             actionIndex: targetIndex,
 | |
|                             indexPath: indexPath,
 | |
|                             tableView: tableView
 | |
|                         ) { _, _, completionHandler  in
 | |
|                             switch threadViewModel.threadId {
 | |
|                                 case SessionThreadViewModel.messageRequestsSectionId:
 | |
|                                     Storage.shared.write { db in db[.hasHiddenMessageRequests] = true }
 | |
|                                     completionHandler(true)
 | |
|                                     
 | |
|                                 default:
 | |
|                                     let confirmationModalExplanation: NSAttributedString = {
 | |
|                                         let message = String(
 | |
|                                             format: "hide_note_to_self_confirmation_alert_message".localized(),
 | |
|                                             threadViewModel.displayName
 | |
|                                         )
 | |
|                                         
 | |
|                                         return NSAttributedString(string: message)
 | |
|                                             .adding(
 | |
|                                                 attributes: [
 | |
|                                                     .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize)
 | |
|                                                 ],
 | |
|                                                 range: (message as NSString).range(of: threadViewModel.displayName)
 | |
|                                             )
 | |
|                                     }()
 | |
|                                     
 | |
|                                     let confirmationModal: ConfirmationModal = ConfirmationModal(
 | |
|                                         info: ConfirmationModal.Info(
 | |
|                                             title: "hide_note_to_self_confirmation_alert_title".localized(),
 | |
|                                             body: .attributedText(confirmationModalExplanation),
 | |
|                                             confirmTitle: "TXT_HIDE_TITLE".localized(),
 | |
|                                             confirmAccessibility: Accessibility(
 | |
|                                                 identifier: "Hide"
 | |
|                                             ),
 | |
|                                             confirmStyle: .danger,
 | |
|                                             cancelStyle: .alert_text,
 | |
|                                             dismissOnConfirm: true,
 | |
|                                             onConfirm: { _ in
 | |
|                                                 Storage.shared.writeAsync { db in
 | |
|                                                     try SessionThread.deleteOrLeave(
 | |
|                                                         db,
 | |
|                                                         threadId: threadViewModel.threadId,
 | |
|                                                         threadVariant: threadViewModel.threadVariant,
 | |
|                                                         groupLeaveType: .forced,
 | |
|                                                         calledFromConfigHandling: false
 | |
|                                                     )
 | |
|                                                 }
 | |
|                                                 viewController?.dismiss(animated: true, completion: nil)
 | |
|                                                 
 | |
|                                                 completionHandler(true)
 | |
|                                             },
 | |
|                                             afterClosed: { completionHandler(false) }
 | |
|                                         )
 | |
|                                     )
 | |
|                                     
 | |
|                                     viewController?.present(confirmationModal, animated: true, completion: nil)
 | |
|                             }
 | |
|                         }
 | |
|                         
 | |
|                     // MARK: -- pin
 | |
|                         
 | |
|                     case .pin:
 | |
|                         return UIContextualAction(
 | |
|                             title: (threadViewModel.threadPinnedPriority > 0 ?
 | |
|                                 "UNPIN_BUTTON_TEXT".localized() :
 | |
|                                 "PIN_BUTTON_TEXT".localized()
 | |
|                             ),
 | |
|                             icon: (threadViewModel.threadPinnedPriority > 0 ?
 | |
|                                 UIImage(systemName: "pin.slash") :
 | |
|                                 UIImage(systemName: "pin")
 | |
|                             ),
 | |
|                             themeTintColor: .white,
 | |
|                             themeBackgroundColor: .conversationButton_swipeTertiary,    // Always Tertiary
 | |
|                             side: side,
 | |
|                             actionIndex: targetIndex,
 | |
|                             indexPath: indexPath,
 | |
|                             tableView: tableView
 | |
|                         ) { _, _, completionHandler in
 | |
|                             (tableView.cellForRow(at: indexPath) as? SwipeActionOptimisticCell)?
 | |
|                                 .optimisticUpdate(
 | |
|                                     isPinned: !(threadViewModel.threadPinnedPriority > 0)
 | |
|                                 )
 | |
|                             completionHandler(true)
 | |
|                             
 | |
|                             // Delay the change to give the cell "unswipe" animation some time to complete
 | |
|                             DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) {
 | |
|                                 Storage.shared.writeAsync { db in
 | |
|                                     try SessionThread
 | |
|                                         .filter(id: threadViewModel.threadId)
 | |
|                                         .updateAllAndConfig(
 | |
|                                             db,
 | |
|                                             SessionThread.Columns.pinnedPriority
 | |
|                                                 .set(to: (threadViewModel.threadPinnedPriority == 0 ? 1 : 0))
 | |
|                                         )
 | |
|                                 }
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                     // MARK: -- mute
 | |
| 
 | |
|                     case .mute:
 | |
|                         return UIContextualAction(
 | |
|                             title: (threadViewModel.threadMutedUntilTimestamp == nil ?
 | |
|                                 "mute_button_text".localized() :
 | |
|                                 "unmute_button_text".localized()
 | |
|                             ),
 | |
|                             icon: (threadViewModel.threadMutedUntilTimestamp == nil ?
 | |
|                                 UIImage(systemName: "speaker.slash") :
 | |
|                                 UIImage(systemName: "speaker")
 | |
|                             ),
 | |
|                             iconHeight: Values.mediumFontSize,
 | |
|                             themeTintColor: .white,
 | |
|                             themeBackgroundColor: themeBackgroundColor,
 | |
|                             side: side,
 | |
|                             actionIndex: targetIndex,
 | |
|                             indexPath: indexPath,
 | |
|                             tableView: tableView
 | |
|                         ) { _, _, completionHandler in
 | |
|                             (tableView.cellForRow(at: indexPath) as? SwipeActionOptimisticCell)?
 | |
|                                 .optimisticUpdate(
 | |
|                                     isMuted: !(threadViewModel.threadMutedUntilTimestamp != nil)
 | |
|                                 )
 | |
|                             completionHandler(true)
 | |
|                             
 | |
|                             // Delay the change to give the cell "unswipe" animation some time to complete
 | |
|                             DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) {
 | |
|                                 Storage.shared.writeAsync { db in
 | |
|                                     let currentValue: TimeInterval? = try SessionThread
 | |
|                                         .filter(id: threadViewModel.threadId)
 | |
|                                         .select(.mutedUntilTimestamp)
 | |
|                                         .asRequest(of: TimeInterval.self)
 | |
|                                         .fetchOne(db)
 | |
|                                     
 | |
|                                     try SessionThread
 | |
|                                         .filter(id: threadViewModel.threadId)
 | |
|                                         .updateAll(
 | |
|                                             db,
 | |
|                                             SessionThread.Columns.mutedUntilTimestamp.set(
 | |
|                                                 to: (currentValue == nil ?
 | |
|                                                     Date.distantFuture.timeIntervalSince1970 :
 | |
|                                                     nil
 | |
|                                                 )
 | |
|                                             )
 | |
|                                         )
 | |
|                                 }
 | |
|                             }
 | |
|                         }
 | |
|                         
 | |
|                     // MARK: -- block
 | |
|                         
 | |
|                     case .block:
 | |
|                         return UIContextualAction(
 | |
|                             title: (threadViewModel.threadIsBlocked == true ?
 | |
|                                 "BLOCK_LIST_UNBLOCK_BUTTON".localized() :
 | |
|                                 "BLOCK_LIST_BLOCK_BUTTON".localized()
 | |
|                             ),
 | |
|                             icon: UIImage(named: "table_ic_block"),
 | |
|                             iconHeight: Values.mediumFontSize,
 | |
|                             themeTintColor: .white,
 | |
|                             themeBackgroundColor: themeBackgroundColor,
 | |
|                             side: side,
 | |
|                             actionIndex: targetIndex,
 | |
|                             indexPath: indexPath,
 | |
|                             tableView: tableView
 | |
|                         ) { [weak viewController] _, _, completionHandler in
 | |
|                             let threadIsBlocked: Bool = (threadViewModel.threadIsBlocked == true)
 | |
|                             let threadIsMessageRequest: Bool = (threadViewModel.threadIsMessageRequest == true)
 | |
|                             let contactChanges: [ConfigColumnAssignment] = [
 | |
|                                 Contact.Columns.isBlocked.set(to: !threadIsBlocked),
 | |
|                                 
 | |
|                                 /// **Note:** We set `didApproveMe` to `true` so the current user will be able to send a
 | |
|                                 /// message to the person who originally sent them the message request in the future if they
 | |
|                                 /// unblock them
 | |
|                                 (!threadIsMessageRequest ? nil : Contact.Columns.didApproveMe.set(to: true)),
 | |
|                                 (!threadIsMessageRequest ? nil : Contact.Columns.isApproved.set(to: false))
 | |
|                             ].compactMap { $0 }
 | |
|                             
 | |
|                             let performBlock: (UIViewController?) -> () = { viewController in
 | |
|                                 (tableView.cellForRow(at: indexPath) as? SwipeActionOptimisticCell)?
 | |
|                                     .optimisticUpdate(
 | |
|                                         isBlocked: !threadIsBlocked
 | |
|                                     )
 | |
|                                 viewController?.dismiss(animated: true, completion: nil)
 | |
|                                 completionHandler(true)
 | |
|                                 
 | |
|                                 // Delay the change to give the cell "unswipe" animation some time to complete
 | |
|                                 DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) {
 | |
|                                     Storage.shared
 | |
|                                         .writePublisher { db in
 | |
|                                             // Create the contact if it doesn't exist
 | |
|                                             try Contact
 | |
|                                                 .fetchOrCreate(db, id: threadViewModel.threadId)
 | |
|                                                 .save(db)
 | |
|                                             try Contact
 | |
|                                                 .filter(id: threadViewModel.threadId)
 | |
|                                                 .updateAllAndConfig(db, contactChanges)
 | |
|                                             
 | |
|                                             // Blocked message requests should be deleted
 | |
|                                             if threadIsMessageRequest {
 | |
|                                                 try SessionThread.deleteOrLeave(
 | |
|                                                     db,
 | |
|                                                     threadId: threadViewModel.threadId,
 | |
|                                                     threadVariant: .contact,
 | |
|                                                     groupLeaveType: .silent,
 | |
|                                                     calledFromConfigHandling: false
 | |
|                                                 )
 | |
|                                             }
 | |
|                                         }
 | |
|                                         .subscribe(on: DispatchQueue.global(qos: .userInitiated))
 | |
|                                         .sinkUntilComplete()
 | |
|                                 }
 | |
|                             }
 | |
|                                 
 | |
|                             switch threadIsMessageRequest {
 | |
|                                 case false: performBlock(nil)
 | |
|                                 case true:
 | |
|                                     let confirmationModal: ConfirmationModal = ConfirmationModal(
 | |
|                                         info: ConfirmationModal.Info(
 | |
|                                             title: "MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON".localized(),
 | |
|                                             confirmTitle: "BLOCK_LIST_BLOCK_BUTTON".localized(),
 | |
|                                             confirmAccessibility: Accessibility(
 | |
|                                                 identifier: "Block"
 | |
|                                             ),
 | |
|                                             confirmStyle: .danger,
 | |
|                                             cancelStyle: .alert_text,
 | |
|                                             dismissOnConfirm: true,
 | |
|                                             onConfirm: { _ in
 | |
|                                                 performBlock(viewController)
 | |
|                                             },
 | |
|                                             afterClosed: { completionHandler(false) }
 | |
|                                         )
 | |
|                                     )
 | |
|                                     
 | |
|                                     viewController?.present(confirmationModal, animated: true, completion: nil)
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                     // MARK: -- leave
 | |
| 
 | |
|                     case .leave:
 | |
|                         return UIContextualAction(
 | |
|                             title: "LEAVE_BUTTON_TITLE".localized(),
 | |
|                             icon: UIImage(systemName: "rectangle.portrait.and.arrow.right"),
 | |
|                             iconHeight: Values.mediumFontSize,
 | |
|                             themeTintColor: .white,
 | |
|                             themeBackgroundColor: themeBackgroundColor,
 | |
|                             side: side,
 | |
|                             actionIndex: targetIndex,
 | |
|                             indexPath: indexPath,
 | |
|                             tableView: tableView
 | |
|                         ) { [weak viewController] _, _, completionHandler in
 | |
|                             let confirmationModalTitle: String = {
 | |
|                                 switch threadViewModel.threadVariant {
 | |
|                                     case .legacyGroup, .group:
 | |
|                                         return "leave_group_confirmation_alert_title".localized()
 | |
|                                         
 | |
|                                     default: return "leave_community_confirmation_alert_title".localized()
 | |
|                                 }
 | |
|                             }()
 | |
|                             
 | |
|                             let confirmationModalExplanation: NSAttributedString = {
 | |
|                                 if threadViewModel.currentUserIsClosedGroupAdmin == true {
 | |
|                                     return NSAttributedString(string: "admin_group_leave_warning".localized())
 | |
|                                 }
 | |
|                                 
 | |
|                                 let mutableAttributedString = NSMutableAttributedString(
 | |
|                                     string: String(
 | |
|                                         format: "leave_community_confirmation_alert_message".localized(),
 | |
|                                         threadViewModel.displayName
 | |
|                                     )
 | |
|                                 )
 | |
|                                 mutableAttributedString.addAttribute(
 | |
|                                     .font,
 | |
|                                     value: UIFont.boldSystemFont(ofSize: Values.smallFontSize),
 | |
|                                     range: (mutableAttributedString.string as NSString).range(of: threadViewModel.displayName)
 | |
|                                 )
 | |
|                                 return mutableAttributedString
 | |
|                             }()
 | |
|                             
 | |
|                             let confirmationModal: ConfirmationModal = ConfirmationModal(
 | |
|                                 info: ConfirmationModal.Info(
 | |
|                                     title: confirmationModalTitle,
 | |
|                                     body: .attributedText(confirmationModalExplanation),
 | |
|                                     confirmTitle: "LEAVE_BUTTON_TITLE".localized(),
 | |
|                                     confirmAccessibility: Accessibility(
 | |
|                                         identifier: "Leave"
 | |
|                                     ),
 | |
|                                     confirmStyle: .danger,
 | |
|                                     cancelStyle: .alert_text,
 | |
|                                     dismissOnConfirm: true,
 | |
|                                     onConfirm: { _ in
 | |
|                                         Storage.shared.writeAsync { db in
 | |
|                                             try SessionThread.deleteOrLeave(
 | |
|                                                 db,
 | |
|                                                 threadId: threadViewModel.threadId,
 | |
|                                                 threadVariant: threadViewModel.threadVariant,
 | |
|                                                 groupLeaveType: .standard,
 | |
|                                                 calledFromConfigHandling: false
 | |
|                                             )
 | |
|                                         }
 | |
|                                         viewController?.dismiss(animated: true, completion: nil)
 | |
|                                         
 | |
|                                         completionHandler(true)
 | |
|                                     },
 | |
|                                     afterClosed: { completionHandler(false) }
 | |
|                                 )
 | |
|                             )
 | |
|                             
 | |
|                             viewController?.present(confirmationModal, animated: true, completion: nil)
 | |
|                         }
 | |
|                         
 | |
|                     // MARK: -- delete
 | |
|                         
 | |
|                     case .delete:
 | |
|                         return UIContextualAction(
 | |
|                             title: "TXT_DELETE_TITLE".localized(),
 | |
|                             icon: UIImage(named: "icon_bin"),
 | |
|                             iconHeight: Values.mediumFontSize,
 | |
|                             themeTintColor: .white,
 | |
|                             themeBackgroundColor: themeBackgroundColor,
 | |
|                             side: side,
 | |
|                             actionIndex: targetIndex,
 | |
|                             indexPath: indexPath,
 | |
|                             tableView: tableView
 | |
|                         ) { [weak viewController] _, _, completionHandler in
 | |
|                             let isMessageRequest: Bool = (threadViewModel.threadIsMessageRequest == true)
 | |
|                             let confirmationModalTitle: String = {
 | |
|                                 switch (threadViewModel.threadVariant, isMessageRequest) {
 | |
|                                     case (_, true): return "TXT_DELETE_TITLE".localized()
 | |
|                                     case (.contact, _):
 | |
|                                         return "delete_conversation_confirmation_alert_title".localized()
 | |
|                                         
 | |
|                                     case (.legacyGroup, _), (.group, _):
 | |
|                                         return "delete_group_confirmation_alert_title".localized()
 | |
|                                         
 | |
|                                     case (.community, _): return "TXT_DELETE_TITLE".localized()
 | |
|                                 }
 | |
|                             }()
 | |
|                             let confirmationModalExplanation: NSAttributedString = {
 | |
|                                 guard !isMessageRequest else {
 | |
|                                     return NSAttributedString(
 | |
|                                         string: "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON".localized()
 | |
|                                     )
 | |
|                                 }
 | |
|                                 guard threadViewModel.currentUserIsClosedGroupAdmin == false else {
 | |
|                                     return NSAttributedString(
 | |
|                                         string: "admin_group_leave_warning".localized()
 | |
|                                     )
 | |
|                                 }
 | |
|                                 
 | |
|                                 let message = String(
 | |
|                                     format: {
 | |
|                                         switch threadViewModel.threadVariant {
 | |
|                                             case .contact:
 | |
|                                                 return
 | |
|                                                     "delete_conversation_confirmation_alert_message".localized()
 | |
|                                                 
 | |
|                                             case .legacyGroup, .group:
 | |
|                                                 return
 | |
|                                                     "delete_group_confirmation_alert_message".localized()
 | |
|                                                 
 | |
|                                             case .community:
 | |
|                                                 return "leave_community_confirmation_alert_message".localized()
 | |
|                                         }
 | |
|                                     }(),
 | |
|                                     threadViewModel.displayName
 | |
|                                 )
 | |
|                                 
 | |
|                                 return NSAttributedString(string: message)
 | |
|                                     .adding(
 | |
|                                         attributes: [
 | |
|                                             .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize)
 | |
|                                         ],
 | |
|                                         range: (message as NSString).range(of: threadViewModel.displayName)
 | |
|                                     )
 | |
|                             }()
 | |
|                             
 | |
|                             let confirmationModal: ConfirmationModal = ConfirmationModal(
 | |
|                                 info: ConfirmationModal.Info(
 | |
|                                     title: confirmationModalTitle,
 | |
|                                     body: .attributedText(confirmationModalExplanation),
 | |
|                                     confirmTitle: "TXT_DELETE_TITLE".localized(),
 | |
|                                     confirmAccessibility: Accessibility(
 | |
|                                         identifier: "Confirm delete"
 | |
|                                     ),
 | |
|                                     confirmStyle: .danger,
 | |
|                                     cancelStyle: .alert_text,
 | |
|                                     dismissOnConfirm: true,
 | |
|                                     onConfirm: { _ in
 | |
|                                         Storage.shared.writeAsync { db in
 | |
|                                             try SessionThread.deleteOrLeave(
 | |
|                                                 db,
 | |
|                                                 threadId: threadViewModel.threadId,
 | |
|                                                 threadVariant: threadViewModel.threadVariant,
 | |
|                                                 groupLeaveType: (isMessageRequest ? .silent : .forced),
 | |
|                                                 calledFromConfigHandling: false
 | |
|                                             )
 | |
|                                         }
 | |
|                                         viewController?.dismiss(animated: true, completion: nil)
 | |
|                                         
 | |
|                                         completionHandler(true)
 | |
|                                     },
 | |
|                                     afterClosed: { completionHandler(false) }
 | |
|                                 )
 | |
|                             )
 | |
|                             
 | |
|                             viewController?.present(confirmationModal, animated: true, completion: nil)
 | |
|                         }
 | |
|                 }
 | |
|             }
 | |
|     }
 | |
| }
 |