From 7d9b79a9f894815b9367f92b2c71c00daafffad8 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Thu, 22 Sep 2022 23:57:47 +0200 Subject: [PATCH] Multiple deletion of meetings (function activated by long click on meeting cell) --- .../Data/ScheduledConferenceData.swift | 2 + .../ScheduledConferencesViewModel.swift | 1 + .../Views/ConferenceSchedulingView.swift | 3 +- .../Views/ScheduledConferencesCell.swift | 28 ++++--- .../Views/ScheduledConferencesView.swift | 69 ++++++++++++++++-- .../Extensions/IOS/UIVIewExtensions.swift | 20 ++++- Classes/Swift/Voip/Theme/VoipTexts.swift | 3 + Classes/Swift/Voip/Theme/VoipTheme.swift | 6 ++ .../Swift/Voip/Widgets/StyledCheckBox.swift | 22 +++--- Resources/fr.lproj/Localizable.strings | Bin 71972 -> 72294 bytes 10 files changed, 126 insertions(+), 28 deletions(-) diff --git a/Classes/Swift/Conference/Data/ScheduledConferenceData.swift b/Classes/Swift/Conference/Data/ScheduledConferenceData.swift index 9e5157343..82558fd35 100644 --- a/Classes/Swift/Conference/Data/ScheduledConferenceData.swift +++ b/Classes/Swift/Conference/Data/ScheduledConferenceData.swift @@ -39,6 +39,8 @@ class ScheduledConferenceData { let isConferenceCancelled = MutableLiveData(false) let canEdit = MutableLiveData(false) let isFinished : Bool + let selectedForDeletion = MutableLiveData(false) + init (conferenceInfo: ConferenceInfo, isFinished: Bool = false) { self.conferenceInfo = conferenceInfo diff --git a/Classes/Swift/Conference/ViewModels/ScheduledConferencesViewModel.swift b/Classes/Swift/Conference/ViewModels/ScheduledConferencesViewModel.swift index b9f4a032c..cb57f4b75 100644 --- a/Classes/Swift/Conference/ViewModels/ScheduledConferencesViewModel.swift +++ b/Classes/Swift/Conference/ViewModels/ScheduledConferencesViewModel.swift @@ -32,6 +32,7 @@ class ScheduledConferencesViewModel { var daySplitted : [Date : [ScheduledConferenceData]] = [:] var coreDelegate: CoreDelegateStub? var showTerminated = MutableLiveData(false) + let editionEnabled = MutableLiveData(false) init () { diff --git a/Classes/Swift/Conference/Views/ConferenceSchedulingView.swift b/Classes/Swift/Conference/Views/ConferenceSchedulingView.swift index f7249b77a..819e8dec3 100644 --- a/Classes/Swift/Conference/Views/ConferenceSchedulingView.swift +++ b/Classes/Swift/Conference/Views/ConferenceSchedulingView.swift @@ -141,7 +141,8 @@ import IQKeyboardManager scheduleForm.addSubview(viaChatView) viaChatView.alignUnder(view: descriptionInput,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).alignParentBottom(withMargin: form_margin*4).done() - let viaChatSwitch = StyledCheckBox(liveValue: ConferenceSchedulingViewModel.shared.sendInviteViaChat) + let viaChatSwitch = StyledCheckBox() + viaChatSwitch.liveValue = ConferenceSchedulingViewModel.shared.sendInviteViaChat viaChatView.addSubview(viaChatSwitch) viaChatSwitch.alignParentLeft().done() diff --git a/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift b/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift index c0693f013..7c802a9af 100644 --- a/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift +++ b/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift @@ -27,6 +27,7 @@ class ScheduledConferencesCell: UITableViewCell { let corner_radius = 7.0 let border_width = 2.0 static let button_size = 40 + let delete_checkbox_margin = 5 let clockIcon = UIImageView(image: UIImage(named: "conference_schedule_time_default")) let timeDuration = StyledLabel(VoipTheme.conference_invite_desc_font) @@ -48,6 +49,7 @@ class ScheduledConferencesCell: UITableViewCell { var owningTableView : UITableView? = nil let joinEditDelete = UIStackView() let expandedRows = UIStackView() + let selectionCheckBox = StyledCheckBox() var conferenceData: ScheduledConferenceData? = nil { didSet { @@ -88,7 +90,7 @@ class ScheduledConferencesCell: UITableViewCell { } else { self.participants.alignParentBottom(withMargin: 10).done() } - + self.selectionCheckBox.liveValue = data.selectedForDeletion } } } @@ -105,7 +107,7 @@ class ScheduledConferencesCell: UITableViewCell { contentView.addSubview(clockIcon) clockIcon.alignParentTop(withMargin: 15).square(15).alignParentLeft(withMargin: 10).done() - + contentView.addSubview(timeDuration) timeDuration.alignParentTop(withMargin: 15).toRightOf(clockIcon,withLeftMargin:10).alignHorizontalCenterWith(clockIcon).done() @@ -117,10 +119,10 @@ class ScheduledConferencesCell: UITableViewCell { subjectCancel.axis = .vertical contentView.addSubview(subjectCancel) subjectCancel.alignUnder(view: timeDuration,withMargin: 15).alignParentLeft(withMargin: 10).done() - + subjectCancel.addArrangedSubview(cancelledLabel) subjectCancel.addArrangedSubview(subject) - + contentView.addSubview(participantsIcon) participantsIcon.alignUnder(view: subject,withMargin: 15).square(15).alignParentLeft(withMargin: 10).done() @@ -133,8 +135,8 @@ class ScheduledConferencesCell: UITableViewCell { infoConf.imageView?.contentMode = .scaleAspectFit infoConf.alignUnder(view: subject,withMargin: 15).square(30).alignParentRight(withMargin: 10).alignHorizontalCenterWith(participantsIcon).done() infoConf.applyTintedIcons(tintedIcons: VoipTheme.conference_info_button) - - + + contentView.addSubview(participants) participants.alignUnder(view: subject,withMargin: 15).toRightOf(participantsIcon,withLeftMargin:10).toRightOf(participantsIcon,withLeftMargin:10).toLeftOf(infoConf,withRightMargin: 15).done() @@ -142,7 +144,7 @@ class ScheduledConferencesCell: UITableViewCell { expandedRows.spacing = 10 contentView.addSubview(expandedRows) expandedRows.alignUnder(view: participants,withMargin: 15).matchParentSideBorders(insetedByDx:10).done() - + expandedRows.addArrangedSubview(descriptionTitle) expandedRows.addArrangedSubview(descriptionValue) @@ -163,10 +165,10 @@ class ScheduledConferencesCell: UITableViewCell { joinEditDelete.axis = .horizontal joinEditDelete.spacing = 10 joinEditDelete.distribution = .equalSpacing - + contentView.addSubview(joinEditDelete) joinEditDelete.alignUnder(view: expandedRows,withMargin: 15).alignParentRight(withMargin: 10).done() - + joinEditDelete.addArrangedSubview(joinConf) joinConf.width(150).done() @@ -204,6 +206,14 @@ class ScheduledConferencesCell: UITableViewCell { deleteConf.onClick { self.askConfirmationTodeleteEntry() } + contentView.addSubview(selectionCheckBox) + selectionCheckBox.alignParentRight(withMargin: delete_checkbox_margin).alignUnder(view:organiser, withMargin: delete_checkbox_margin).done() + ScheduledConferencesViewModel.shared.editionEnabled.readCurrentAndObserve { editing in + self.selectionCheckBox.isHidden = editing != true + } + onLongClick { + ScheduledConferencesViewModel.shared.editionEnabled.value = true + } } func askConfirmationTodeleteEntry() { diff --git a/Classes/Swift/Conference/Views/ScheduledConferencesView.swift b/Classes/Swift/Conference/Views/ScheduledConferencesView.swift index dec1fcbd5..a87102bd1 100644 --- a/Classes/Swift/Conference/Views/ScheduledConferencesView.swift +++ b/Classes/Swift/Conference/Views/ScheduledConferencesView.swift @@ -27,6 +27,7 @@ import linphonesw let conferenceListView = UITableView() let noConference = StyledLabel(VoipTheme.empty_list_font,VoipTexts.conference_no_schedule) let filters = UIStackView() + let selectAllButton = CallControlButton(buttonTheme:VoipTheme.nav_button("deselect_all")) static let compositeDescription = UICompositeViewDescription(ScheduledConferencesView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, fullscreen: false, isLeftFragment: false,fragmentWith: nil) static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription } @@ -36,15 +37,27 @@ import linphonesw super.viewDidLoad( backAction: { - PhoneMainView.instance().popView(self.compositeViewDescription()) + if (ScheduledConferencesViewModel.shared.editionEnabled.value == true) { + ScheduledConferencesViewModel.shared.editionEnabled.value = false + } else { + PhoneMainView.instance().popView(self.compositeViewDescription()) + } },nextAction: { - ConferenceSchedulingViewModel.shared.reset() - PhoneMainView.instance().changeCurrentView(ConferenceSchedulingView.compositeDescription) + if (ScheduledConferencesViewModel.shared.editionEnabled.value == true) { + self.deleteSelection() + } else { + ConferenceSchedulingViewModel.shared.reset() + PhoneMainView.instance().changeCurrentView(ConferenceSchedulingView.compositeDescription) + } }, nextActionEnableCondition: MutableLiveData(), title:VoipTexts.conference_scheduled) - - super.nextButton.applyTintedIcons(tintedIcons: VoipTheme.conference_create_button) + + // Select all + selectAllButton.setImage(UIImage(named: "deselect_all"), for: .selected) + selectAllButton.setImage(UIImage(named: "select_all_default"), for: .normal) + topBar.addSubview(selectAllButton) + selectAllButton.toLeftOf(nextButton,withRightMargin: CGFloat(side_buttons_margin)).matchParentHeight().done() // Filter buttons let showTerminated = getFilterButton(title: VoipTexts.conference_scheduled_terminated_filter) @@ -93,6 +106,29 @@ import linphonesw view.addSubview(noConference) noConference.center().done() + ScheduledConferencesViewModel.shared.editionEnabled.readCurrentAndObserve { editing in + if (editing == true) { + self.selectAllButton.isSelected = false + self.selectAllButton.isHidden = false + super.nextButton.setImage(UIImage(named: "delete_default"), for: .normal) + super.nextButton.setImage(UIImage(named: "delete_disabled"), for: .disabled) + super.nextButton.setImage(UIImage(named: "delete_default"), for: .highlighted) + super.backButton.setImage(UIImage(named: "cancel_edit_default"), for: .normal) + self.nextButton.isEnabled = ScheduledConferencesViewModel.shared.conferences.value?.filter{$0.selectedForDeletion.value == true}.count ?? 0 > 0 + } else { + self.selectAllButton.isHidden = true + ScheduledConferencesViewModel.shared.conferences.value?.forEach {$0.selectedForDeletion.value = false} + super.nextButton.applyTintedIcons(tintedIcons: VoipTheme.conference_create_button) + super.backButton.setImage(UIImage(named: "back_default"), for: .normal) + self.nextButton.isEnabled = true + } + } + + self.selectAllButton.onClick { + let selectIt = !self.selectAllButton.isSelected + ScheduledConferencesViewModel.shared.conferences.value?.forEach {$0.selectedForDeletion.value = selectIt} + } + } func getFilterButton(title:String) -> UIButton { @@ -118,6 +154,7 @@ import linphonesw self.conferenceListView.matchParentSideBorders(insetedByDx: 10).alignUnder(view: filters,withMargin: self.form_margin).alignParentBottom().done() noConference.isHidden = !ScheduledConferencesViewModel.shared.daySplitted.isEmpty super.nextButton.isEnabled = Core.get().defaultAccount != nil + ScheduledConferencesViewModel.shared.editionEnabled.value = false } // TableView datasource delegate @@ -163,6 +200,12 @@ import linphonesw } cell.conferenceData = data cell.owningTableView = tableView + data.selectedForDeletion.readCurrentAndObserve { selected in + let selectedCount = ScheduledConferencesViewModel.shared.conferences.value?.filter{$0.selectedForDeletion.value == true}.count ?? 0 + let totalCount = ScheduledConferencesViewModel.shared.conferences.value?.count ?? 0 + self.nextButton.isEnabled = selectedCount > 0 + self.selectAllButton.isSelected = selectedCount == totalCount + } return cell } @@ -175,4 +218,20 @@ import linphonesw } } + func deleteSelection () { + let selectedCount = ScheduledConferencesViewModel.shared.conferences.value?.filter{$0.selectedForDeletion.value == true}.count ?? 0 + let delete = ButtonAttributes(text:VoipTexts.conference_info_confirm_removal_delete, action: { + ScheduledConferencesViewModel.shared.conferences.value?.forEach { + Core.get().deleteConferenceInformation(conferenceInfo: $0.conferenceInfo) + ScheduledConferencesViewModel.shared.computeConferenceInfoList() + self.conferenceListView.reloadData() + } + VoipDialog.toast(message: selectedCount == 1 ? VoipTexts.conference_info_removed : VoipTexts.conference_infos_removed) + ScheduledConferencesViewModel.shared.editionEnabled.value = false + }, isDestructive:false) + let cancel = ButtonAttributes(text:VoipTexts.cancel, action: {}, isDestructive:true) + VoipDialog(message:selectedCount == 1 ? VoipTexts.conference_info_confirm_removal : VoipTexts.conference_infos_confirm_removal, givenButtons: [cancel,delete]).show() + + } + } diff --git a/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift b/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift index b77519d2c..4fb2b6c1f 100644 --- a/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift +++ b/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift @@ -446,8 +446,8 @@ extension UIView { // to avoid the unused variable warning } - // Onclick - + // Single click + class TapGestureRecognizer: UITapGestureRecognizer { var action : (()->Void)? = nil } @@ -466,6 +466,22 @@ extension UIView { sender.action!() } + // Long click + class LongPressGestureRecognizer: UILongPressGestureRecognizer { + var action : (()->Void)? = nil + } + func onLongClick(action : @escaping ()->Void ){ + let tap = LongPressGestureRecognizer(target: self , action: #selector(self.handleLongClick(_:))) + tap.action = action + tap.cancelsTouchesInView = false + self.addGestureRecognizer(tap) + self.isUserInteractionEnabled = true + + } + @objc func handleLongClick(_ sender: LongPressGestureRecognizer) { + sender.action!() + } + func VIEW( _ desc: UICompositeViewDescription) -> T{ return PhoneMainView.instance().mainViewController.getCachedController(desc.name) as! T } diff --git a/Classes/Swift/Voip/Theme/VoipTexts.swift b/Classes/Swift/Voip/Theme/VoipTexts.swift index 253f14b29..5bbc9cf72 100644 --- a/Classes/Swift/Voip/Theme/VoipTexts.swift +++ b/Classes/Swift/Voip/Theme/VoipTexts.swift @@ -90,7 +90,10 @@ import UIKit @objc static let conference_group_call_title = NSLocalizedString("Start a group call",comment:"") @objc static let conference_incoming_title = NSLocalizedString("Incoming group call",comment:"") @objc static let conference_info_confirm_removal = NSLocalizedString("Do you really want to delete this meeting?",comment:"") + @objc static let conference_infos_confirm_removal = NSLocalizedString("Do you really want to delete these meetings?",comment:"") @objc static let conference_info_removed = NSLocalizedString("Meeting info has been deleted",comment:"") + @objc static let conference_infos_removed = NSLocalizedString("Meeting infos have been deleted",comment:"") + @objc static let conference_invite_join = NSLocalizedString("Join",comment:"") @objc static let conference_invite_participants_count = NSLocalizedString("%d participants",comment:"") @objc static let conference_invite_title = NSLocalizedString("Meeting invite:",comment:"") diff --git a/Classes/Swift/Voip/Theme/VoipTheme.swift b/Classes/Swift/Voip/Theme/VoipTheme.swift index a4f46a259..d1d7f1a55 100644 --- a/Classes/Swift/Voip/Theme/VoipTheme.swift +++ b/Classes/Swift/Voip/Theme/VoipTheme.swift @@ -403,6 +403,12 @@ import UIKit UIButton.State.disabled.rawValue : TintableIcon(name: "voip_conference_new",tintColor: LightDarkColor(voip_light_gray,voip_light_gray)), ] + static let generic_delete_button = [ + UIButton.State.normal.rawValue : TintableIcon(name: "voip_conference_new",tintColor: LightDarkColor(voip_dark_gray,voip_dark_gray)), + UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_conference_new",tintColor: LightDarkColor(primary_color,primary_color)), + UIButton.State.disabled.rawValue : TintableIcon(name: "voip_conference_new",tintColor: LightDarkColor(voip_light_gray,voip_light_gray)), + ] + } diff --git a/Classes/Swift/Voip/Widgets/StyledCheckBox.swift b/Classes/Swift/Voip/Widgets/StyledCheckBox.swift index b5bf8323f..dd1f2a7a7 100644 --- a/Classes/Swift/Voip/Widgets/StyledCheckBox.swift +++ b/Classes/Swift/Voip/Widgets/StyledCheckBox.swift @@ -27,27 +27,27 @@ class StyledCheckBox: UIButton { // layout constants let button_size = 25.0 + var liveValue : MutableLiveData? = nil { + didSet { + liveValue?.readCurrentAndObserve { (value) in + self.isSelected = value! + } + } + } required init?(coder: NSCoder) { super.init(coder: coder) } - init (liveValue:MutableLiveData) { + init () { super.init(frame: .zero) setBackgroundImage(UIImage(named:"voip_checkbox_unchecked")/*?.tinted(with: VoipTheme.light_grey_color)*/,for: .normal) // tinting not working with those icons setBackgroundImage(UIImage(named:"voip_checkbox_checked")/*?.tinted(with: VoipTheme.primary_color)*/,for: .selected) onClick { - liveValue.value = !liveValue.value! - self.isSelected = liveValue.value! + self.liveValue?.value = self.liveValue?.value != true + self.isSelected = self.liveValue?.value == true } - - size(w: button_size,h: button_size).done() - - liveValue.readCurrentAndObserve { (value) in - self.isSelected = value! - } - - + size(w: button_size,h: button_size).done() } diff --git a/Resources/fr.lproj/Localizable.strings b/Resources/fr.lproj/Localizable.strings index e193859789fda441859ccd374c3f686904f0a02d..32dfe5dead649884a3e619308fd09d26e7c3ea8f 100644 GIT binary patch delta 98 zcmZ3oiRIZAmWC~iw-_f&Y4c6DVq`R6PGu;b{*jGQn6Y?zEhD3)a4~}dLlMJEhEj$+ zhD?ThAXz+}pNmm``YT4pn(4O~86~(uN-`J{8Oj(^r~hPR^q=gcCA2+)iSdjW0Er