diff --git a/Classes/Swift/Conference/Data/ScheduledConferenceData.swift b/Classes/Swift/Conference/Data/ScheduledConferenceData.swift index 0115c250c..e8f66aad2 100644 --- a/Classes/Swift/Conference/Data/ScheduledConferenceData.swift +++ b/Classes/Swift/Conference/Data/ScheduledConferenceData.swift @@ -55,7 +55,7 @@ class ScheduledConferenceData { durationFormatter.unitsStyle = .positional durationFormatter.allowedUnits = [.minute, .second ] durationFormatter.zeroFormattingBehavior = [ .pad ] - duration.value = durationFormatter.string(from: TimeInterval(conferenceInfo.duration)) + duration.value = conferenceInfo.duration > 0 ? durationFormatter.string(from: TimeInterval(conferenceInfo.duration)) : nil organizer.value = conferenceInfo.organizer?.addressBookEnhancedDisplayName() diff --git a/Classes/Swift/Conference/ViewModels/ScheduledConferencesViewModel.swift b/Classes/Swift/Conference/ViewModels/ScheduledConferencesViewModel.swift index 72449a192..005651614 100644 --- a/Classes/Swift/Conference/ViewModels/ScheduledConferencesViewModel.swift +++ b/Classes/Swift/Conference/ViewModels/ScheduledConferencesViewModel.swift @@ -48,7 +48,9 @@ class ScheduledConferencesViewModel { func computeConferenceInfoList() { conferences.value!.removeAll() - core.futureConferenceInformationList.forEach { conferenceInfo in // Sorted in the sdk + let now = Date().timeIntervalSince1970 // Linphone uses time_t in seconds + let oneHourAgo = now - 3600 // Show all conferences from 1 hour ago and forward + core.getConferenceInformationListAfterTime(time: time_t(oneHourAgo)).forEach { conferenceInfo in conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo)) } diff --git a/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift b/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift index 0b5f21a87..f95d11325 100644 --- a/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift +++ b/Classes/Swift/Conference/Views/ScheduledConferencesCell.swift @@ -26,42 +26,58 @@ class ScheduledConferencesCell: UITableViewCell { let corner_radius = 7.0 let border_width = 2.0 + static let button_size = 40 + let clockIcon = UIImageView(image: UIImage(named: "conference_schedule_time_default")) let timeDuration = StyledLabel(VoipTheme.conference_invite_desc_font) let organiser = StyledLabel(VoipTheme.conference_invite_desc_font) let subject = StyledLabel(VoipTheme.conference_invite_subject_font) + let participantsIcon = UIImageView(image: UIImage(named: "conference_schedule_participants_default")) let participants = StyledLabel(VoipTheme.conference_invite_desc_font) let infoConf = UIButton() - let descriptionTitle = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_description_title) - let descriptionValue = StyledLabel(VoipTheme.conference_scheduling_font) - let urlTitle = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_address_title) - let urlAndCopy = UIView() + let descriptionTitle = StyledLabel(VoipTheme.conference_invite_desc_font, VoipTexts.conference_description_title) + let descriptionValue = StyledLabel(VoipTheme.conference_invite_desc_font) + let urlTitle = StyledLabel(VoipTheme.conference_invite_desc_font, VoipTexts.conference_schedule_address_title) let urlValue = StyledLabel(VoipTheme.conference_scheduling_font) - let copyLink = CallControlButton(buttonTheme: VoipTheme.scheduled_conference_action("voip_copy")) - let joinEditDelete = UIView() + let copyLink = CallControlButton(width:button_size,height:button_size,buttonTheme: VoipTheme.scheduled_conference_action("voip_copy")) let joinConf = FormButton(title:VoipTexts.conference_invite_join.uppercased(), backgroundStateColors: VoipTheme.button_green_background) - let deleteConf = CallControlButton(buttonTheme: VoipTheme.scheduled_conference_action("voip_delete")) - let editConf = CallControlButton(buttonTheme: VoipTheme.scheduled_conference_action("voip_edit")) - + let deleteConf = CallControlButton(width:button_size,height:button_size,buttonTheme: VoipTheme.scheduled_conference_action("voip_delete")) + let editConf = CallControlButton(width:button_size,height:button_size,buttonTheme: VoipTheme.scheduled_conference_action("voip_edit")) + var owningTableView : UITableView? = nil + let joinEditDelete = UIStackView() + let expandedRows = UIStackView() + var conferenceData: ScheduledConferenceData? = nil { didSet { if let data = conferenceData { - timeDuration.text = "\(data.time) ( \(data.duration) )" - timeDuration.addIndicatorIcon(iconName: "conference_schedule_time_default", trailing: false) + timeDuration.text = "\(data.time.value)"+(data.duration.value != nil ? " ( \(data.duration.value) )" : "") organiser.text = VoipTexts.conference_schedule_organizer+data.organizer.value! subject.text = data.subject.value! descriptionValue.text = data.description.value! urlValue.text = data.address.value! data.expanded.readCurrentAndObserve { expanded in self.contentView.layer.borderWidth = expanded == true ? 2.0 : 0.0 - self.descriptionTitle.isHidden = expanded != true - self.descriptionValue.isHidden = expanded != true - self.urlAndCopy.isHidden = expanded != true - self.joinEditDelete.isHidden = expanded != true + self.descriptionTitle.isHidden = expanded != true || self.descriptionValue.text?.count == 0 + self.descriptionValue.isHidden = expanded != true || self.descriptionValue.text?.count == 0 self.infoConf.isSelected = expanded == true self.participants.text = expanded == true ? data.participantsExpanded.value : data.participantsShort.value - self.participants.addIndicatorIcon(iconName: "conference_schedule_participants_default", trailing: false) + self.participants.numberOfLines = expanded == true ? 6 : 2 + self.expandedRows.isHidden = expanded != true + self.joinEditDelete.isHidden = expanded != true + if let myAddress = Core.get().defaultAccount?.params?.identityAddress { + self.editConf.isHidden = expanded != true || data.conferenceInfo.organizer?.weakEqual(address2: myAddress) != true + } else { + self.editConf.isHidden = true + } + self.participants.removeConstraints().alignUnder(view: self.subject,withMargin: 15).toRightOf(self.participantsIcon,withLeftMargin:10).toRightOf(self.participantsIcon,withLeftMargin:10).toLeftOf(self.infoConf,withRightMargin: 15).done() + self.joinEditDelete.removeConstraints().alignUnder(view: self.expandedRows,withMargin: 15).alignParentRight(withMargin: 10).done() + if (expanded == true) { + self.joinEditDelete.alignParentBottom(withMargin: 10).done() + } else { + self.participants.alignParentBottom(withMargin: 10).done() + } + } } } @@ -75,48 +91,90 @@ class ScheduledConferencesCell: UITableViewCell { contentView.backgroundColor = VoipTheme.header_background_color contentView.layer.borderColor = VoipTheme.primary_color.cgColor - let rows = UIStackView() - rows.axis = .vertical - rows.addArrangedSubview(timeDuration) - rows.addArrangedSubview(subject) + + contentView.addSubview(clockIcon) + clockIcon.alignParentTop(withMargin: 15).square(15).alignParentLeft(withMargin: 10).done() - let participantsAndInfos = UIView() - participantsAndInfos.addSubview(participants) - participants.alignParentLeft().done() - participantsAndInfos.addSubview(infoConf) - infoConf.toRightOf(participants).done() - rows.addArrangedSubview(participantsAndInfos) - infoConf.applyTintedIcons(tintedIcons: VoipTheme.conference_info_button) + contentView.addSubview(timeDuration) + timeDuration.alignParentTop(withMargin: 15).toRightOf(clockIcon,withLeftMargin:10).alignHorizontalCenterWith(clockIcon).done() + + contentView.addSubview(organiser) + organiser.alignParentTop(withMargin: 15).toRightOf(timeDuration, withLeftMargin:10).alignParentRight(withMargin:10).alignHorizontalCenterWith(clockIcon).done() + + contentView.addSubview(subject) + subject.alignUnder(view: timeDuration,withMargin: 15).alignParentLeft(withMargin: 10).done() + + contentView.addSubview(participantsIcon) + participantsIcon.alignUnder(view: subject,withMargin: 15).square(15).alignParentLeft(withMargin: 10).done() + infoConf.onClick { self.conferenceData?.toggleExpand() + self.owningTableView?.reloadData() } - - rows.addArrangedSubview(descriptionTitle) - rows.addArrangedSubview(descriptionValue) + contentView.addSubview(infoConf) + 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) - rows.addArrangedSubview(urlTitle) - urlAndCopy.addSubview(urlValue) + + contentView.addSubview(participants) + participants.alignUnder(view: subject,withMargin: 15).toRightOf(participantsIcon,withLeftMargin:10).toRightOf(participantsIcon,withLeftMargin:10).toLeftOf(infoConf,withRightMargin: 15).done() + + expandedRows.axis = .vertical + expandedRows.spacing = 10 + contentView.addSubview(expandedRows) + expandedRows.alignUnder(view: participants,withMargin: 15).matchParentSideBorders(insetedByDx:10).done() + + expandedRows.addArrangedSubview(descriptionTitle) + expandedRows.addArrangedSubview(descriptionValue) + + expandedRows.addArrangedSubview(urlTitle) + let urlAndCopy = UIStackView() + urlAndCopy.addArrangedSubview(urlValue) urlValue.backgroundColor = .white + self.urlValue.isEnabled = false urlValue.alignParentLeft().done() - urlAndCopy.addSubview(copyLink) - copyLink.toLeftOf(urlValue).done() - rows.addArrangedSubview(urlAndCopy) - - joinEditDelete.addSubview(joinConf) - joinEditDelete.addSubview(editConf) - joinEditDelete.addSubview(deleteConf) - deleteConf.alignParentRight().done() - editConf.toLeftOf(deleteConf).done() - joinConf.toLeftOf(deleteConf).done() - - joinConf.onClick { - /* - ConferenceWaitingRoomFragment *view = VIEW(ConferenceWaitingRoomFragment); - [PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomFragment.compositeViewDescription]; - [view setDetailsWithSubject:@"Sujet de la conférence" url:@"toto"]; - return; - */ + urlAndCopy.addArrangedSubview(copyLink) + copyLink.toRightOf(urlValue,withLeftMargin: 10).done() + expandedRows.addArrangedSubview(urlAndCopy) + copyLink.onClick { + UIPasteboard.general.string = self.conferenceData?.address.value! + VoipDialog.toast(message: VoipTexts.conference_schedule_address_copied_to_clipboard) } + + 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() + joinConf.onClick { + let view : ConferenceWaitingRoomFragment = self.VIEW(ConferenceWaitingRoomFragment.compositeViewDescription()) + PhoneMainView.instance().changeCurrentView(view.compositeViewDescription()) + view.setDetails(subject: (self.conferenceData?.subject.value)!, url: (self.conferenceData?.address.value)!) + } + + joinEditDelete.addArrangedSubview(editConf) + editConf.onClick { + // TODO + } + + joinEditDelete.addArrangedSubview(deleteConf) + deleteConf.onClick { + let delete = ButtonAttributes(text:VoipTexts.conference_info_confirm_removal_delete, action: { + Core.get().deleteConferenceInformation(conferenceInfo: self.conferenceData!.conferenceInfo) + ScheduledConferencesViewModel.shared.computeConferenceInfoList() + self.owningTableView?.reloadData() + }, isDestructive:false) + let cancel = ButtonAttributes(text:VoipTexts.cancel, action: {}, isDestructive:true) + VoipDialog(message:VoipTexts.conference_info_confirm_removal, givenButtons: [cancel,delete]).show() + } + + } required init?(coder: NSCoder) { diff --git a/Classes/Swift/Conference/Views/ScheduledConferencesView.swift b/Classes/Swift/Conference/Views/ScheduledConferencesView.swift index f8139e375..1974404b4 100644 --- a/Classes/Swift/Conference/Views/ScheduledConferencesView.swift +++ b/Classes/Swift/Conference/Views/ScheduledConferencesView.swift @@ -22,7 +22,7 @@ import UIKit import Foundation import linphonesw -@objc class ScheduledConferencesView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource { +@objc class ScheduledConferencesView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource, UITableViewDelegate { let conferenceListView = UITableView() let noConference = StyledLabel(VoipTheme.empty_list_font,VoipTexts.conference_no_schedule) @@ -37,17 +37,20 @@ import linphonesw backAction: { PhoneMainView.instance().popView(self.compositeViewDescription()) },nextAction: { + PhoneMainView.instance().changeCurrentView(ConferenceSchedulingView.compositeDescription) }, - nextActionEnableCondition: MutableLiveData(false), + nextActionEnableCondition: MutableLiveData(true), title:VoipTexts.conference_scheduled) - super.nextButton.isHidden = true + + super.nextButton.applyTintedIcons(tintedIcons: VoipTheme.conference_create_button) - - contentView.addSubview(conferenceListView) - conferenceListView.isScrollEnabled = false + self.view.addSubview(conferenceListView) + conferenceListView.isScrollEnabled = true conferenceListView.dataSource = self + conferenceListView.delegate = self conferenceListView.register(ScheduledConferencesCell.self, forCellReuseIdentifier: "ScheduledConferencesCell") conferenceListView.allowsSelection = false + conferenceListView.rowHeight = UITableView.automaticDimension if #available(iOS 15.0, *) { conferenceListView.allowsFocus = false } @@ -56,7 +59,6 @@ import linphonesw view.addSubview(noConference) noConference.center().done() - } @@ -66,21 +68,22 @@ import linphonesw super.viewWillAppear(animated) self.conferenceListView.reloadData() self.conferenceListView.removeConstraints().done() - self.conferenceListView.matchParentSideBorders().alignUnder(view: super.topBar,withMargin: self.form_margin).alignParentBottom().done() + self.conferenceListView.matchParentSideBorders(insetedByDx: 10).alignUnder(view: super.topBar,withMargin: self.form_margin).alignParentBottom().done() noConference.isHidden = !ScheduledConferencesViewModel.shared.daySplitted.isEmpty } // TableView datasource delegate func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys) + let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed()) let day = daysArray[section] - return TimestampUtils.dateToString(date: day) + return TimestampUtils.dateLongToString(date: day) } func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { guard let header = view as? UITableViewHeaderFooterView else { return } header.textLabel?.applyStyle(VoipTheme.conference_invite_title_font) + header.textLabel?.matchParentSideBorders().done() } func numberOfSections(in tableView: UITableView) -> Int { @@ -88,19 +91,30 @@ import linphonesw } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys) + let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed()) let day = daysArray[section] return ScheduledConferencesViewModel.shared.daySplitted[day]!.count } + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed()) + let day = daysArray[indexPath.section] + guard let data = ScheduledConferencesViewModel.shared.daySplitted[day]?[indexPath.row] else { + return UITableView.automaticDimension + } + return data.expanded.value! ? UITableView.automaticDimension : 100 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:ScheduledConferencesCell = tableView.dequeueReusableCell(withIdentifier: "ScheduledConferencesCell") as! ScheduledConferencesCell - let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys) + let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed()) let day = daysArray[indexPath.section] guard let data = ScheduledConferencesViewModel.shared.daySplitted[day]?[indexPath.row] else { return cell } cell.conferenceData = data + cell.owningTableView = tableView return cell } diff --git a/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift b/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift index edbb118be..7d974d818 100644 --- a/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift +++ b/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift @@ -308,6 +308,12 @@ extension UIView { return self } + func toLeftOf(_ view:UIView, withRightMargin:CGFloat) -> UIView { + snp.makeConstraints { (make) in + make.right.equalTo(view.snp.left).offset(-withRightMargin) + } + return self + } func centerX(withDx:Int = 0) -> UIView { snp.makeConstraints { (make) in diff --git a/Classes/Swift/Util/TimestampUtils.swift b/Classes/Swift/Util/TimestampUtils.swift index 64946c34c..d217ee350 100644 --- a/Classes/Swift/Util/TimestampUtils.swift +++ b/Classes/Swift/Util/TimestampUtils.swift @@ -53,6 +53,13 @@ class TimestampUtils { return dateFormatter.string(from: date) } + static func dateLongToString(date:Date) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .none + return dateFormatter.string(from: date) + } + static func timeToString(date:Date) -> String { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .none diff --git a/Classes/Swift/Voip/Theme/VoipTexts.swift b/Classes/Swift/Voip/Theme/VoipTexts.swift index 60e5bdafd..8d4ba6618 100644 --- a/Classes/Swift/Voip/Theme/VoipTexts.swift +++ b/Classes/Swift/Voip/Theme/VoipTexts.swift @@ -104,7 +104,8 @@ import UIKit static let conference_schedule_organizer = NSLocalizedString("Organizer:",comment:"") static let conference_go_to_chat = NSLocalizedString("Conference's chat room",comment:"") static let conference_creation_failed = NSLocalizedString("Failed to create conference",comment:"") - + static let conference_info_confirm_removal = NSLocalizedString("Do you really want to delete this conference?",comment:"") + static let conference_info_confirm_removal_delete = NSLocalizedString("Delete",comment:"") // Call Stats diff --git a/Classes/Swift/Voip/Theme/VoipTheme.swift b/Classes/Swift/Voip/Theme/VoipTheme.swift index 47ddd4ca6..28cfe24ba 100644 --- a/Classes/Swift/Voip/Theme/VoipTheme.swift +++ b/Classes/Swift/Voip/Theme/VoipTheme.swift @@ -388,6 +388,11 @@ class VoipTheme { // Names & values replicated from Android UIButton.State.selected.rawValue : TintableIcon(name: "voip_info",tintColor: LightDarkColor(primary_color,primary_color)), ] + static let conference_create_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)), + ] + } diff --git a/Classes/Swift/Voip/VoipDialog.swift b/Classes/Swift/Voip/VoipDialog.swift index 7ece3a8d8..2ed619103 100644 --- a/Classes/Swift/Voip/VoipDialog.swift +++ b/Classes/Swift/Voip/VoipDialog.swift @@ -86,11 +86,11 @@ class VoipDialog : UIView{ } func show() { - rootVC()?.view.addSubview(self) + VoipDialog.rootVC()?.view.addSubview(self) matchParentDimmensions().done() } - private func rootVC() -> UIViewController? { + private static func rootVC() -> UIViewController? { return UIApplication.getTopMostViewController() } @@ -98,6 +98,15 @@ class VoipDialog : UIView{ super.init(coder: coder) } + static func toast(message:String, timeout:CGFloat = 1.5) { + let alertDisapperTimeInSeconds = 2.0 + let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet) + rootVC()?.present(alert, animated: true) + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + alertDisapperTimeInSeconds) { + alert.dismiss(animated: true) + } + } + } struct ButtonAttributes {