@@ -2352,102 +2352,7 @@ extension ConversationVC:
23522352 /// Retrieve the deletion actions for the selected message(s) of there are any
23532353 let messagesToDelete : [ MessageViewModel ] = [ cellViewModel]
23542354
2355- guard let deletionBehaviours: MessageViewModel . DeletionBehaviours = self . viewModel. deletionActions ( for: messagesToDelete) else {
2356- return
2357- }
2358-
2359- let modal : ConfirmationModal = ConfirmationModal (
2360- info: ConfirmationModal . Info (
2361- title: deletionBehaviours. title,
2362- body: . radio(
2363- explanation: ThemedAttributedString ( string: deletionBehaviours. body) ,
2364- warning: deletionBehaviours. warning. map { ThemedAttributedString ( string: $0) } ,
2365- options: deletionBehaviours. actions. map { action in
2366- ConfirmationModal . Info. Body. RadioOptionInfo (
2367- title: action. title,
2368- enabled: action. state != . disabled,
2369- selected: action. state == . enabledAndDefaultSelected,
2370- accessibility: action. accessibility
2371- )
2372- }
2373- ) ,
2374- confirmTitle: " delete " . localized ( ) ,
2375- confirmStyle: . danger,
2376- cancelTitle: " cancel " . localized ( ) ,
2377- cancelStyle: . alert_text,
2378- dismissOnConfirm: false ,
2379- onConfirm: { [ weak self, dependencies = viewModel. dependencies] modal in
2380- /// Determine the selected action index
2381- let selectedIndex : Int = {
2382- switch modal. info. body {
2383- case . radio( _, _, let options) :
2384- return options
2385- . enumerated ( )
2386- . first ( where: { _, value in value. selected } )
2387- . map { index, _ in index }
2388- . defaulting ( to: 0 )
2389-
2390- default : return 0
2391- }
2392- } ( )
2393-
2394- /// Stop the messages audio if needed
2395- messagesToDelete. forEach { cellViewModel in
2396- self ? . viewModel. stopAudioIfNeeded ( for: cellViewModel)
2397- }
2398-
2399- /// Trigger the deletion behaviours
2400- deletionBehaviours
2401- . publisherForAction ( at: selectedIndex, using: dependencies)
2402- . showingBlockingLoading (
2403- in: deletionBehaviours. requiresNetworkRequestForAction ( at: selectedIndex) ?
2404- self ? . viewModel. navigatableState :
2405- nil
2406- )
2407- . sinkUntilComplete (
2408- receiveCompletion: { result in
2409- DispatchQueue . main. async {
2410- switch result {
2411- case . finished:
2412- modal. dismiss ( animated: true ) {
2413- /// Dispatch after a delay because becoming the first responder can cause
2414- /// an odd appearance animation
2415- DispatchQueue . main. asyncAfter ( deadline: . now( ) + . milliseconds( 150 ) ) {
2416- self ? . viewModel. showToast (
2417- text: " deleteMessageDeleted "
2418- . putNumber ( messagesToDelete. count)
2419- . localized ( ) ,
2420- backgroundColor: . backgroundSecondary,
2421- inset: ( self ? . inputAccessoryView? . frame. height ?? Values . mediumSpacing) + Values. smallSpacing
2422- )
2423- }
2424- }
2425-
2426- case . failure:
2427- self ? . viewModel. showToast (
2428- text: " deleteMessageFailed "
2429- . putNumber ( messagesToDelete. count)
2430- . localized ( ) ,
2431- backgroundColor: . backgroundSecondary,
2432- inset: ( self ? . inputAccessoryView? . frame. height ?? Values . mediumSpacing) + Values. smallSpacing
2433- )
2434- }
2435- completion ? ( )
2436- }
2437- }
2438- )
2439- } ,
2440- afterClosed: { [ weak self] in
2441- self ? . becomeFirstResponder ( )
2442- }
2443- )
2444- )
2445-
2446- /// Show the modal after a small delay so it doesn't look as weird with the context menu dismissal
2447- DispatchQueue . main. asyncAfter ( deadline: . now( ) + . milliseconds( Int ( ContextMenuVC . dismissDurationPartOne * 1000 ) ) ) { [ weak self] in
2448- self ? . present ( modal, animated: true )
2449- self ? . resignFirstResponder ( )
2450- }
2355+ onDelete ( messagesToDelete, completion: completion)
24512356 }
24522357
24532358 func save( _ cellViewModel: MessageViewModel , completion: ( ( ) -> Void ) ? ) {
@@ -3348,4 +3253,140 @@ extension ConversationVC: MediaPresentationContextProvider {
33483253 func snapshotOverlayView( in coordinateSpace: UICoordinateSpace ) -> ( UIView , CGRect ) ? {
33493254 return self . navigationController? . navigationBar. generateSnapshot ( in: coordinateSpace)
33503255 }
3256+
3257+
3258+ // MARK: - Delete Messages
3259+ func deleteAttachments( _ items: [ MediaGalleryViewModel . Item ] , completion: @escaping ( ) -> Void ) {
3260+ let desiredIDSet : Set < Int64 > = Set ( items. map { $0. interactionId } )
3261+
3262+ let messagesToDelete : [ MessageViewModel ] = viewModel. interactionData
3263+ . flatMap { $0. elements }
3264+ . filter { desiredIDSet. contains ( $0. id) }
3265+
3266+ onDelete ( messagesToDelete, fromAttachments: true ) {
3267+ completion ( )
3268+ }
3269+ }
3270+
3271+ func onDelete( _ messagesToDelete: [ MessageViewModel ] , fromAttachments: Bool = false , completion: ( ( ) -> Void ) ? = nil ) {
3272+ guard let topViewController = viewModel. dependencies [ singleton: . appContext] . frontMostViewController else {
3273+ return
3274+ }
3275+
3276+ guard let deletionBehaviours: MessageViewModel . DeletionBehaviours = self . viewModel. deletionActions ( for: messagesToDelete) else {
3277+ return
3278+ }
3279+
3280+ var alertContentForDeletion : ( title: String , body: String ) {
3281+ guard fromAttachments else {
3282+ return (
3283+ deletionBehaviours. title,
3284+ deletionBehaviours. body,
3285+ )
3286+ }
3287+
3288+ // If items to delete contains other messages aside from attachments w/ message
3289+ return (
3290+ " deleteAttachments "
3291+ . putNumber ( messagesToDelete. count)
3292+ . localized ( ) ,
3293+ " deleteAttachmentsDescription "
3294+ . putNumber ( messagesToDelete. count)
3295+ . localized ( ) ,
3296+ )
3297+ }
3298+
3299+ let modal : ConfirmationModal = ConfirmationModal (
3300+ info: ConfirmationModal . Info (
3301+ title: alertContentForDeletion. title,
3302+ body: . radio(
3303+ explanation: ThemedAttributedString ( string: alertContentForDeletion. body) ,
3304+ warning: deletionBehaviours. warning. map { ThemedAttributedString ( string: $0) } ,
3305+ options: deletionBehaviours. actions. map { action in
3306+ ConfirmationModal . Info. Body. RadioOptionInfo (
3307+ title: action. title,
3308+ enabled: action. state != . disabled,
3309+ selected: action. state == . enabledAndDefaultSelected,
3310+ accessibility: action. accessibility
3311+ )
3312+ }
3313+ ) ,
3314+ confirmTitle: " delete " . localized ( ) ,
3315+ confirmStyle: . danger,
3316+ cancelTitle: " cancel " . localized ( ) ,
3317+ cancelStyle: . alert_text,
3318+ dismissOnConfirm: false ,
3319+ onConfirm: { [ weak self, dependencies = viewModel. dependencies] modal in
3320+ /// Determine the selected action index
3321+ let selectedIndex : Int = {
3322+ switch modal. info. body {
3323+ case . radio( _, _, let options) :
3324+ return options
3325+ . enumerated ( )
3326+ . first ( where: { _, value in value. selected } )
3327+ . map { index, _ in index }
3328+ . defaulting ( to: 0 )
3329+
3330+ default : return 0
3331+ }
3332+ } ( )
3333+
3334+ /// Stop the messages audio if needed
3335+ messagesToDelete. forEach { cellViewModel in
3336+ self ? . viewModel. stopAudioIfNeeded ( for: cellViewModel)
3337+ }
3338+
3339+ /// Trigger the deletion behaviours
3340+ deletionBehaviours
3341+ . publisherForAction ( at: selectedIndex, using: dependencies)
3342+ . showingBlockingLoading (
3343+ in: deletionBehaviours. requiresNetworkRequestForAction ( at: selectedIndex) ?
3344+ self ? . viewModel. navigatableState :
3345+ nil
3346+ )
3347+ . sinkUntilComplete (
3348+ receiveCompletion: { result in
3349+ DispatchQueue . main. async {
3350+ switch result {
3351+ case . finished:
3352+ modal. dismiss ( animated: true ) {
3353+ /// Dispatch after a delay because becoming the first responder can cause
3354+ /// an odd appearance animation
3355+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + . milliseconds( 150 ) ) {
3356+ self ? . viewModel. showToast (
3357+ text: " deleteMessageDeleted "
3358+ . putNumber ( messagesToDelete. count)
3359+ . localized ( ) ,
3360+ backgroundColor: . backgroundSecondary,
3361+ inset: ( self ? . inputAccessoryView? . frame. height ?? Values . mediumSpacing) + Values. smallSpacing
3362+ )
3363+ }
3364+ }
3365+
3366+ case . failure:
3367+ self ? . viewModel. showToast (
3368+ text: " deleteMessageFailed "
3369+ . putNumber ( messagesToDelete. count)
3370+ . localized ( ) ,
3371+ backgroundColor: . backgroundSecondary,
3372+ inset: ( self ? . inputAccessoryView? . frame. height ?? Values . mediumSpacing) + Values. smallSpacing
3373+ )
3374+ }
3375+ completion ? ( )
3376+ }
3377+ }
3378+ )
3379+ } ,
3380+ afterClosed: { [ weak self] in
3381+ self ? . becomeFirstResponder ( )
3382+ }
3383+ )
3384+ )
3385+
3386+ /// Show the modal after a small delay so it doesn't look as weird with the context menu dismissal
3387+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + . milliseconds( Int ( ContextMenuVC . dismissDurationPartOne * 1000 ) ) ) { [ weak self] in
3388+ topViewController. present ( modal, animated: true )
3389+ self ? . resignFirstResponder ( )
3390+ }
3391+ }
33513392}
0 commit comments