diff --git a/Classes/Base.lproj/HistoryListView.xib b/Classes/Base.lproj/HistoryListView.xib index 24c74a7a0..90a4022b3 100644 --- a/Classes/Base.lproj/HistoryListView.xib +++ b/Classes/Base.lproj/HistoryListView.xib @@ -1,13 +1,17 @@ - + + - + + + + @@ -29,7 +33,7 @@ - + + @@ -127,12 +146,12 @@ - + - + @@ -143,7 +162,7 @@ diff --git a/Classes/ChatConversationCreateView.h b/Classes/ChatConversationCreateView.h index b51116e61..8c8f271ea 100644 --- a/Classes/ChatConversationCreateView.h +++ b/Classes/ChatConversationCreateView.h @@ -46,6 +46,8 @@ @property(nonatomic) Boolean isEncrypted; @property(nonatomic) Boolean isForVoipConference; +@property(nonatomic) Boolean isForOngoingVoipConference; + @property (weak, nonatomic) IBOutlet UILabel *voipTitle; - (IBAction)onBackClick:(id)sender; diff --git a/Classes/ChatConversationCreateView.m b/Classes/ChatConversationCreateView.m index 83eacf09a..b5b949769 100644 --- a/Classes/ChatConversationCreateView.m +++ b/Classes/ChatConversationCreateView.m @@ -90,8 +90,14 @@ static UICompositeViewDescription *compositeDescription = nil; _switchView.hidden = true; _chiffreOptionView.hidden = true; _voipTitle.hidden = false; + if (_isForOngoingVoipConference) { + [_nextButton setImage:[UIImage imageNamed:@"valid_default"] forState:UIControlStateNormal]; + } else { + [_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal]; + } } else { _voipTitle.hidden = true; + [_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal]; } } @@ -171,8 +177,13 @@ static UICompositeViewDescription *compositeDescription = nil; - (IBAction)onNextClick:(id)sender { if (_isForVoipConference) { - [PhoneMainView.instance changeCurrentView:VIEW(ConferenceSchedulingSummaryView).compositeViewDescription]; - [VIEW(ConferenceSchedulingSummaryView) setParticipantsWithAddresses:_tableController.contactsGroup]; + if (_isForOngoingVoipConference) { + [PhoneMainView.instance changeCurrentView:VIEW(ActiveCallOrConferenceView).compositeViewDescription]; + [ConferenceViewModelBridge updateParticipantsListWithAddresses:_tableController.contactsGroup]; + } else { + [PhoneMainView.instance changeCurrentView:VIEW(ConferenceSchedulingSummaryView).compositeViewDescription]; + [VIEW(ConferenceSchedulingSummaryView) setParticipantsWithAddresses:_tableController.contactsGroup]; + } } else { ChatConversationInfoView *view = VIEW(ChatConversationInfoView); view.contacts = _tableController.contactsGroup; diff --git a/Classes/ChatConversationTableView.m b/Classes/ChatConversationTableView.m index 5fe80f82d..7c12fa3d3 100644 --- a/Classes/ChatConversationTableView.m +++ b/Classes/ChatConversationTableView.m @@ -24,6 +24,7 @@ #import "UIChatBubblePhotoCell.h" #import "UIChatNotifiedEventCell.h" #import "PhoneMainView.h" +#import "linphoneapp-Swift.h" @implementation ChatConversationTableView @@ -326,7 +327,8 @@ static const int BASIC_EVENT_LIST=15; LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue]; if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) { LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event); - if (linphone_chat_message_get_file_transfer_information(chat) || linphone_chat_message_get_external_body_url(chat)) + BOOL isConferenceIcs = [ICSBubbleView isConferenceInvitationMessageWithCmessage:chat]; + if (!isConferenceIcs && (linphone_chat_message_get_file_transfer_information(chat) || linphone_chat_message_get_external_body_url(chat))) kCellId = NSStringFromClass(UIChatBubblePhotoCell.class); else kCellId = NSStringFromClass(UIChatBubbleTextCell.class); @@ -373,14 +375,12 @@ static const CGFloat MESSAGE_SPACING_PERCENTAGE = 1.f; LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue]; if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) { LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event); - - //If the message is followed by another one that is not from the same address, we add a little space under it - CGFloat height = 0; - if ([self isLastIndexInTableView:indexPath chat:chat]) - height += tableView.frame.size.height * MESSAGE_SPACING_PERCENTAGE / 100; - if (![self isFirstIndexInTableView:indexPath chat:chat]) - height -= 20; - + //If the message is followed by another one that is not from the same address, we add a little space under it + CGFloat height = 0; + if ([self isLastIndexInTableView:indexPath chat:chat]) + height += tableView.frame.size.height * MESSAGE_SPACING_PERCENTAGE / 100; + if (![self isFirstIndexInTableView:indexPath chat:chat]) + height -= 20; return [UIChatBubbleTextCell ViewHeightForMessage:chat withWidth:self.view.frame.size.width].height + height; } return [UIChatNotifiedEventCell height]; diff --git a/Classes/ChatsListTableView.m b/Classes/ChatsListTableView.m index ba82444af..6f8c3fd36 100644 --- a/Classes/ChatsListTableView.m +++ b/Classes/ChatsListTableView.m @@ -24,6 +24,8 @@ #import "linphone/linphonecore.h" #import "PhoneMainView.h" #import "Utils.h" +#import "SVProgressHUD.h" + @implementation ChatsListTableView @@ -202,11 +204,13 @@ void deletion_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomStat // will force a call to [self loadData] [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:view]; view.waitView.hidden = TRUE; + [SVProgressHUD dismiss]; } } - (void) deleteChatRooms { _waitView.hidden = FALSE; + [SVProgressHUD show]; bctbx_list_t *chatRooms = bctbx_list_copy(_chatRooms); while (chatRooms) { LinphoneChatRoom *chatRoom = (LinphoneChatRoom *)chatRooms->data; diff --git a/Classes/HistoryDetailsView.m b/Classes/HistoryDetailsView.m index 3dda8aa54..a3f5e46d6 100644 --- a/Classes/HistoryDetailsView.m +++ b/Classes/HistoryDetailsView.m @@ -138,6 +138,7 @@ static UICompositeViewDescription *compositeDescription = nil; _addContactButton.hidden = YES; return; } + _emptyLabel.hidden = YES; const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog); diff --git a/Classes/HistoryListTableView.h b/Classes/HistoryListTableView.h index 4255763b5..2f9af3fc5 100644 --- a/Classes/HistoryListTableView.h +++ b/Classes/HistoryListTableView.h @@ -25,7 +25,11 @@ } @property(nonatomic, assign) BOOL missedFilter; +@property(nonatomic, assign) BOOL confFilter; + @property(strong, nonatomic) NSMutableDictionary *sections; @property(strong, nonatomic) NSMutableArray *sortedDays; + +- (void)removeFIlters; @end diff --git a/Classes/HistoryListTableView.m b/Classes/HistoryListTableView.m index e2bbcc374..97f50fbce 100644 --- a/Classes/HistoryListTableView.m +++ b/Classes/HistoryListTableView.m @@ -25,12 +25,13 @@ @implementation HistoryListTableView -@synthesize missedFilter; +@synthesize missedFilter,confFilter; #pragma mark - Lifecycle Functions - (void)initHistoryTableViewController { missedFilter = false; + confFilter = false; } - (id)init { @@ -102,9 +103,30 @@ return; } missedFilter = amissedFilter; + if (missedFilter) { + confFilter = false; + } [self loadData]; } +- (void)setConfFilter:(BOOL)aconfFilter { + if (confFilter == aconfFilter) { + return; + } + confFilter = aconfFilter; + if (confFilter) { + missedFilter = false; + } + [self loadData]; +} + +- (void)removeFIlters { + confFilter = false; + missedFilter = false; + [self loadData]; +} + + #pragma mark - UITableViewDataSource Functions - (NSDate *)dateAtBeginningOfDayForDate:(NSDate *)inputDate { @@ -129,7 +151,8 @@ self.sections = [NSMutableDictionary dictionary]; while (logs != NULL) { LinphoneCallLog *log = (LinphoneCallLog *)logs->data; - if (!missedFilter || linphone_call_log_get_status(log) == LinphoneCallMissed) { + BOOL keepIt = (!missedFilter || linphone_call_log_get_status(log) == LinphoneCallMissed) && (!confFilter||linphone_call_log_was_conference(log)) ; + if (keepIt) { NSDate *startDate = [self dateAtBeginningOfDayForDate:[NSDate dateWithTimeIntervalSince1970:linphone_call_log_get_start_date(log)]]; @@ -143,7 +166,7 @@ // if this contact was already the previous entry, do not add it twice LinphoneCallLog *prev = [eventsOnThisDay lastObject] ? [[eventsOnThisDay lastObject] pointerValue] : NULL; - if (prev && linphone_address_weak_equal(linphone_call_log_get_remote_address(prev), + if (!linphone_call_log_was_conference(log) && prev && linphone_address_weak_equal(linphone_call_log_get_remote_address(prev), linphone_call_log_get_remote_address(log))) { bctbx_list_t *list = linphone_call_log_get_user_data(prev); list = bctbx_list_append(list, log); diff --git a/Classes/HistoryListView.h b/Classes/HistoryListView.h index 87118e2de..9e1a176bb 100644 --- a/Classes/HistoryListView.h +++ b/Classes/HistoryListView.h @@ -31,6 +31,7 @@ @property(nonatomic, strong) IBOutlet UIButton *allButton; @property(nonatomic, strong) IBOutlet UIButton *missedButton; +@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *conferenceButton; @property(weak, nonatomic) IBOutlet UIImageView *selectedButtonImage; @property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleSelectionButton; diff --git a/Classes/HistoryListView.m b/Classes/HistoryListView.m index efb6cc5ff..7c3e96de6 100644 --- a/Classes/HistoryListView.m +++ b/Classes/HistoryListView.m @@ -23,7 +23,7 @@ @implementation HistoryListView -typedef enum _HistoryView { History_All, History_Missed, History_MAX } HistoryView; +typedef enum _HistoryView { History_All, History_Missed, History_Conference, History_MAX } HistoryView; #pragma mark - UICompositeViewDelegate Functions @@ -48,6 +48,11 @@ static UICompositeViewDescription *compositeDescription = nil; #pragma mark - ViewController Functions +-(void) viewDidLoad { + [super viewDidLoad]; + _conferenceButton.imageView.contentMode = UIViewContentModeScaleAspectFit; +} + - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; @@ -70,18 +75,27 @@ static UICompositeViewDescription *compositeDescription = nil; #pragma mark - + - (void)changeView:(HistoryView)view { CGRect frame = _selectedButtonImage.frame; if (view == History_All) { frame.origin.x = _allButton.frame.origin.x; _allButton.selected = TRUE; - [_tableController setMissedFilter:FALSE]; + [_tableController removeFIlters]; _missedButton.selected = FALSE; + _conferenceButton.selected = false; + } else if (view == History_Conference) { + frame.origin.x = _conferenceButton.frame.origin.x; + _conferenceButton.selected = TRUE; + [_tableController setConfFilter:true]; + _missedButton.selected = FALSE; + _allButton.selected = FALSE; } else { frame.origin.x = _missedButton.frame.origin.x; _missedButton.selected = TRUE; [_tableController setMissedFilter:TRUE]; _allButton.selected = FALSE; + _conferenceButton.selected = false; } _selectedButtonImage.frame = frame; } @@ -96,6 +110,10 @@ static UICompositeViewDescription *compositeDescription = nil; [self changeView:History_Missed]; } +- (IBAction)onConferenceClick:(id)sender { + [self changeView:History_Conference]; +} + - (IBAction)onDeleteClick:(id)event { NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"Do you want to delete selected logs?", nil)]; [UIConfirmationDialog ShowWithMessage:msg diff --git a/Classes/LinphoneCoreSettingsStore.m b/Classes/LinphoneCoreSettingsStore.m index d9a59ac84..f40d673bc 100644 --- a/Classes/LinphoneCoreSettingsStore.m +++ b/Classes/LinphoneCoreSettingsStore.m @@ -331,6 +331,8 @@ { [self setBool:[lm lpConfigBoolForKey:@"use_device_ringtone"] forKey:@"use_device_ringtone"]; + [self setBool:linphone_core_is_record_aware_enabled(LC) forKey:@"record_aware"]; + [self setBool:linphone_core_get_use_info_for_dtmf(LC) forKey:@"sipinfo_dtmf_preference"]; [self setBool:linphone_core_get_use_rfc2833_for_dtmf(LC) forKey:@"rfc_dtmf_preference"]; @@ -787,7 +789,9 @@ linphone_core_set_use_rfc2833_for_dtmf(LC, [self boolForKey:@"rfc_dtmf_preference"]); [lm lpConfigSetBool:[self boolForKey:@"use_device_ringtone"] forKey:@"use_device_ringtone"]; [ProviderDelegate resetSharedProviderConfiguration]; - + + linphone_core_set_record_aware_enabled(LC, [self boolForKey:@"record_aware"]); + linphone_core_set_use_info_for_dtmf(LC, [self boolForKey:@"sipinfo_dtmf_preference"]); linphone_core_set_inc_timeout(LC, [self integerForKey:@"incoming_call_timeout_preference"]); linphone_core_set_in_call_timeout(LC, [self integerForKey:@"in_call_timeout_preference"]); diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index 8cb934663..59fc88be5 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -487,6 +487,15 @@ static int check_should_migrate_images(void *data, int argc, char **argv, char * linphone_account_set_params(account, newAccountParams); } } + if (!linphone_account_params_get_audio_video_conference_factory_address(newAccountParams) && strcmp(appDomain.UTF8String, linphone_account_params_get_domain(newAccountParams)) == 0) { + NSString *uri = [self lpConfigStringForKey:@"default_audio_video_conference_factory_uri" withDefault:@"sip:videoconference-factory2@sip.linphone.org"]; + LinphoneAddress *a = linphone_factory_create_address(linphone_factory_get(), uri.UTF8String); + if (a) { + linphone_account_params_set_audio_video_conference_factory_address(newAccountParams, a); + linphone_account_set_params(account, newAccountParams); + } + } + linphone_account_params_unref(newAccountParams); accounts = accounts->next; } diff --git a/Classes/LinphoneUI/TabBarView.m b/Classes/LinphoneUI/TabBarView.m index ec171fbf9..35a242be0 100644 --- a/Classes/LinphoneUI/TabBarView.m +++ b/Classes/LinphoneUI/TabBarView.m @@ -19,6 +19,7 @@ #import "TabBarView.h" #import "PhoneMainView.h" +#import "linphoneapp-Swift.h" @implementation TabBarView @@ -99,7 +100,8 @@ - (void)updateSelectedButton:(UICompositeViewDescription *)view { _historyButton.selected = [view equal:HistoryListView.compositeViewDescription] || - [view equal:HistoryDetailsView.compositeViewDescription]; + [view equal:HistoryDetailsView.compositeViewDescription] || + [view equal:ConferenceHistoryDetailsView.compositeViewDescription]; _contactsButton.selected = [view equal:ContactsListView.compositeViewDescription] || [view equal:ContactDetailsView.compositeViewDescription]; _dialerButton.selected = [view equal:DialerView.compositeViewDescription]; diff --git a/Classes/LinphoneUI/UICamSwitch.h b/Classes/LinphoneUI/UICamSwitch.h index 9713703cb..545d71428 100644 --- a/Classes/LinphoneUI/UICamSwitch.h +++ b/Classes/LinphoneUI/UICamSwitch.h @@ -24,5 +24,6 @@ @interface UICamSwitch : UIIconButton @property(nonatomic, weak) IBOutlet UIView *preview; ++ (void) switchCamera; @end diff --git a/Classes/LinphoneUI/UICamSwitch.m b/Classes/LinphoneUI/UICamSwitch.m index 9aee5f2f5..ce88d3120 100644 --- a/Classes/LinphoneUI/UICamSwitch.m +++ b/Classes/LinphoneUI/UICamSwitch.m @@ -34,6 +34,10 @@ INIT_WITH_COMMON_CF { #pragma mark - - (void)touchUp:(id)sender { + [UICamSwitch switchCamera]; +} + ++ (void) switchCamera { const char *currentCamId = (char *)linphone_core_get_video_device(LC); const char **cameras = linphone_core_get_video_devices(LC); const char *newCamId = NULL; @@ -52,7 +56,7 @@ INIT_WITH_COMMON_CF { linphone_core_set_video_device(LC, newCamId); LinphoneCall *call = linphone_core_get_current_call(LC); if (call != NULL) { - linphone_call_update(call, NULL); + linphone_core_update_call(LC, call, NULL); } } } diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.h b/Classes/LinphoneUI/UIChatBubbleTextCell.h index 6b583ecac..941d44364 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.h +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.h @@ -29,6 +29,9 @@ #define IMAGE_DEFAULT_MARGIN 5 #define VOICE_RECORDING_PLAYER_HEIGHT 60 #define VOICE_RECORDING_PLAYER_WIDTH 300 +#define CONFERENCE_INVITATION_HEIGHT 210 +#define CONFERENCE_INVITATION_WIDTH 300 + @interface UIChatBubbleTextCell : UITableViewCell diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index 079891029..da123c580 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -30,6 +30,9 @@ @implementation UIChatBubbleTextCell +ICSBubbleView *icsBubbleView; + + #pragma mark - Lifecycle Functions @@ -44,6 +47,11 @@ UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:arrayOfViews.count - 1]); [self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)]; [self addSubview:sub]; + icsBubbleView = [[ICSBubbleView alloc] init]; + icsBubbleView.frame = CGRectMake(_messageText.frame.origin.x, _messageText.frame.origin.y+25, CONFERENCE_INVITATION_WIDTH-80, CONFERENCE_INVITATION_HEIGHT-20); + [self.innerView addSubview:icsBubbleView]; + [icsBubbleView setLayoutConstraintsWithView:self.backgroundColorImage]; + } } @@ -276,6 +284,18 @@ _replyView.view.hidden = true; } + // ICS for conference invitations + + if ([ICSBubbleView isConferenceInvitationMessageWithCmessage:self.message]) { + [icsBubbleView setFromChatMessageWithCmessage:self.message]; + icsBubbleView.hidden = false; + _messageText.hidden = true; + } else { + icsBubbleView.hidden = true; + _messageText.hidden = false; + } + + } - (void)setEditing:(BOOL)editing { @@ -455,6 +475,11 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18; } + (CGSize)ViewHeightForMessageText:(LinphoneChatMessage *)chat withWidth:(int)width textForImdn:(NSString *)imdnText { + + if ([ICSBubbleView isConferenceInvitationMessageWithCmessage:chat]) { + return CGSizeMake(CONFERENCE_INVITATION_WIDTH, CONFERENCE_INVITATION_HEIGHT); + } + NSString *messageText = [UIChatBubbleTextCell TextMessageForChat:chat]; static UIFont *messageFont = nil; diff --git a/Classes/LinphoneUI/UICompositeView.h b/Classes/LinphoneUI/UICompositeView.h index db15589b2..e91b21fdb 100644 --- a/Classes/LinphoneUI/UICompositeView.h +++ b/Classes/LinphoneUI/UICompositeView.h @@ -85,5 +85,7 @@ - (UIInterfaceOrientation)currentOrientation; - (void)clearCache:(NSArray *)exclude; - (IBAction)onRightSwipe:(id)sender; +- (void) removeCallViewFromCache; + @end diff --git a/Classes/LinphoneUI/UICompositeView.m b/Classes/LinphoneUI/UICompositeView.m index 2354b51b5..08baa4963 100644 --- a/Classes/LinphoneUI/UICompositeView.m +++ b/Classes/LinphoneUI/UICompositeView.m @@ -22,6 +22,7 @@ #import "LinphoneAppDelegate.h" #import "Utils.h" #import "SideMenuView.h" +#import "linphoneapp-Swift.h" @implementation UICompositeViewDescription @@ -304,6 +305,14 @@ return nil; } +-(void) removeCallViewFromCache { + for (NSString *key in [viewControllerCache allKeys]) { + if ([key isEqualToString:ActiveCallOrConferenceView.compositeViewDescription.name]) { + [viewControllerCache removeObjectForKey:key]; + } + } +} + - (void)clearCache:(NSArray *)exclude { for (NSString *key in [viewControllerCache allKeys]) { bool remove = true; diff --git a/Classes/LinphoneUI/UIHistoryCell.m b/Classes/LinphoneUI/UIHistoryCell.m index 8df28cd4b..346c029c6 100644 --- a/Classes/LinphoneUI/UIHistoryCell.m +++ b/Classes/LinphoneUI/UIHistoryCell.m @@ -21,6 +21,7 @@ #import "LinphoneManager.h" #import "PhoneMainView.h" #import "Utils.h" +#import "linphoneapp-Swift.h" @implementation UIHistoryCell @@ -59,10 +60,16 @@ if (callLog != NULL) { HistoryDetailsView *view = VIEW(HistoryDetailsView); if (linphone_call_log_get_call_id(callLog) != NULL) { - // Go to History details view - [view setCallLogId:[NSString stringWithUTF8String:linphone_call_log_get_call_id(callLog)]]; + if (linphone_call_log_was_conference(callLog)) { + ConferenceHistoryDetailsView *view = VIEW(ConferenceHistoryDetailsView); + [PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; + [view setCallLogWithCallLog:callLog]; + } else { + // Go to History details view + [view setCallLogId:[NSString stringWithUTF8String:linphone_call_log_get_call_id(callLog)]]; + [PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; + } } - [PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; } } @@ -80,32 +87,39 @@ LOGW(@"Cannot update history cell: null callLog"); return; } - + // Set up the cell... - const LinphoneAddress *addr; - UIImage *image; - if (linphone_call_log_get_dir(callLog) == LinphoneCallIncoming) { - if (linphone_call_log_get_status(callLog) != LinphoneCallMissed) { - image = [UIImage imageNamed:@"call_status_incoming.png"]; - } else { - image = [UIImage imageNamed:@"call_status_missed.png"]; - } - addr = linphone_call_log_get_from_address(callLog); + if (linphone_call_log_was_conference(callLog)) { + const char *subject = linphone_conference_info_get_subject(linphone_call_log_get_conference_info(callLog)); + displayNameLabel.text = [NSString stringWithFormat:@"%s",subject]; + [_avatarImage setImage:[UIImage imageNamed:@"voip_multiple_contacts_avatar"]]; + _stateImage.hidden = true; } else { - image = [UIImage imageNamed:@"call_status_outgoing.png"]; - addr = linphone_call_log_get_to_address(callLog); - } - _stateImage.image = image; - - [ContactDisplay setDisplayNameLabel:displayNameLabel forAddress:addr]; - - size_t count = bctbx_list_size(linphone_call_log_get_user_data(callLog)) + 1; - if (count > 1) { - displayNameLabel.text = + _stateImage.hidden = false; + const LinphoneAddress *addr; + UIImage *image; + if (linphone_call_log_get_dir(callLog) == LinphoneCallIncoming) { + if (linphone_call_log_get_status(callLog) != LinphoneCallMissed) { + image = [UIImage imageNamed:@"call_status_incoming.png"]; + } else { + image = [UIImage imageNamed:@"call_status_missed.png"]; + } + addr = linphone_call_log_get_from_address(callLog); + } else { + image = [UIImage imageNamed:@"call_status_outgoing.png"]; + addr = linphone_call_log_get_to_address(callLog); + } + _stateImage.image = image; + [ContactDisplay setDisplayNameLabel:displayNameLabel forAddress:addr]; + + size_t count = bctbx_list_size(linphone_call_log_get_user_data(callLog)) + 1; + if (count > 1) { + displayNameLabel.text = [displayNameLabel.text stringByAppendingString:[NSString stringWithFormat:@" (%lu)", count]]; + } + + [_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES]; } - - [_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES]; } - (void)setEditing:(BOOL)editing { diff --git a/Classes/LinphoneUI/VideoZoomHandler.h b/Classes/LinphoneUI/VideoZoomHandler.h deleted file mode 100644 index a106d1f2c..000000000 --- a/Classes/LinphoneUI/VideoZoomHandler.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-iphone - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#import -#import - -@interface VideoZoomHandler : NSObject { - float zoomLevel, cx, cy; - UIView* videoView; -} - -- (void) setup: (UIView*) videoView; -- (void) resetZoom; - -@end diff --git a/Classes/LinphoneUI/VideoZoomHandler.m b/Classes/LinphoneUI/VideoZoomHandler.m deleted file mode 100644 index 1539c0252..000000000 --- a/Classes/LinphoneUI/VideoZoomHandler.m +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-iphone - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#import "VideoZoomHandler.h" -#include "linphone/linphonecore.h" -#import "LinphoneManager.h" - -@implementation VideoZoomHandler - -- (void)zoomInOut:(UITapGestureRecognizer *)reco { - if (zoomLevel != 1) - zoomLevel = 1; - else - zoomLevel = 2; - - if (zoomLevel != 1) { - CGPoint point = [reco locationInView:videoView]; - cx = point.x / videoView.frame.size.width; - cy = 1 - point.y / videoView.frame.size.height; - } else { - cx = cy = 0.5; - } - linphone_call_zoom_video(linphone_core_get_current_call(LC), zoomLevel, &cx, &cy); -} - -- (void)videoPan:(UIPanGestureRecognizer *)reco { - if (zoomLevel <= 1.0) - return; - - float x, y; - CGPoint translation = [reco translationInView:videoView]; - if ([reco state] == UIGestureRecognizerStateEnded) { - cx -= translation.x / videoView.frame.size.width; - cy += translation.y / videoView.frame.size.height; - x = cx; - y = cy; - } else if ([reco state] == UIGestureRecognizerStateChanged) { - x = cx - translation.x / videoView.frame.size.width; - y = cy + translation.y / videoView.frame.size.height; - [reco setTranslation:CGPointMake(0, 0) inView:videoView]; - } else { - return; - } - - linphone_call_zoom_video(linphone_core_get_current_call(LC), zoomLevel, &x, &y); - cx = x; - cy = y; -} - -- (void)pinch:(UIPinchGestureRecognizer *)reco { - float s = zoomLevel; - // CGPoint point = [reco locationInView:videoGroup]; - // float ccx = cx + (point.x / videoGroup.frame.size.width - 0.5) / s; - // float ccy = cy - (point.y / videoGroup.frame.size.height - 0.5) / s; - if ([reco state] == UIGestureRecognizerStateEnded) { - zoomLevel = MAX(MIN(zoomLevel * reco.scale, 3.0), 1.0); - s = zoomLevel; - // cx = ccx; - // cy = ccy; - } else if ([reco state] == UIGestureRecognizerStateChanged) { - s = zoomLevel * reco.scale; - s = MAX(MIN(s, 3.0), 1.0); - } else if ([reco state] == UIGestureRecognizerStateBegan) { - - } else { - return; - } - - linphone_call_zoom_video(linphone_core_get_current_call(LC), s, &cx, &cy); -} - -- (void)resetZoom { - zoomLevel = 1; - cx = cy = 0.5; -} - -- (void)setup:(UIView *)view { - videoView = view; - - UITapGestureRecognizer *doubleFingerTap = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(zoomInOut:)]; - [doubleFingerTap setNumberOfTapsRequired:2]; - [doubleFingerTap setNumberOfTouchesRequired:1]; - [videoView addGestureRecognizer:doubleFingerTap]; - - UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(videoPan:)]; - [videoView addGestureRecognizer:pan]; - UIPinchGestureRecognizer *pinchReco = - [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)]; - [videoView addGestureRecognizer:pinchReco]; - - [self resetZoom]; -} - -@end diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index a45b51376..49f6aa3f8 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -374,7 +374,10 @@ static RootViewManager *rootViewManagerInstance = nil; } break; } - case LinphoneCallOutgoingInit: { + case LinphoneCallOutgoingInit: + case LinphoneCallOutgoingEarlyMedia: + case LinphoneCallOutgoingProgress: + case LinphoneCallOutgoingRinging: { OutgoingCallView *v = VIEW(OutgoingCallView); [self changeCurrentView:OutgoingCallView.compositeViewDescription]; [v setCallWithCall:call]; @@ -395,10 +398,6 @@ static RootViewManager *rootViewManagerInstance = nil; case LinphoneCallEarlyUpdating: case LinphoneCallIdle: break; - case LinphoneCallOutgoingEarlyMedia: - case LinphoneCallOutgoingProgress: { - break; - } case LinphoneCallReleased: if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -407,7 +406,6 @@ static RootViewManager *rootViewManagerInstance = nil; }); } break; - case LinphoneCallOutgoingRinging: case LinphoneCallPaused: case LinphoneCallPausing: case LinphoneCallRefered: diff --git a/Classes/SideMenuTableView.m b/Classes/SideMenuTableView.m index 67ca16831..a70462fde 100644 --- a/Classes/SideMenuTableView.m +++ b/Classes/SideMenuTableView.m @@ -27,6 +27,7 @@ #import "ShopView.h" #import "LinphoneManager.h" #import "RecordingsListView.h" +#import "linphoneapp-Swift.h" @implementation SideMenuEntry @@ -101,6 +102,15 @@ changeCurrentView:ShopView.compositeViewDescription]; }]]; } + + [_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"Conferences", nil) + image:[UIImage imageNamed:@"voip_conference_new.png"] + tapBlock:^() { + [PhoneMainView.instance + changeCurrentView:ScheduledConferencesView.compositeViewDescription]; + + }]]; + [_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"About", nil) image:[UIImage imageNamed:@"menu_about.png"] tapBlock:^() { diff --git a/Classes/AppManager.swift b/Classes/Swift/AppManager.swift similarity index 100% rename from Classes/AppManager.swift rename to Classes/Swift/AppManager.swift diff --git a/Classes/CallManager.swift b/Classes/Swift/CallManager.swift similarity index 97% rename from Classes/CallManager.swift rename to Classes/Swift/CallManager.swift index cc2e380e2..9d72c6bdb 100644 --- a/Classes/CallManager.swift +++ b/Classes/Swift/CallManager.swift @@ -231,6 +231,15 @@ import AVFoundation try? doCall(addr: sAddr, isSas: isSas) } } + + func startCall(addr:String, isSas: Bool = false) { + do { + let address = try Factory.Instance.createAddress(addr: addr) + startCall(addr: address.getCobject,isSas: isSas) + } catch { + Log.e("[CallManager] unable to create address for a new outgoing call : \(addr) \(error) ") + } + } func doCall(addr: Address, isSas: Bool) throws { let displayName = FastAddressBook.displayName(for: addr.getCobject) @@ -410,6 +419,12 @@ import AVFoundation let appData = CallAppData() CallManager.setAppData(sCall: call, appData: appData) } + + if let conference = call.conference, ConferenceViewModel.shared.conference.value == nil { + Log.i("[Call] Found conference attached to call and no conference in dedicated view model, init & configure it") + ConferenceViewModel.shared.initConference(conference) + ConferenceViewModel.shared.configureConference(conference) + } switch cstate { case .IncomingReceived: diff --git a/Classes/Conference/data/Duration.swift b/Classes/Swift/Conference/data/Duration.swift similarity index 100% rename from Classes/Conference/data/Duration.swift rename to Classes/Swift/Conference/data/Duration.swift diff --git a/Classes/Conference/data/ScheduledConferenceData.swift b/Classes/Swift/Conference/data/ScheduledConferenceData.swift similarity index 95% rename from Classes/Conference/data/ScheduledConferenceData.swift rename to Classes/Swift/Conference/data/ScheduledConferenceData.swift index 58b2019d0..0115c250c 100644 --- a/Classes/Conference/data/ScheduledConferenceData.swift +++ b/Classes/Swift/Conference/data/ScheduledConferenceData.swift @@ -36,6 +36,7 @@ class ScheduledConferenceData { let organizer = MutableLiveData() let participantsShort = MutableLiveData() let participantsExpanded = MutableLiveData() + let rawDate : Date init (conferenceInfo: ConferenceInfo) { @@ -48,7 +49,8 @@ class ScheduledConferenceData { time.value = TimestampUtils.timeToString(unixTimestamp: Double(conferenceInfo.dateTime)) date.value = TimestampUtils.toString(unixTimestamp:Double(conferenceInfo.dateTime), onlyDate:true, shortDate:false) - + rawDate = Date(timeIntervalSince1970:TimeInterval(conferenceInfo.dateTime)) + let durationFormatter = DateComponentsFormatter() durationFormatter.unitsStyle = .positional durationFormatter.allowedUnits = [.minute, .second ] @@ -76,4 +78,8 @@ class ScheduledConferenceData { String(describing: participant.addressBookEnhancedDisplayName())+" ("+String(describing: participant.asStringUriOnly())+")" }.joined(separator: "\n") } + + func gotoAssociatedChat() { + + } } diff --git a/Classes/Conference/data/TimeZoneData.swift b/Classes/Swift/Conference/data/TimeZoneData.swift similarity index 100% rename from Classes/Conference/data/TimeZoneData.swift rename to Classes/Swift/Conference/data/TimeZoneData.swift diff --git a/Classes/Conference/viewmodels/ConferenceSchedulingViewModel.swift b/Classes/Swift/Conference/models/ConferenceSchedulingViewModel.swift similarity index 65% rename from Classes/Conference/viewmodels/ConferenceSchedulingViewModel.swift rename to Classes/Swift/Conference/models/ConferenceSchedulingViewModel.swift index d373f0673..22eb92419 100644 --- a/Classes/Conference/viewmodels/ConferenceSchedulingViewModel.swift +++ b/Classes/Swift/Conference/models/ConferenceSchedulingViewModel.swift @@ -22,7 +22,6 @@ import Foundation import linphonesw - class ConferenceSchedulingViewModel { let core = Core.get() @@ -45,54 +44,78 @@ class ConferenceSchedulingViewModel { let sendInviteViaChat = MutableLiveData() let sendInviteViaEmail = MutableLiveData() - + let address = MutableLiveData
() let conferenceCreationInProgress = MutableLiveData() - let conferenceCreationCompletedEvent: MutableLiveData = MediatorLiveData() + let conferenceCreationCompletedEvent: MutableLiveData> = MutableLiveData() let onErrorEvent = MutableLiveData() - let continueEnabled: MediatorLiveData = MediatorLiveData() + let continueEnabled: MutableLiveData = MutableLiveData() let selectedAddresses = MutableLiveData<[Address]>([]) + private let conferenceScheduler = try? Core.get().createConferenceScheduler() + private var hour: Int = 0 private var minutes: Int = 0 - private var coreDelegate : CoreDelegateStub? = nil private var chatRooomDelegate : ChatRoomDelegate? = nil + private var conferenceSchedulerDelegate : ConferenceSchedulerDelegateStub? = nil init () { - coreDelegate = CoreDelegateStub( - onConferenceStateChanged : { (core: Core, conference: Conference, state: Conference.State?) -> Void in - Log.i("[Conference Creation] Conference state changed: \(state)") - if (state == .CreationPending) { - Log.i("[Conference Creation] Conference address will be \(conference.conferenceAddress?.asStringUriOnly())") - self.address.value = conference.conferenceAddress + + conferenceSchedulerDelegate = ConferenceSchedulerDelegateStub( + onStateChanged: { scheduler, state in + Log.i("[Conference Creation] Conference scheduler state is \(state)") + if (state == .Ready) { + Log.i("[Conference Creation] Conference info created, address will be \(scheduler.info?.uri?.asStringUriOnly())") + guard let conferenceAddress = scheduler.info?.uri else { + Log.e("[Conference Creation] conference address is null") + return + } + self.address.value = conferenceAddress - if (self.scheduleForLater.value == true) { - self.sendConferenceInfo() + if (self.sendInviteViaChat.value == true) { + // Send conference info even when conf is not scheduled for later + // as the conference server doesn't invite participants automatically + if let chatRoomParams = try?self.core.createDefaultChatRoomParams() { + chatRoomParams.backend = ChatRoomBackend.FlexisipChat + chatRoomParams.groupEnabled = false + chatRoomParams.encryptionEnabled = true + chatRoomParams.subject = self.subject.value! + scheduler.sendInvitations(chatRoomParams: chatRoomParams) + } } else { self.conferenceCreationInProgress.value = false - self.conferenceCreationCompletedEvent.value = true + self.conferenceCreationCompletedEvent.value = Pair(conferenceAddress.asStringUriOnly(),self.conferenceScheduler?.info?.subject) } } - }, - onConferenceInfoOnSent : { (core: Core, conferenceInfo:ConferenceInfo) -> Void in + }, onInvitationsSent: { conferenceScheduler, failedInvitations in Log.i("[Conference Creation] Conference information successfully sent to all participants") self.conferenceCreationInProgress.value = false - self.conferenceCreationCompletedEvent.value = true - }, - onConferenceInfoOnParticipantError : { (core: Core, conferenceInfo: ConferenceInfo, participant: Address, error: ConferenceInfoError?) -> Void in - Log.e("[Conference Creation] Conference information wasn't sent to participant \(participant.asStringUriOnly())") - self.onErrorEvent.value = VoipTexts.conference_schedule_info_not_sent_to_participant - self.conferenceCreationInProgress.value = false + + if (failedInvitations.count > 0) { + failedInvitations.forEach { address in + Log.e("[Conference Creation] Conference information wasn't sent to participant \(address.asStringUriOnly())") + self.onErrorEvent.value = VoipTexts.conference_schedule_info_not_sent_to_participant+" (\(address.username))" + } + } + + guard let conferenceAddress = conferenceScheduler.info?.uri else { + Log.e("[Conference Creation] conference address is null") + return + } + self.conferenceCreationCompletedEvent.value = Pair(conferenceAddress.asStringUriOnly(),self.conferenceScheduler?.info?.subject) } ) - Core.get().addDelegate(delegate: coreDelegate!) + + + conferenceScheduler?.addDelegate(delegate: conferenceSchedulerDelegate!) + chatRooomDelegate = ChatRoomDelegateStub( onStateChanged : { (room: ChatRoom, state: ChatRoom.State) -> Void in @@ -134,7 +157,7 @@ class ConferenceSchedulingViewModel { let now = Date() scheduledTime.value = Calendar.current.date(from: Calendar.current.dateComponents([.hour, .minute, .second], from: now)) scheduledDate.value = Calendar.current.date(from: Calendar.current.dateComponents([.year, .month, .day], from: now)) - + scheduledTimeZone.value = ConferenceSchedulingViewModel.timeZones.indices.filter { ConferenceSchedulingViewModel.timeZones[$0].timeZone.identifier == NSTimeZone.default.identifier }.first @@ -149,10 +172,14 @@ class ConferenceSchedulingViewModel { func destroy() { - core.removeDelegate(delegate: coreDelegate!) + conferenceScheduler?.removeDelegate(delegate: conferenceSchedulerDelegate!) } + func gotoChatRoom() { + + } + func createConference() { @@ -163,8 +190,12 @@ class ConferenceSchedulingViewModel { do { conferenceCreationInProgress.value = true - let localAddress = core.defaultAccount?.params?.identityAddress + guard let localAddress = core.defaultAccount?.params?.identityAddress else { + Log.e("[Conference Creation] Couldn't get local address from default account!") + return + } + /* // TODO: Temporary workaround for chat room, to be removed once we can get matching chat room from conference let chatRoomParams = try core.createDefaultChatRoomParams() chatRoomParams.backend = ChatRoomBackend.FlexisipChat @@ -173,16 +204,21 @@ class ConferenceSchedulingViewModel { let chatRoom = try core.createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: selectedAddresses.value!) Log.i("[Conference Creation] Creating chat room with same subject [\(subject.value)] & participants as for conference") chatRoom.addDelegate(delegate: chatRooomDelegate!) - let params = try core.createConferenceParams() - params.videoEnabled = true // TODO: Keep this to true ? - params.subject = subject.value! - let startTime = getConferenceStartTimestamp() - params.startTime = time_t(startTime) + // END OF TODO +*/ - scheduledDuration.value.map { - params.endTime = params.startTime + $0 + let conferenceInfo = try Factory.Instance.createConferenceInfo() + conferenceInfo.organizer = localAddress + subject.value.map { conferenceInfo.subject = $0} + description.value.map { conferenceInfo.description = $0} + conferenceInfo.participants = selectedAddresses.value! + if (scheduleForLater.value == true) { + let timestamp = getConferenceStartTimestamp() + conferenceInfo.dateTime = time_t(timestamp) + scheduledDuration.value.map {conferenceInfo.duration = UInt($0) } } - try core.createConferenceOnServer(params: params, localAddr: localAddress, participants: selectedAddresses.value!) + conferenceScheduler?.info = conferenceInfo // Will trigger the conference creation automatically + } catch { Log.e("[Conference Creation] Failed \(error)") } @@ -194,30 +230,7 @@ class ConferenceSchedulingViewModel { return subject.value != nil && subject.value!.count > 0 && (scheduleForLater.value != true || (scheduledDate.value != nil && scheduledTime.value != nil)); } - private func sendConferenceInfo() { - let participants :[Address] = [] - - do { - let conferenceInfo = try Factory.Instance.createConferenceInfo() - conferenceInfo.uri = try Factory.Instance.createAddress(addr: "sip:video-conference-0@sip.linphone.org") // TODO: use address.value - conferenceInfo.participants = participants - conferenceInfo.organizer = core.defaultAccount?.params?.identityAddress - subject.value.map { conferenceInfo.subject = $0} - description.value.map { conferenceInfo.description = $0} - scheduledDuration.value.map {conferenceInfo.duration = $0 } - let timestamp = getConferenceStartTimestamp() - conferenceInfo.dateTime = time_t(timestamp) - - Log.i("[Conference Creation] Conference date & time set to ${TimestampUtils.dateToString(timestamp)} ${TimestampUtils.timeToString(timestamp)}, duration = ${conferenceInfo.duration}") - core.sendConferenceInformation(conferenceInformation: conferenceInfo, text: "") - - conferenceCreationInProgress.value = false - conferenceCreationCompletedEvent.value = true - } catch { - Log.e("[Conference Creation] unable to create conference \(error)") - } - } - + private func getConferenceStartTimestamp() -> Double { return scheduleForLater.value == true ? scheduledDate.value!.timeIntervalSince1970 + scheduledTime.value!.timeIntervalSince1970 : Date().timeIntervalSince1970 diff --git a/Classes/Swift/Conference/models/ScheduledConferencesViewModel.swift b/Classes/Swift/Conference/models/ScheduledConferencesViewModel.swift new file mode 100644 index 000000000..15a69d549 --- /dev/null +++ b/Classes/Swift/Conference/models/ScheduledConferencesViewModel.swift @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +import Foundation +import linphonesw + + +class ScheduledConferencesViewModel { + + let core = Core.get() + static let shared = ScheduledConferencesViewModel() + + var conferences : MutableLiveData<[ScheduledConferenceData]> = MutableLiveData([]) + var daySplitted : [Date : [ScheduledConferenceData]] = [:] + var coreDelegate: CoreDelegateStub? + + init () { + + coreDelegate = CoreDelegateStub( + onConferenceInfoReceived: { (core, conferenceInfo) in + Log.i("[Scheduled Conferences] New conference info received") + self.conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo)) + self.conferences.notifyValue() + } + + ) + + computeConferenceInfoList() + } + + func computeConferenceInfoList() { + conferences.value!.removeAll() + core.futureConferenceInformationList.forEach { conferenceInfo in // Sorted in the sdk + conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo)) + } + + daySplitted = [:] + conferences.value!.forEach { (conferenceInfo) in + let startDateDay = dateAtBeginningOfDay(for: conferenceInfo.rawDate) + if (daySplitted[startDateDay] == nil) { + daySplitted[startDateDay] = [] + } + daySplitted[startDateDay]!.append(conferenceInfo) + } + } + + + func dateAtBeginningOfDay(for inputDate: Date) -> Date { + var calendar = Calendar.current + let timeZone = NSTimeZone.system as NSTimeZone + calendar.timeZone = timeZone as TimeZone + return calendar.date(from: calendar.dateComponents([.year, .month, .day], from: inputDate))! + } + + +} diff --git a/Classes/Swift/Conference/views/ConferenceHistoryDetailsView.swift b/Classes/Swift/Conference/views/ConferenceHistoryDetailsView.swift new file mode 100644 index 000000000..2c38a8c47 --- /dev/null +++ b/Classes/Swift/Conference/views/ConferenceHistoryDetailsView.swift @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +import UIKit +import Foundation +import linphonesw + +@objc class ConferenceHistoryDetailsView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource { + + + let participantsListTableView = UITableView() + let conectionsListTableView = UITableView() + let participantsLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_participants_list) + let datePicker = StyledDatePicker(pickerMode: .date, readOnly:true) + let timePicker = StyledDatePicker(pickerMode: .time, readOnly:true) + + var conferenceData : ScheduledConferenceData? { + didSet { + if let data = conferenceData { + super.titleLabel.text = data.subject.value! + self.participantsListTableView.reloadData() + self.participantsListTableView.removeConstraints().done() + self.participantsListTableView.matchParentSideBorders().alignUnder(view: participantsLabel,withMargin: self.form_margin).done() + self.participantsListTableView.height(Double(data.conferenceInfo.participants.count) * VoipParticipantCell.cell_height).done() + datePicker.liveValue = MutableLiveData(conferenceData!.rawDate) + timePicker.liveValue = MutableLiveData(conferenceData!.rawDate) + } + } + } + + + static let compositeDescription = UICompositeViewDescription(ConferenceHistoryDetailsView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, fullscreen: false, isLeftFragment: false,fragmentWith: nil) + static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription } + func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription } + + override func viewDidLoad() { + + super.viewDidLoad( + backAction: { + PhoneMainView.instance().popView(self.compositeViewDescription()) + },nextAction: { + }, + nextActionEnableCondition: MutableLiveData(false), + title:"") + super.nextButton.isHidden = true + + + let schedulingStack = UIStackView() + schedulingStack.axis = .vertical + contentView.addSubview(schedulingStack) + schedulingStack.alignParentTop(withMargin: 2*form_margin).matchParentSideBorders(insetedByDx: form_margin).done() + + + let scheduleForm = UIView() + schedulingStack.addArrangedSubview(scheduleForm) + scheduleForm.matchParentSideBorders().done() + + // Left column (Date & Time) + let leftColumn = UIView() + scheduleForm.addSubview(leftColumn) + leftColumn.matchParentWidthDividedBy(2.2).alignParentLeft(withMargin: form_margin).alignParentTop(withMargin: form_margin).done() + + let dateLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_date) + leftColumn.addSubview(dateLabel) + dateLabel.alignParentLeft().alignParentTop(withMargin: form_margin).done() + + leftColumn.addSubview(datePicker) + datePicker.alignParentLeft().alignUnder(view: dateLabel,withMargin: form_margin).matchParentSideBorders().done() + + leftColumn.wrapContentY().done() + + // Right column (Duration & Timezone) + let rightColumn = UIView() + scheduleForm.addSubview(rightColumn) + rightColumn.matchParentWidthDividedBy(2.2).alignParentRight(withMargin: form_margin).alignParentTop().done() + + let timeLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_time) + rightColumn.addSubview(timeLabel) + timeLabel.alignParentLeft().alignUnder(view: datePicker,withMargin: form_margin).done() + + rightColumn.addSubview(timePicker) + timePicker.alignParentLeft().alignUnder(view: timeLabel,withMargin: form_margin).matchParentSideBorders().done() + + + rightColumn.wrapContentY().done() + + + scheduleForm.wrapContentY().done() + + // Participants + participantsLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get() + contentView.addSubview(participantsLabel) + participantsLabel.matchParentSideBorders().height(form_input_height).alignUnder(view: schedulingStack,withMargin: form_margin*2).done() + participantsLabel.textAlignment = .left + + contentView.addSubview(participantsListTableView) + participantsListTableView.isScrollEnabled = false + participantsListTableView.dataSource = self + participantsListTableView.register(VoipParticipantCell.self, forCellReuseIdentifier: "VoipParticipantCellSSchedule") + participantsListTableView.allowsSelection = false + if #available(iOS 15.0, *) { + participantsListTableView.allowsFocus = false + } + participantsListTableView.separatorStyle = .singleLine + participantsListTableView.separatorColor = VoipTheme.light_grey_color + + + + // Goto chat + let chatButton = FormButton(title: VoipTexts.conference_go_to_chat.uppercased(), backgroundStateColors: VoipTheme.primary_colors_background) + contentView.addSubview(chatButton) + chatButton.onClick { + //let chatRoom = ChatRoom() + //PhoneMainView.instance().go(to: chatRoom?.getCobject) + } + + chatButton.centerX().alignParentBottom(withMargin: 3*self.form_margin).alignUnder(view: participantsListTableView,withMargin: 3*self.form_margin).done() + + } + + + // Objc - bridge, as can't access easily to the view model. + @objc func setCallLog(callLog:OpaquePointer) { + // TODO when available : create view model from the conference that should be retreivable via call log + } + + + // TableView datasource delegate + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let data = conferenceData else { + return 0 + } + return data.conferenceInfo.participants.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell:VoipParticipantCell = tableView.dequeueReusableCell(withIdentifier: "VoipParticipantCellSSchedule") as! VoipParticipantCell + guard let data = conferenceData else { + return cell + } + cell.selectionStyle = .none + cell.scheduleConfParticipantAddress = data.conferenceInfo.participants[indexPath.row] + cell.limeBadge.isHidden = true + return cell + } + + +} diff --git a/Classes/Conference/views/ConferenceSchedulingSummaryView.swift b/Classes/Swift/Conference/views/ConferenceSchedulingSummaryView.swift similarity index 86% rename from Classes/Conference/views/ConferenceSchedulingSummaryView.swift rename to Classes/Swift/Conference/views/ConferenceSchedulingSummaryView.swift index 2d809e857..cabdebde2 100644 --- a/Classes/Conference/views/ConferenceSchedulingSummaryView.swift +++ b/Classes/Swift/Conference/views/ConferenceSchedulingSummaryView.swift @@ -21,8 +21,11 @@ import UIKit import Foundation import linphonesw +import SVProgressHUD -@objc class ConferenceSchedulingSummaryView: NavigationView, UICompositeViewDelegate, UITableViewDataSource { +@objc class ConferenceSchedulingSummaryView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource { + + let CONFERENCE_CREATION_TIME_OUT_SEC = 15.0 let viewModel = ConferenceSchedulingViewModel.shared let participantsListTableView = UITableView() @@ -166,14 +169,52 @@ import linphonesw } // Create / Schedule - let createButton = FormButton() + let createButton = FormButton(backgroundStateColors: VoipTheme.primary_colors_background) contentView.addSubview(createButton) + viewModel.scheduleForLater.readCurrentAndObserve { _ in + createButton.title = self.viewModel.scheduleForLater.value == true ? VoipTexts.conference_schedule.uppercased() : VoipTexts.conference_schedule_create.uppercased() + createButton.addSidePadding() + } + + self.viewModel.conferenceCreationInProgress.observe { progress in + if (progress == true) { + SVProgressHUD.show() + } else { + SVProgressHUD.dismiss() + } + } + + var enableCreationTimeOut = false + + viewModel.conferenceCreationCompletedEvent.observe { pair in + enableCreationTimeOut = false + if (self.viewModel.scheduleForLater.value == true) { + PhoneMainView.instance().pop(toView:ScheduledConferencesView.compositeDescription) + } else { + let view: ConferenceWaitingRoomFragment = self.VIEW(ConferenceWaitingRoomFragment.compositeViewDescription()); + PhoneMainView.instance().pop(toView:view.compositeViewDescription()) + view.setDetails(subject: pair!.second!, url: pair!.first!) + } + } + viewModel.onErrorEvent.observe { error in + VoipDialog.init(message: error!).show() + } createButton.onClick { + enableCreationTimeOut = true self.viewModel.createConference() + DispatchQueue.main.asyncAfter(deadline: .now() + self.CONFERENCE_CREATION_TIME_OUT_SEC) { + if (enableCreationTimeOut) { + enableCreationTimeOut = false + self.viewModel.conferenceCreationInProgress.value = false + self.viewModel.onErrorEvent.value = VoipTexts.call_error_server_timeout + } + } } viewModel.scheduleForLater.readCurrentAndObserve { _ in createButton.title = self.viewModel.scheduleForLater.value == true ? VoipTexts.conference_schedule.uppercased() : VoipTexts.conference_schedule_create.uppercased() + createButton.addSidePadding() } + createButton.centerX().alignParentBottom(withMargin: 3*self.form_margin).alignUnder(view: participantsListTableView,withMargin: 3*self.form_margin).done() } diff --git a/Classes/Conference/views/ConferenceSchedulingView.swift b/Classes/Swift/Conference/views/ConferenceSchedulingView.swift similarity index 98% rename from Classes/Conference/views/ConferenceSchedulingView.swift rename to Classes/Swift/Conference/views/ConferenceSchedulingView.swift index a471138f8..a64402d96 100644 --- a/Classes/Conference/views/ConferenceSchedulingView.swift +++ b/Classes/Swift/Conference/views/ConferenceSchedulingView.swift @@ -22,7 +22,7 @@ import UIKit import Foundation import linphonesw -@objc class ConferenceSchedulingView: NavigationView, UICompositeViewDelegate { +@objc class ConferenceSchedulingView: BackNextNavigationView, UICompositeViewDelegate { let viewModel = ConferenceSchedulingViewModel.shared @@ -195,6 +195,7 @@ import linphonesw view.tableController.contactsGroup = (addresses as NSArray).mutableCopy() as? NSMutableArray view.isForEditing = false view.isForVoipConference = true + view.isForOngoingVoipConference = false view.tableController.notFirstTime = true view.isGroupChat = true PhoneMainView.instance().changeCurrentView(view.compositeViewDescription()) diff --git a/Classes/Swift/Conference/views/ConferenceWaitingRoomFragment.swift b/Classes/Swift/Conference/views/ConferenceWaitingRoomFragment.swift new file mode 100644 index 000000000..867ca5f31 --- /dev/null +++ b/Classes/Swift/Conference/views/ConferenceWaitingRoomFragment.swift @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +import UIKit +import linphonesw + + +@objc class ConferenceWaitingRoomFragment: UIViewController, UICompositeViewDelegate { // Replaces CallView + + // Layout constants + let common_margin = 17.0 + let switch_camera_button_size = 50 + let switch_camera_button_margins = 7.0 + let content_inset = 12.0 + let button_spacing = 15.0 + let center_view_corner_radius = 20.0 + let button_width = 150 + + + var audioRoutesView : AudioRoutesView? = nil + let subject = StyledLabel(VoipTheme.conference_preview_subject_font) + let localVideo = UIView() + let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white)) + let buttonsView = UIStackView() + let cancel = FormButton(title: VoipTexts.cancel.uppercased(), backgroundStateColors: VoipTheme.primary_colors_background_gray, bold:false) + let start = FormButton(title: VoipTexts.conference_waiting_room_start_call.uppercased(), backgroundStateColors: VoipTheme.primary_colors_background) + + var conferenceUrl : String? = nil + let conferenceSubject = MutableLiveData() + + + static let compositeDescription = UICompositeViewDescription(ConferenceWaitingRoomFragment.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil) + static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription } + func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = VoipTheme.voipBackgroundColor.get() + + view.addSubview(subject) + subject.centerX().alignParentTop(withMargin: common_margin).done() + conferenceSubject.observe { subject in + self.subject.text = subject + } + + // Controls + let controlsView = ControlsView(showVideo: true) + view.addSubview(controlsView) + controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() + + + // Form buttons + buttonsView.axis = .horizontal + buttonsView.spacing = button_spacing + view.addSubview(buttonsView) + buttonsView.alignAbove(view: controlsView,withMargin: SharedLayoutConstants.buttons_bottom_margin).centerX().done() + + start.width(button_width).done() + cancel.width(button_width).done() + + buttonsView.addArrangedSubview(cancel) + buttonsView.addArrangedSubview(start) + + cancel.onClick { + PhoneMainView.instance().popView(self.compositeViewDescription()) + } + + start.onClick { + self.conferenceUrl.map{ CallManager.instance().startCall(addr: $0, isSas: false) } + } + + + // localVideo view + localVideo.layer.cornerRadius = center_view_corner_radius + localVideo.clipsToBounds = true + localVideo.backgroundColor = .black + self.view.addSubview(localVideo) + localVideo.matchParentSideBorders(insetedByDx: content_inset).alignAbove(view:buttonsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).alignUnder(view: subject,withMargin: common_margin).done() + localVideo.addSubview(switchCamera) + switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins).square(switch_camera_button_size).done() + switchCamera.contentMode = .scaleAspectFit + switchCamera.onClick { + Core.get().videoPreviewEnabled = false + Core.get().toggleCamera() + Core.get().nativePreviewWindow = self.localVideo + Core.get().videoPreviewEnabled = true + } + + // Audio Routes + audioRoutesView = AudioRoutesView() + view.addSubview(audioRoutesView!) + audioRoutesView!.alignBottomWith(otherView: controlsView).done() + ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (audioRoutesSelected) in + self.audioRoutesView!.isHidden = audioRoutesSelected != true + } + audioRoutesView!.alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() + + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(true) + ControlsViewModel.shared.audioRoutesSelected.value = false + Core.get().nativePreviewWindow = localVideo + Core.get().videoPreviewEnabled = true + } + + override func viewWillDisappear(_ animated: Bool) { + ControlsViewModel.shared.fullScreenMode.value = false + Core.get().nativePreviewWindow = nil + Core.get().videoPreviewEnabled = false + super.viewWillDisappear(animated) + } + + @objc func setDetails(subject:String, url:String) { + self.conferenceSubject.value = subject + self.conferenceUrl = url + } + +} diff --git a/Classes/Swift/Conference/views/ICSBubbleView.swift b/Classes/Swift/Conference/views/ICSBubbleView.swift new file mode 100644 index 000000000..7b352ccba --- /dev/null +++ b/Classes/Swift/Conference/views/ICSBubbleView.swift @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import UIKit +import Foundation +import linphonesw + +@objc class ICSBubbleView: UIView { + + let corner_radius = 7.0 + let border_width = 2.0 + let rows_spacing = 6.0 + let inner_padding = 8.0 + let indicator_y = 3.0 + let share_size = 25 + let join_share_width = 150.0 + + + let inviteTitle = StyledLabel(VoipTheme.conference_invite_title_font, VoipTexts.conference_invite_title) + let subject = StyledLabel(VoipTheme.conference_invite_subject_font) + let participants = StyledLabel(VoipTheme.conference_invite_desc_font) + let date = StyledLabel(VoipTheme.conference_invite_desc_font) + let timeDuration = StyledLabel(VoipTheme.conference_invite_desc_font) + let descriptionTitle = StyledLabel(VoipTheme.conference_invite_desc_title_font, VoipTexts.conference_description_title) + let descriptionValue = StyledLabel(VoipTheme.conference_invite_desc_font) + let joinShare = UIStackView() + let join = FormButton(title:VoipTexts.conference_invite_join.uppercased(), backgroundStateColors: VoipTheme.button_green_background) + let share = UIImageView(image:UIImage(named:"voip_export")?.tinted(with: VoipTheme.primaryTextColor.get())) + + var icsFile : String? = nil + + var conferenceData: ScheduledConferenceData? = nil { + didSet { + if let data = conferenceData { + subject.text = data.subject.value + participants.text = VoipTexts.conference_invite_participants_count.replacingOccurrences(of: "%d", with: String(data.conferenceInfo.participants.count+1)) + participants.addIndicatorIcon(iconName: "conference_schedule_participants_default",padding : 0.0, y: -indicator_y, trailing: false) + date.text = " "+TimestampUtils.dateToString(date: data.rawDate) + date.addIndicatorIcon(iconName: "conference_schedule_calendar_default", padding: 0.0, y:-indicator_y, trailing:false) + timeDuration.text = " \(data.time.value) ( \(data.duration.value) )" + timeDuration.addIndicatorIcon(iconName: "conference_schedule_time_default",padding : 0.0, y: -indicator_y, trailing: false) + descriptionTitle.isHidden = data.description.value == nil || data.description.value!.count == 0 + descriptionValue.isHidden = descriptionTitle.isHidden + descriptionValue.text = data.description.value + } + } + } + + init() { + super.init(frame:.zero) + + layer.cornerRadius = corner_radius + clipsToBounds = true + backgroundColor = VoipTheme.voip_light_gray + + let rows = UIStackView() + rows.axis = .vertical + rows.spacing = rows_spacing + + addSubview(rows) + + rows.addArrangedSubview(inviteTitle) + rows.addArrangedSubview(subject) + rows.addArrangedSubview(participants) + rows.addArrangedSubview(date) + rows.addArrangedSubview(timeDuration) + rows.addArrangedSubview(descriptionTitle) + rows.addArrangedSubview(descriptionValue) + + + addSubview(joinShare) + joinShare.axis = .horizontal + joinShare.spacing = rows_spacing + joinShare.addArrangedSubview(share) + share.square(share_size).done() + joinShare.addArrangedSubview(join) + rows.matchParentSideBorders(insetedByDx: inner_padding).alignParentTop(withMargin: inner_padding).done() + joinShare.alignParentBottom(withMargin: inner_padding).width(join_share_width).alignParentRight(withMargin: inner_padding).done() + + join.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)!) + } + + share.onClick { + let ics = URL(string: "file://"+self.icsFile!) + UIApplication.shared.open(ics!) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func setFromChatMessage(cmessage: OpaquePointer) { + let message = ChatMessage.getSwiftObject(cObject: cmessage) + message.contents.forEach { content in + if (content.isIcalendar) { + if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: content) { + self.conferenceData = ScheduledConferenceData(conferenceInfo: conferenceInfo) + self.icsFile = content.filePath + } + } + } + } + @objc static func isConferenceInvitationMessage(cmessage: OpaquePointer) -> Bool { + var isConferenceInvitationMessage = false + let message = ChatMessage.getSwiftObject(cObject: cmessage) + message.contents.forEach { content in + if (content.isIcalendar) { + isConferenceInvitationMessage = true + } + } + return isConferenceInvitationMessage + } + + @objc func setLayoutConstraints(view:UIView) { + matchDimensionsWith(view: view, insetedByDx: inner_padding).done() + } + +} diff --git a/Classes/Swift/Conference/views/ScheduledConferencesCell.swift b/Classes/Swift/Conference/views/ScheduledConferencesCell.swift new file mode 100644 index 000000000..0b5f21a87 --- /dev/null +++ b/Classes/Swift/Conference/views/ScheduledConferencesCell.swift @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +import UIKit +import Foundation +import linphonesw + +class ScheduledConferencesCell: UITableViewCell { + + let corner_radius = 7.0 + let border_width = 2.0 + + 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 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 urlValue = StyledLabel(VoipTheme.conference_scheduling_font) + let copyLink = CallControlButton(buttonTheme: VoipTheme.scheduled_conference_action("voip_copy")) + let joinEditDelete = UIView() + 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")) + + var conferenceData: ScheduledConferenceData? = nil { + didSet { + if let data = conferenceData { + timeDuration.text = "\(data.time) ( \(data.duration) )" + timeDuration.addIndicatorIcon(iconName: "conference_schedule_time_default", trailing: false) + 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.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) + } + } + } + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + contentView.layer.cornerRadius = corner_radius + contentView.clipsToBounds = true + 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) + + 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) + infoConf.onClick { + self.conferenceData?.toggleExpand() + } + + rows.addArrangedSubview(descriptionTitle) + rows.addArrangedSubview(descriptionValue) + + rows.addArrangedSubview(urlTitle) + urlAndCopy.addSubview(urlValue) + urlValue.backgroundColor = .white + 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; + */ + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Classes/Swift/Conference/views/ScheduledConferencesView.swift b/Classes/Swift/Conference/views/ScheduledConferencesView.swift new file mode 100644 index 000000000..f8139e375 --- /dev/null +++ b/Classes/Swift/Conference/views/ScheduledConferencesView.swift @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +import UIKit +import Foundation +import linphonesw + +@objc class ScheduledConferencesView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource { + + let conferenceListView = UITableView() + let noConference = StyledLabel(VoipTheme.empty_list_font,VoipTexts.conference_no_schedule) + + 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 } + func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription } + + override func viewDidLoad() { + + super.viewDidLoad( + backAction: { + PhoneMainView.instance().popView(self.compositeViewDescription()) + },nextAction: { + }, + nextActionEnableCondition: MutableLiveData(false), + title:VoipTexts.conference_scheduled) + super.nextButton.isHidden = true + + + contentView.addSubview(conferenceListView) + conferenceListView.isScrollEnabled = false + conferenceListView.dataSource = self + conferenceListView.register(ScheduledConferencesCell.self, forCellReuseIdentifier: "ScheduledConferencesCell") + conferenceListView.allowsSelection = false + if #available(iOS 15.0, *) { + conferenceListView.allowsFocus = false + } + conferenceListView.separatorStyle = .singleLine + conferenceListView.separatorColor = VoipTheme.light_grey_color + + view.addSubview(noConference) + noConference.center().done() + + + } + + + override func viewWillAppear(_ animated: Bool) { + ScheduledConferencesViewModel.shared.computeConferenceInfoList() + super.viewWillAppear(animated) + self.conferenceListView.reloadData() + self.conferenceListView.removeConstraints().done() + self.conferenceListView.matchParentSideBorders().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 day = daysArray[section] + return TimestampUtils.dateToString(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) + } + + func numberOfSections(in tableView: UITableView) -> Int { + return ScheduledConferencesViewModel.shared.daySplitted.keys.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys) + let day = daysArray[section] + return ScheduledConferencesViewModel.shared.daySplitted[day]!.count + } + + 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 day = daysArray[indexPath.section] + guard let data = ScheduledConferencesViewModel.shared.daySplitted[day]?[indexPath.row] else { + return cell + } + cell.conferenceData = data + return cell + } + + +} diff --git a/Classes/ConfigManager.swift b/Classes/Swift/ConfigManager.swift similarity index 100% rename from Classes/ConfigManager.swift rename to Classes/Swift/ConfigManager.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/OptionalExtensions.swift b/Classes/Swift/Extensions/IOS/OptionalExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/IOS/OptionalExtensions.swift rename to Classes/Swift/Extensions/IOS/OptionalExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/UIApplication+Extension.swift b/Classes/Swift/Extensions/IOS/UIApplication+Extension.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/IOS/UIApplication+Extension.swift rename to Classes/Swift/Extensions/IOS/UIApplication+Extension.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/UIButtonExtensions.swift b/Classes/Swift/Extensions/IOS/UIButtonExtensions.swift similarity index 74% rename from Classes/SwiftUtil/Extensions/IOS/UIButtonExtensions.swift rename to Classes/Swift/Extensions/IOS/UIButtonExtensions.swift index 2e7825594..5ceed069a 100644 --- a/Classes/SwiftUtil/Extensions/IOS/UIButtonExtensions.swift +++ b/Classes/Swift/Extensions/IOS/UIButtonExtensions.swift @@ -27,4 +27,14 @@ extension UIButton { width(w+p).done() } } + + func applyTintedIcons(tintedIcons: [UInt: TintableIcon]) { + tintedIcons.keys.forEach { (stateRawValue) in + let tintedIcon = tintedIcons[stateRawValue]! + UIImage(named:tintedIcon.name).map { + setImage($0.tinted(with: tintedIcon.tintColor?.get()),for: UIButton.State(rawValue: stateRawValue)) + } + } + } + } diff --git a/Classes/SwiftUtil/Extensions/IOS/UIColorExtensions.swift b/Classes/Swift/Extensions/IOS/UIColorExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/IOS/UIColorExtensions.swift rename to Classes/Swift/Extensions/IOS/UIColorExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/UIDeviceExtensions.swift b/Classes/Swift/Extensions/IOS/UIDeviceExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/IOS/UIDeviceExtensions.swift rename to Classes/Swift/Extensions/IOS/UIDeviceExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/UIImageExtensions.swift b/Classes/Swift/Extensions/IOS/UIImageExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/IOS/UIImageExtensions.swift rename to Classes/Swift/Extensions/IOS/UIImageExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/UIImageViewExtensions.swift b/Classes/Swift/Extensions/IOS/UIImageViewExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/IOS/UIImageViewExtensions.swift rename to Classes/Swift/Extensions/IOS/UIImageViewExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/UILabelExtensions.swift b/Classes/Swift/Extensions/IOS/UILabelExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/IOS/UILabelExtensions.swift rename to Classes/Swift/Extensions/IOS/UILabelExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/UIVIewControllerExtensions.swift b/Classes/Swift/Extensions/IOS/UIVIewControllerExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/IOS/UIVIewControllerExtensions.swift rename to Classes/Swift/Extensions/IOS/UIVIewControllerExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/IOS/UIVIewExtensions.swift b/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift similarity index 94% rename from Classes/SwiftUtil/Extensions/IOS/UIVIewExtensions.swift rename to Classes/Swift/Extensions/IOS/UIVIewExtensions.swift index d557a2294..46008f213 100644 --- a/Classes/SwiftUtil/Extensions/IOS/UIVIewExtensions.swift +++ b/Classes/Swift/Extensions/IOS/UIVIewExtensions.swift @@ -106,6 +106,22 @@ extension UIView { return self } + func matchParentDimmensions(insetedByDx:CGFloat) -> UIView { + snp.makeConstraints { (make) in + make.left.top.equalToSuperview().offset(insetedByDx) + make.right.bottom.equalToSuperview().offset(-insetedByDx) + } + return self + } + + func matchDimensionsWith(view:UIView, insetedByDx:CGFloat = 0) -> UIView { + snp.makeConstraints { (make) in + make.left.top.equalTo(view).offset(insetedByDx) + make.right.bottom.equalTo(view).offset(-insetedByDx) + } + return self + } + func matchParentEdges() -> UIView { snp.makeConstraints { (make) in make.edges.equalToSuperview() diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/AddressExtensions.swift b/Classes/Swift/Extensions/LinphoneCore/AddressExtensions.swift similarity index 90% rename from Classes/SwiftUtil/Extensions/LinphoneCore/AddressExtensions.swift rename to Classes/Swift/Extensions/LinphoneCore/AddressExtensions.swift index 39bd741f8..d4aff4ef7 100644 --- a/Classes/SwiftUtil/Extensions/LinphoneCore/AddressExtensions.swift +++ b/Classes/Swift/Extensions/LinphoneCore/AddressExtensions.swift @@ -39,7 +39,9 @@ extension Address { } func addressBookEnhancedDisplayName() -> String? { - if let contact = FastAddressBook.getContactWith(getCobject) { + if (username == Core.get().defaultAccount?.contactAddress?.username) { + return VoipTexts.me + } else if let contact = FastAddressBook.getContactWith(getCobject) { return contact.displayName } else if (!displayName.isEmpty) { return displayName diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/CallExtensions.swift b/Classes/Swift/Extensions/LinphoneCore/CallExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/LinphoneCore/CallExtensions.swift rename to Classes/Swift/Extensions/LinphoneCore/CallExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/ConferenceExtensions.swift b/Classes/Swift/Extensions/LinphoneCore/ConferenceExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/LinphoneCore/ConferenceExtensions.swift rename to Classes/Swift/Extensions/LinphoneCore/ConferenceExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/CoreExtensions.swift b/Classes/Swift/Extensions/LinphoneCore/CoreExtensions.swift similarity index 96% rename from Classes/SwiftUtil/Extensions/LinphoneCore/CoreExtensions.swift rename to Classes/Swift/Extensions/LinphoneCore/CoreExtensions.swift index ee0c99edb..f9a4c15f1 100644 --- a/Classes/SwiftUtil/Extensions/LinphoneCore/CoreExtensions.swift +++ b/Classes/Swift/Extensions/LinphoneCore/CoreExtensions.swift @@ -30,6 +30,9 @@ extension Core { } func toggleCamera() { + + UICamSwitch.switchCamera() + /* Not working Log.i("[Core] Current camera device is \(videoDevice)") videoDevicesList.forEach { @@ -43,6 +46,6 @@ extension Core { let inConference = conference != nil && conference!.isIn if !inConference, let call = currentCall { try?call.update(params: nil) - } + }*/ } } diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/IceState.swift b/Classes/Swift/Extensions/LinphoneCore/IceState.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/LinphoneCore/IceState.swift rename to Classes/Swift/Extensions/LinphoneCore/IceState.swift diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/ParticipantExtensions.swift b/Classes/Swift/Extensions/LinphoneCore/ParticipantExtensions.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/LinphoneCore/ParticipantExtensions.swift rename to Classes/Swift/Extensions/LinphoneCore/ParticipantExtensions.swift diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/PayloadType.swift b/Classes/Swift/Extensions/LinphoneCore/PayloadType.swift similarity index 100% rename from Classes/SwiftUtil/Extensions/LinphoneCore/PayloadType.swift rename to Classes/Swift/Extensions/LinphoneCore/PayloadType.swift diff --git a/Classes/ProviderDelegate.swift b/Classes/Swift/ProviderDelegate.swift similarity index 100% rename from Classes/ProviderDelegate.swift rename to Classes/Swift/ProviderDelegate.swift diff --git a/Classes/SwiftUtil/GenericViews/NavigationView.swift b/Classes/Swift/Util/BackNextNavigationView.swift similarity index 97% rename from Classes/SwiftUtil/GenericViews/NavigationView.swift rename to Classes/Swift/Util/BackNextNavigationView.swift index a44bf3d89..ca61e6b20 100644 --- a/Classes/SwiftUtil/GenericViews/NavigationView.swift +++ b/Classes/Swift/Util/BackNextNavigationView.swift @@ -22,11 +22,11 @@ import UIKit import Foundation import linphonesw -@objc class NavigationView: UIViewController { +@objc class BackNextNavigationView: UIViewController { // layout constants - let top_bar_height = 60.0 + let top_bar_height = 66.0 let navigation_buttons_padding = 18.0 let content_margin_top = 20 @@ -35,9 +35,7 @@ import linphonesw let form_input_height = 40.0 let schdule_for_later_height = 80.0 let description_height = 150.0 - - - + let titleLabel = StyledLabel(VoipTheme.calls_list_header_font) let topBar = UIView() diff --git a/Classes/SwiftUtil/ViewModel/MutableLiveData.swift b/Classes/Swift/Util/MutableLiveData.swift similarity index 100% rename from Classes/SwiftUtil/ViewModel/MutableLiveData.swift rename to Classes/Swift/Util/MutableLiveData.swift diff --git a/Classes/Swift/Util/Pair.swift b/Classes/Swift/Util/Pair.swift new file mode 100644 index 000000000..f49b6fa98 --- /dev/null +++ b/Classes/Swift/Util/Pair.swift @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2010-2020 Belledonne Communications SARL. +* +* This file is part of linhome +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + + +import Foundation + +class Pair { + var first:T1 + var second:T2 + + init(_ first:T1, _ second:T2) { + self.first = first + self.second = second + } + + +} diff --git a/Classes/SwiftUtil/TimestampUtils.swift b/Classes/Swift/Util/TimestampUtils.swift similarity index 100% rename from Classes/SwiftUtil/TimestampUtils.swift rename to Classes/Swift/Util/TimestampUtils.swift diff --git a/Classes/SwiftUtil/ViewModel/MediatorLiveData.swift b/Classes/Swift/Util/ViewModel/MediatorLiveData.swift similarity index 100% rename from Classes/SwiftUtil/ViewModel/MediatorLiveData.swift rename to Classes/Swift/Util/ViewModel/MediatorLiveData.swift diff --git a/Classes/VFSUtil.swift b/Classes/Swift/VFSUtil.swift similarity index 99% rename from Classes/VFSUtil.swift rename to Classes/Swift/VFSUtil.swift index 79343d154..5047fd21c 100644 --- a/Classes/VFSUtil.swift +++ b/Classes/Swift/VFSUtil.swift @@ -187,7 +187,7 @@ import os return false } guard let secret = decrypt(encryptedText: encryptedKey) else { - log(log: "[VFS] Unable to decryt encrypted key.", level: .error) + log("[VFS] Unable to decryt encrypted key.", .error) return false } Factory.Instance.setVfsEncryption(encryptionModule: 2, secret: secret, secretSize: 32) diff --git a/Classes/Voip/AudioRouteUtils.swift b/Classes/Swift/Voip/AudioRouteUtils.swift similarity index 100% rename from Classes/Voip/AudioRouteUtils.swift rename to Classes/Swift/Voip/AudioRouteUtils.swift diff --git a/Classes/Voip/Models/CallData.swift b/Classes/Swift/Voip/Models/CallData.swift similarity index 78% rename from Classes/Voip/Models/CallData.swift rename to Classes/Swift/Voip/Models/CallData.swift index 49f5fa2a5..fb69db380 100644 --- a/Classes/Voip/Models/CallData.swift +++ b/Classes/Swift/Voip/Models/CallData.swift @@ -54,7 +54,7 @@ class CallData { self.iFrameReceived.value = true }, onRemoteRecording: { (call: linphonesw.Call, recording:Bool) -> Void in - self.isRemotelyRecorded.value = true + self.isRemotelyRecorded.value = recording } ) call.addDelegate(delegate: callDelegate!) @@ -105,8 +105,13 @@ class CallData { } private func initChatRoom() { + + return // V1 work around + let localSipUri = Core.get().defaultAccount?.params?.identityAddress?.asStringUriOnly() let remoteSipUri = call.remoteAddress?.asStringUriOnly() + let conference = call.conference + guard let localSipUri = Core.get().defaultAccount?.params?.identityAddress?.asStringUriOnly(), @@ -118,15 +123,31 @@ class CallData { return } do { - chatRoom = Core.get().searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: remoteSipAddress, participants: []) - if (chatRoom == nil) { - chatRoom = Core.get().searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: nil, participants: [remoteSipAddress]) + if let conferenceInfo = Core.get().findConferenceInformationFromUri(uri: call.remoteAddress!), let params = try?Core.get().createDefaultChatRoomParams() { + params.subject = conferenceInfo.subject + params.backend = ChatRoomBackend.FlexisipChat + params.groupEnabled = true + chatRoom = Core.get().searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: conferenceInfo.participants) + } else { + chatRoom = Core.get().searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: remoteSipAddress, participants: []) + if (chatRoom == nil) { + chatRoom = Core.get().searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: nil, participants: [remoteSipAddress]) + } } + if (chatRoom == nil) { - Log.w("[Call] Failed to find existing chat room for local address [$localSipUri] and remote address [$remoteSipUri]") let chatRoomParams = try Core.get().createDefaultChatRoomParams() - // TODO: configure chat room params - chatRoom = try Core.get().createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: [remoteSipAddress]) + if let conferenceInfo = Core.get().findConferenceInformationFromUri(uri: call.remoteAddress!) { + Log.w("[Call] Failed to find existing chat room with same subject & participants, creating it") + chatRoomParams.backend = ChatRoomBackend.FlexisipChat + chatRoomParams.groupEnabled = true + chatRoomParams.subject = conferenceInfo.subject + chatRoom = try?Core.get().createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: conferenceInfo.participants) + } else { + Log.w("[Call] Failed to find existing chat room with same participants, creating it") + // TODO: configure chat room params + chatRoom = try?Core.get().createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: [remoteSipAddress]) + } } if (chatRoom == nil) { diff --git a/Classes/Voip/Models/CallStatisticsData.swift b/Classes/Swift/Voip/Models/CallStatisticsData.swift similarity index 100% rename from Classes/Voip/Models/CallStatisticsData.swift rename to Classes/Swift/Voip/Models/CallStatisticsData.swift diff --git a/Classes/Voip/Models/CallsViewModel.swift b/Classes/Swift/Voip/Models/CallsViewModel.swift similarity index 99% rename from Classes/Voip/Models/CallsViewModel.swift rename to Classes/Swift/Voip/Models/CallsViewModel.swift index 2caa75232..b539b8e5c 100644 --- a/Classes/Voip/Models/CallsViewModel.swift +++ b/Classes/Swift/Voip/Models/CallsViewModel.swift @@ -46,7 +46,7 @@ class CallsViewModel { let currentCall = core.currentCall if (currentCall != nil && self.currentCallData.value??.call.getCobject != currentCall?.getCobject) { self.updateCurrentCallData(currentCall: currentCall) - } else if (currentCall == nil && core.callsNb > 1) { + } else if (currentCall == nil && core.callsNb > 0) { self.updateCurrentCallData(currentCall: currentCall) } if ([.End,.Released,.Error].contains(state)) { diff --git a/Classes/Voip/Models/ConferenceParticipantData.swift b/Classes/Swift/Voip/Models/ConferenceParticipantData.swift similarity index 100% rename from Classes/Voip/Models/ConferenceParticipantData.swift rename to Classes/Swift/Voip/Models/ConferenceParticipantData.swift diff --git a/Classes/Voip/Models/ConferenceParticipantDeviceData.swift b/Classes/Swift/Voip/Models/ConferenceParticipantDeviceData.swift similarity index 100% rename from Classes/Voip/Models/ConferenceParticipantDeviceData.swift rename to Classes/Swift/Voip/Models/ConferenceParticipantDeviceData.swift diff --git a/Classes/Voip/Models/ConferenceViewModel.swift b/Classes/Swift/Voip/Models/ConferenceViewModel.swift similarity index 58% rename from Classes/Voip/Models/ConferenceViewModel.swift rename to Classes/Swift/Voip/Models/ConferenceViewModel.swift index 26206c306..0b0f4601a 100644 --- a/Classes/Voip/Models/ConferenceViewModel.swift +++ b/Classes/Swift/Voip/Models/ConferenceViewModel.swift @@ -27,60 +27,36 @@ class ConferenceViewModel { let core = Core.get() static let shared = ConferenceViewModel() - - let isConferencePaused = MutableLiveData() - let canResumeConference = MutableLiveData() - - let isMeConferenceFocus = MutableLiveData() + let conferenceExists = MutableLiveData() + let subject = MutableLiveData() + let isConferenceLocallyPaused = MutableLiveData() + let isVideoConference = MutableLiveData() let isMeAdmin = MutableLiveData() - - let conferenceAddress = MutableLiveData
() - + + let conference = MutableLiveData() let conferenceParticipants = MutableLiveData<[ConferenceParticipantData]>() let conferenceParticipantDevices = MutableLiveData<[ConferenceParticipantDeviceData]>() let conferenceDisplayMode = MutableLiveData() - - let isInConference = MutableLiveData() - - let isVideoConference = MutableLiveData() - let isRecording = MutableLiveData() let isRemotelyRecorded = MutableLiveData() - - let subject = MutableLiveData() - - let conference = MutableLiveData() - let maxParticipantsForMosaicLayout = ConfigManager.instance().lpConfigIntForKey(key: "max_conf_part_mosaic_layout",defaultValue: 6) + private var conferenceDelegate : ConferenceDelegateStub? private var coreDelegate : CoreDelegateStub? init () { - conferenceDelegate = ConferenceDelegateStub(onParticipantAdded: { (conference: Conference, participant: Participant)in - if (conference.isMe(uri: participant.address!)) { - Log.i("[Conference] \(conference) Entered conference") - self.isConferencePaused.value = false - } else { - Log.i("[Conference] \(conference) Participant \(participant) added") - } + conferenceDelegate = ConferenceDelegateStub(onParticipantAdded: { (conference: Conference, participant: Participant) in + Log.i("[Conference] \(conference) Participant \(participant) added") self.updateParticipantsList(conference) - self.updateParticipantsDevicesList(conference) - let count = self.conferenceParticipantDevices.value!.count if (count > self.maxParticipantsForMosaicLayout) { Log.w("[Conference] \(conference) More than \(self.maxParticipantsForMosaicLayout) participants \(count), forcing active speaker layout") self.conferenceDisplayMode.value = .ActiveSpeaker } }, onParticipantRemoved: {(conference: Conference, participant: Participant) in - if (conference.isMe(uri: participant.address!)) { - Log.i("[Conference] \(conference) \(participant) Left conference") - self.isConferencePaused.value = true - } else { - Log.i("[Conference] \(conference) \(participant) Participant removed") - } + Log.i("[Conference] \(conference) \(participant) Participant removed") self.updateParticipantsList(conference) - self.updateParticipantsDevicesList(conference) }, onParticipantDeviceAdded: {(conference: Conference, participantDevice: ParticipantDevice) in Log.i("[Conference] \(conference) Participant device \(participantDevice) added") self.updateParticipantsDevicesList(conference) @@ -91,43 +67,29 @@ class ConferenceViewModel { Log.i("[Conference] \(conference) Participant admin status changed") self.isMeAdmin.value = conference.me?.isAdmin self.updateParticipantsList(conference) + }, onParticipantDeviceLeft: { (conference: Conference, device: ParticipantDevice) in + Log.i("[Conference] onParticipantDeviceJoined Entered conference") + self.isConferenceLocallyPaused.value = true + }, onParticipantDeviceJoined: { (conference: Conference, device: ParticipantDevice) in + Log.i("[Conference] onParticipantDeviceJoined Entered conference") + self.isConferenceLocallyPaused.value = false + }, onSubjectChanged: { (conference: Conference, subject: String) in + self.subject.value = subject } ) coreDelegate = CoreDelegateStub( onConferenceStateChanged: { (core, conference, state) in Log.i("[Conference] \(conference) Conference state changed: \(state)") - self.isConferencePaused.value = !conference.isIn - self.canResumeConference.value = true // TODO: How can this value be false? self.isVideoConference.value = conference.currentParams?.isVideoEnabled == true if (state == Conference.State.Instantiated) { - self.conference.value = conference - self.isInConference.value = true - conference.addDelegate(delegate: self.conferenceDelegate!) + self.initConference(conference) } else if (state == Conference.State.Created) { - self.updateParticipantsList(conference) - self.updateParticipantsDevicesList(conference) - self.isMeConferenceFocus.value = conference.me?.isFocus == true - self.isMeAdmin.value = conference.me?.isAdmin == true - self.conferenceAddress.value = conference.conferenceAddress - self.subject.value = conference.subject.isEmpty ? ( - conference.me?.isFocus == true ? ( - VoipTexts.conference_local_title - ) : ( - VoipTexts.conference_default_title - ) - ) : ( - conference.subject - ) + self.initConference(conference) + self.configureConference(conference) } else if (state == Conference.State.Terminated || state == Conference.State.TerminationFailed) { - self.isInConference.value = false - self.isVideoConference.value = false - conference.removeDelegate(delegate: self.conferenceDelegate!) - self.conferenceParticipants.value?.forEach{ $0.destroy()} - self.conferenceParticipantDevices.value?.forEach{ $0.destroy()} - self.conferenceParticipants.value = [] - self.conferenceParticipantDevices.value = [] + self.terminateConference(conference) } let layout = conference.layout == .None ? .Grid : conference.layout @@ -137,103 +99,91 @@ class ConferenceViewModel { ) Core.get().addDelegate(delegate: coreDelegate!) - - conferenceParticipants.value = [] conferenceParticipantDevices.value = [] conferenceDisplayMode.value = .Grid - subject.value = VoipTexts.conference_default_title if let conference = core.conference != nil ? core.conference : core.currentCall?.conference { Log.i("[Conference] Found an existing conference: \(conference)") - self.conference.value = conference - conference.addDelegate(delegate: self.conferenceDelegate!) - - - isInConference.value = true - isConferencePaused.value = !conference.isIn - isMeConferenceFocus.value = conference.me?.isFocus == true - isMeAdmin.value = conference.me?.isAdmin == true - isVideoConference.value = conference.currentParams?.isVideoEnabled == true - conferenceAddress.value = conference.conferenceAddress - if (!conference.subject.isEmpty) { - subject.value = conference.subject - } - - let layout = conference.layout == .None ? .Grid : conference.layout - conferenceDisplayMode.value = layout - Log.i("[Conference] \(conference) Conference current layout is: \(layout)") - - updateParticipantsList(conference) - updateParticipantsDevicesList(conference) + initConference(conference) + configureConference(conference) } } + func initConference(_ conference: Conference) { + conferenceExists.value = true + self.conference.value = conference + conference.addDelegate(delegate: self.conferenceDelegate!) + isRecording.value = conference.isRecording + } - func destroy() { - core.removeDelegate(delegate: self.coreDelegate!) + func terminateConference(_ conference: Conference) { + conferenceExists.value = false + isVideoConference.value = false self.conferenceParticipants.value?.forEach{ $0.destroy()} self.conferenceParticipantDevices.value?.forEach{ $0.destroy()} + conferenceParticipants.value = [] + conferenceParticipantDevices.value = [] } + func configureConference(_ conference: Conference) { + self.updateParticipantsList(conference) + self.updateParticipantsDevicesList(conference) + + isConferenceLocallyPaused.value = !conference.isIn + self.isMeAdmin.value = conference.me?.isAdmin == true + isVideoConference.value = conference.currentParams?.isVideoEnabled == true + + self.subject.value = conference.subject.isEmpty ? ( + conference.me?.isFocus == true ? ( + VoipTexts.conference_local_title + ) : ( + VoipTexts.conference_default_title + ) + ) : ( + conference.subject + ) + } + + func pauseConference() { - let defaultProxyConfig = core.defaultProxyConfig - let localAddress = defaultProxyConfig?.identityAddress - let participants : [Address] = [] - let remoteConference = core.searchConference(params: nil, localAddr: localAddress, remoteAddr: conferenceAddress.value, participants: participants) - let localConference = core.searchConference(params: nil, localAddr: conferenceAddress.value, remoteAddr: conferenceAddress.value, participants: participants) - let conference = remoteConference != nil ? remoteConference : localConference - - if (conference != nil) { - Log.i("[Conference] Leaving conference with address \(conference) temporarily") - conference!.leave() - } else { - Log.w("[Conference] Unable to find conference with address \(conference)") - } + Log.i("[Conference] Leaving conference with address \(conference) temporarily") + conference.value?.leave() } func resumeConference() { - let defaultProxyConfig = core.defaultProxyConfig - let localAddress = defaultProxyConfig?.identityAddress - let participants : [Address] = [] - let remoteConference = core.searchConference(params: nil, localAddr: localAddress, remoteAddr: conferenceAddress.value, participants: participants) - let localConference = core.searchConference(params: nil, localAddr: conferenceAddress.value, remoteAddr: conferenceAddress.value, participants: participants) - - if let conference = remoteConference != nil ? remoteConference : localConference { - Log.i("[Conference] Entering again conference with address \(conference)") - conference.enter() - } else { - Log.w("[Conference] Unable to find conference with address \(conference)") - } + Log.i("[Conference] entering conference with address \(conference)") + conference.value?.enter() } func togglePlayPause () { - if (isConferencePaused.value == true) { + if (isConferenceLocallyPaused.value == true) { resumeConference() - isConferencePaused.value = false + isConferenceLocallyPaused.value = false } else { pauseConference() - isConferencePaused.value = true + isConferenceLocallyPaused.value = true } } func toggleRecording() { - guard let conference = core.conference else { + guard let conference = conference.value else { Log.e("[Conference] Failed to find conference!") return } - + /* frogtrust has is own recording method if (conference.isRecording == true) { conference.stopRecording() } else { let path = AppManager.recordingFilePathFromCall(address: conference.conferenceAddress?.asStringUriOnly() ?? "") Log.i("[Conference] Starting recording \(conference) in file \(path)") conference.startRecording(path: path) - } + }*/ + isRecording.value = conference.isRecording } @@ -250,7 +200,7 @@ class ConferenceViewModel { let participantData = ConferenceParticipantData(conference: conference, participant: participant) participants.append(participantData) } - + conferenceParticipants.value = participants } @@ -266,24 +216,80 @@ class ConferenceViewModel { Log.i("[Conference] \(conference) Participant found: \(participant) with \(participantDevices.count) device(s)") participantDevices.forEach { (device) in - Log.i("[Conference] \(conference) Participant device found: \(device.name) (\(device.address!.asStringUriOnly()))") + Log.i("[Conference] \(conference) Participant device found: \(device.name) (\(device.address!.asStringUriOnly()))") let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: false) - devices.append(deviceData) + devices.append(deviceData) } - + } conference.me?.devices.forEach { (device) in Log.i("[Conference] \(conference) Participant device for myself found: \(device.name) (\(device.address!.asStringUriOnly()))") let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: true) devices.append(deviceData) } - + conferenceParticipantDevices.value = devices } + func updateParticipants(addresses:[Address]) { + guard let conference = conference.value else { + Log.w("[Conference Participants] conference not set, can't update participants") + return + } + do { + // Adding new participants first, because if we remove all of them (or all of them except one) + // It will terminate the conference first and we won't be able to add new participants after + try addresses.forEach { address in + let participant = conference.participantList.filter { $0.address?.asStringUriOnly() == address.asStringUriOnly() }.first + if (participant == nil) { + Log.i("[Conference Participants] Participant \(address.asStringUriOnly()) will be added to group") + try conference.addParticipant(uri: address) + } + } + + // Removing participants + try conference.participantList.forEach { participant in + let member = addresses.filter { $0.asStringUriOnly() == participant.address?.asStringUriOnly() }.first + if (member == nil) { + Log.w("[Conference Participants] Participant \(participant.address?.asStringUriOnly()) will be removed from conference") + try conference.removeParticipant(participant: participant) + } + } + } catch { + Log.e("[Conference Participants] Error updating participant lists \(error)") + } + } + + func addCallsToConference() { + Log.i("[Conference] Trying to merge all calls into existing conference") + guard let conf = conference.value else { + return + } + core.calls.forEach { call in + if (call.conference == nil) { + try? conf.addParticipant(call: call) + } + } + } + + } +@objc class ConferenceViewModelBridge : NSObject { + @objc static func updateParticipantsList(addresses:[String]) { + do { + try ConferenceViewModel.shared.updateParticipants(addresses: addresses.map { try Factory.Instance.createAddress(addr: $0)} ) + } catch { + Log.e("[ParticipantsListView] unable to update participants list \(error)") + } + } +} + + + + + enum FlexDirection { case ROW case ROW_REVERSE diff --git a/Classes/Voip/Models/ControlsViewModel.swift b/Classes/Swift/Voip/Models/ControlsViewModel.swift similarity index 100% rename from Classes/Voip/Models/ControlsViewModel.swift rename to Classes/Swift/Voip/Models/ControlsViewModel.swift diff --git a/Classes/Voip/Theme/ButtonTheme.swift b/Classes/Swift/Voip/Theme/ButtonTheme.swift similarity index 100% rename from Classes/Voip/Theme/ButtonTheme.swift rename to Classes/Swift/Voip/Theme/ButtonTheme.swift diff --git a/Classes/Voip/Theme/LightDarkColor.swift b/Classes/Swift/Voip/Theme/LightDarkColor.swift similarity index 100% rename from Classes/Voip/Theme/LightDarkColor.swift rename to Classes/Swift/Voip/Theme/LightDarkColor.swift diff --git a/Classes/Voip/Theme/TextStyle.swift b/Classes/Swift/Voip/Theme/TextStyle.swift similarity index 92% rename from Classes/Voip/Theme/TextStyle.swift rename to Classes/Swift/Voip/Theme/TextStyle.swift index 66c6d45e1..bf1f93c30 100644 --- a/Classes/Voip/Theme/TextStyle.swift +++ b/Classes/Swift/Voip/Theme/TextStyle.swift @@ -47,10 +47,10 @@ extension UILabel { font = UIFont.init(name: style.font, size: CGFloat(style.size*fontSizeMultiplier)) } - func addIndicatorIcon(iconName:String, _ padding:CGFloat = 5.0, trailing: Bool = true) { + func addIndicatorIcon(iconName:String, padding:CGFloat = 5.0, y:CGFloat = 4.0, trailing: Bool = true) { let imageAttachment = NSTextAttachment() imageAttachment.image = UIImage(named:iconName) - imageAttachment.bounds = CGRect(x: 5.0, y: 4.0, width: font.lineHeight - 2*padding, height: font.lineHeight - 2*padding) + imageAttachment.bounds = CGRect(x: 5.0, y: y , width: font.lineHeight - 2*padding, height: font.lineHeight - 2*padding) let iconString = NSMutableAttributedString(attachment: imageAttachment) let textXtring = NSMutableAttributedString(string: text != nil ? text! : "") if (trailing) { diff --git a/Classes/Voip/Theme/VoipTexts.swift b/Classes/Swift/Voip/Theme/VoipTexts.swift similarity index 95% rename from Classes/Voip/Theme/VoipTexts.swift rename to Classes/Swift/Voip/Theme/VoipTexts.swift index b5c1e8f21..f8e1d2649 100644 --- a/Classes/Voip/Theme/VoipTexts.swift +++ b/Classes/Swift/Voip/Theme/VoipTexts.swift @@ -23,7 +23,8 @@ import UIKit @objc class VoipTexts : NSObject { // From android key names. Added intentionnally with NSLocalizedString calls for each key, so it can be picked up by translation system (Weblate or Xcode). static let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as! String - + static let me = NSLocalizedString("Me",comment:"") + // Calls static let call_incoming_title = NSLocalizedString("Incoming Call",comment:"") static let call_outgoing_title = NSLocalizedString("Outgoing Call",comment:"") @@ -97,7 +98,11 @@ import UIKit static let conference_scheduled = NSLocalizedString("Conferences",comment:"") static let conference_too_many_participants_for_mosaic_layout = NSLocalizedString("You can't change conference layout as there is too many participants",comment:"") static let conference_participant_paused = NSLocalizedString("(paused)",comment:"") - + static let conference_no_schedule = NSLocalizedString("No scheduled conference yet.",comment:"") + 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:"") + // Call Stats diff --git a/Classes/Voip/Theme/VoipTheme.swift b/Classes/Swift/Voip/Theme/VoipTheme.swift similarity index 89% rename from Classes/Voip/Theme/VoipTheme.swift rename to Classes/Swift/Voip/Theme/VoipTheme.swift index c17d33e09..72d6ae29e 100644 --- a/Classes/Voip/Theme/VoipTheme.swift +++ b/Classes/Swift/Voip/Theme/VoipTheme.swift @@ -53,6 +53,9 @@ class VoipTheme { // Names & values replicated from Android static let light_grey_color = UIColor(hex:"#c4c4c4") static let header_background_color = UIColor(hex:"#f3f3f3") static let dark_grey_color = UIColor(hex:"#444444") + static let voip_conference_invite_out = UIColor(hex:"ffeee5") + static let voip_conference_invite_in = header_background_color + // Light / Dark variations static let voipBackgroundColor = LightDarkColor(voip_gray_blue_color,voip_dark_color) @@ -72,6 +75,7 @@ class VoipTheme { // Names & values replicated from Android + // Text styles static let fontName = "Roboto" static let call_header_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 18.0) @@ -86,7 +90,8 @@ class VoipTheme { // Names & values replicated from Android static let call_or_conference_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 30.0) static let call_or_conference_subtitle = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 20.0) static let basic_popup_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 21.0) - static let big_button = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Bold", size: 17.0) + static let form_button_bold = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Bold", size: 17.0) + static let form_button_light = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Regular", size: 17.0) static let call_display_name_duration = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0) static let call_sip_address = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 14.0) @@ -104,18 +109,27 @@ class VoipTheme { // Names & values replicated from Android static let call_context_menu_item_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .left, font: fontName+"-Bold", size: 16.0) + static let conference_participant_admin_label = TextStyle(fgColor: primarySubtextLightColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 13.0) static let conference_participant_name_font = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0) static let conference_participant_sip_uri_font = TextStyle(fgColor: LightDarkColor(primary_color,primary_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0) static let conference_participant_name_font_grid = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 15.0) static let conference_participant_name_font_as = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 12.0) - - static let conference_mode_title = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0) static let conference_mode_title_selected = conference_mode_title.boldEd() - static let conference_scheduling_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0) + static let conference_invite_desc_font = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 14.0) + static let conference_invite_desc_title_font = TextStyle(fgColor: LightDarkColor(voip_dark_gray,voip_dark_gray), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 14.0) + static let conference_invite_subject_font = TextStyle(fgColor: LightDarkColor(voip_dark_gray,voip_dark_gray), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 14.0) + static let conference_invite_title_font = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 16.0) + static let conference_preview_subject_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 24.0) + static let empty_list_font = TextStyle(fgColor: primaryTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0) + + + + + @@ -156,13 +170,16 @@ class VoipTheme { // Names & values replicated from Android UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray) ] - - static let primary_colors_background = [ UIButton.State.normal.rawValue : LightDarkColor(primary_color,primary_color), UIButton.State.highlighted.rawValue : LightDarkColor(primary_dark_color,primary_dark_color), ] + static let button_green_background = [ + UIButton.State.normal.rawValue : LightDarkColor(green_color,green_color), + UIButton.State.highlighted.rawValue : LightDarkColor(primary_color,primary_color), + ] + static let primary_colors_background_gray = [ UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray), UIButton.State.highlighted.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray), @@ -353,6 +370,18 @@ class VoipTheme { // Names & values replicated from Android backgroundStateColors: [:]) } + // Conference scheduling + static func scheduled_conference_action(_ iconName:String) -> ButtonTheme { + return ButtonTheme( + tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(.white,.white))], + backgroundStateColors: button_background) + } + + static let conference_info_button = [ + UIButton.State.normal.rawValue : TintableIcon(name: "voip_info",tintColor: LightDarkColor(voip_drawable_color,voip_drawable_color)), + UIButton.State.selected.rawValue : TintableIcon(name: "voip_info",tintColor: LightDarkColor(primary_color,primary_color)), + ] + } diff --git a/Classes/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift similarity index 85% rename from Classes/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift rename to Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift index f85be7062..924d2d95f 100644 --- a/Classes/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift @@ -22,7 +22,7 @@ import UIKit import linphonesw -class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // Replaces CallView +@objc class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // Replaces CallView // Layout constants let content_inset = 12.0 @@ -40,7 +40,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // var shadingMask = UIView() var videoAcceptDialog : VoipDialog? = nil var dismissableView : DismissableView? = nil - var participantsListView : ParticipantsListView? = nil + @objc var participantsListView : ParticipantsListView? = nil var audioRoutesView : AudioRoutesView? = nil @@ -68,7 +68,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done() - // Container fiew + // Container view let fullScreenMutableContainerView = UIView() fullScreenMutableContainerView.backgroundColor = .clear self.view.addSubview(fullScreenMutableContainerView) @@ -80,7 +80,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // fullScreenMutableContainerView.addSubview(currentCallView!) CallsViewModel.shared.currentCallData.readCurrentAndObserve { (currentCallData) in self.updateNavigation() - self.currentCallView!.isHidden = currentCallData == nil || ConferenceViewModel.shared.isInConference.value == true + self.currentCallView!.isHidden = currentCallData == nil || ConferenceViewModel.shared.conferenceExists.value == true self.currentCallView!.callData = currentCallData != nil ? currentCallData! : nil currentCallData??.isRemotelyPaused.readCurrentAndObserve { remotelyPaused in self.callPausedByRemoteView?.isHidden = remotelyPaused != true @@ -114,7 +114,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // fullScreenMutableContainerView.addSubview(conferenceGridView!) conferenceGridView?.matchParentDimmensions().done() conferenceGridView?.isHidden = true - ConferenceViewModel.shared.isInConference.readCurrentAndObserve { (isInConference) in + ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (isInConference) in self.updateNavigation() if (isInConference == true) { self.currentCallView!.isHidden = true @@ -137,7 +137,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // // Conference mode switching ConferenceViewModel.shared.conferenceDisplayMode.readCurrentAndObserve { (conferenceMode) in - if (ConferenceViewModel.shared.isInConference.value == true) { + if (ConferenceViewModel.shared.conferenceExists.value == true) { self.conferenceGridView!.isHidden = conferenceMode != .Grid self.conferenceActiveSpeakerView!.isHidden = conferenceMode != .ActiveSpeaker self.conferenceActiveSpeakerView?.conferenceViewModel = ConferenceViewModel.shared @@ -146,7 +146,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // } } - ConferenceViewModel.shared.isInConference.readCurrentAndObserve { (isInConference) in + ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (isInConference) in self.updateNavigation() } @@ -199,7 +199,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // boucingCounter.dataSource = CallsViewModel.shared.chatAndCallsCount view.addSubview(extraButtonsView) - extraButtonsView.matchParentSideBorders(insetedByDx: content_inset).alignParentBottom().done() + extraButtonsView.matchParentSideBorders(insetedByDx: content_inset).alignParentBottom(withMargin:SharedLayoutConstants.bottom_margin_notch_clearance).done() ControlsViewModel.shared.hideExtraButtons.readCurrentAndObserve { (_) in self.hideModalSubview(view: self.extraButtonsView) } @@ -215,7 +215,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) { self.numpadView?.removeFromSuperview() self.shadingMask.isHidden = false - self.numpadView = NumpadView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!, onDismissAction: { + self.numpadView = NumpadView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!,marginTop:self.currentCallView?.centerSection.frame.origin.y ?? 0.0, onDismissAction: { self.numpadView?.removeFromSuperview() self.shadingMask.isHidden = true }) @@ -227,7 +227,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) { self.currentCallStatsVew?.removeFromSuperview() self.shadingMask.isHidden = false - self.currentCallStatsVew = CallStatsView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!, onDismissAction: { + self.currentCallStatsVew = CallStatsView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!,marginTop:self.currentCallView?.centerSection.frame.origin.y ?? 0.0, onDismissAction: { self.currentCallStatsVew?.removeFromSuperview() self.shadingMask.isHidden = true }) @@ -296,6 +296,7 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // func updateNavigation() { if (Core.get().callsNb == 0) { PhoneMainView.instance().popView(self.compositeViewDescription()) + PhoneMainView.instance().mainViewController.removeCallFromCache() } else { if let data = CallsViewModel.shared.currentCallData.value { if (data?.isOutgoing.value == true || data?.isIncoming.value == true) { @@ -312,35 +313,12 @@ class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // func goToChat() { let core = Core.get() guard - let localSipUri = core.defaultAccount?.params?.identityAddress?.asStringUriOnly(), - let remoteSipUri = ConferenceViewModel.shared.isInConference.value == true ? ConferenceViewModel.shared.conferenceAddress.value?.asStringUriOnly() : core.currentCall?.remoteAddress?.asStringUriOnly(), - let localAddress = try?Factory.Instance.createAddress(addr: localSipUri), - let remoteSipAddress = try?Factory.Instance.createAddress(addr: remoteSipUri), - let chatRoomParams = try?core.createDefaultChatRoomParams() + let chatRoom = CallsViewModel.shared.currentCallData.value??.chatRoom else { + Log.w("[Call] Failed to find existing chat room associated to call") return - } - - var chatRoom = core.searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: remoteSipAddress, participants: []) - if (chatRoom == nil) { - chatRoom = core.searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: nil, participants: [remoteSipAddress]) - } - if (chatRoom == nil) { - Log.w("[Call] Failed to find existing chat room for local address \(localSipUri) and remote address \(remoteSipUri)") - - // TODO: configure chat room params - if (ConferenceViewModel.shared.isInConference.value == true) { - // TODO: compute conference participants addresses list - } else { - chatRoom = try?core.createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: [remoteSipAddress]) - } - } - - if (chatRoom != nil) { - PhoneMainView.instance().go(to: chatRoom?.getCobject) - } else { - Log.w("[Call] Failed to create chat room for local address \(localSipUri) and remote address \(remoteSipUri)") } + PhoneMainView.instance().go(to: chatRoom.getCobject) } diff --git a/Classes/Voip/Views/CompositeViewControllers/IncomingCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift similarity index 100% rename from Classes/Voip/Views/CompositeViewControllers/IncomingCallView.swift rename to Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift diff --git a/Classes/Voip/Views/CompositeViewControllers/OutgoingCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift similarity index 98% rename from Classes/Voip/Views/CompositeViewControllers/OutgoingCallView.swift rename to Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift index d2898a148..31286af27 100644 --- a/Classes/Voip/Views/CompositeViewControllers/OutgoingCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift @@ -61,7 +61,7 @@ import linphonesw showNumPad = CallControlButton(imageInset:UIEdgeInsets(top: numpad_icon_padding, left: numpad_icon_padding, bottom: numpad_icon_padding, right: numpad_icon_padding), buttonTheme: VoipTheme.call_numpad, onClickAction: { self.numpadView?.removeFromSuperview() self.shadingMask.isHidden = false - self.numpadView = NumpadView(superView: self.view,callData: self.callData!, onDismissAction: { + self.numpadView = NumpadView(superView: self.view,callData: self.callData!, marginTop: 0.0, onDismissAction: { self.numpadView?.removeFromSuperview() self.shadingMask.isHidden = true }) diff --git a/Classes/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift b/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift similarity index 92% rename from Classes/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift rename to Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift index 2b8194b70..e08dc14b2 100644 --- a/Classes/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift +++ b/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift @@ -41,10 +41,13 @@ class ActiveCallView: UIView { // = currentCall static let local_video_margins = 15.0 + let upperSection = UIStackView() let displayNameTop = StyledLabel(VoipTheme.call_display_name_duration) let duration = CallTimer(nil, VoipTheme.call_display_name_duration) let sipAddress = StyledLabel(VoipTheme.call_sip_address) let remotelyRecordedIndicator = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording) + + let centerSection = UIView() let avatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views), color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large) let displayNameBottom = StyledLabel(VoipTheme.call_remote_name) var recordCallButtons : [CallControlButton] = [] @@ -77,9 +80,15 @@ class ActiveCallView: UIView { // = currentCall self.localVideo.isHidden = true } } + callData?.isRemotelyRecorded.readCurrentAndObserve { (remotelyRecorded) in + self.centerSection.removeConstraints().matchParentSideBorders().alignUnder(view:remotelyRecorded == true ? self.remotelyRecordedIndicator : self.upperSection ,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done() + self.setNeedsLayout() + } + + Core.get().nativeVideoWindow = remoteVideo - Core.get().nativePreviewWindow = localVideo + Core.get().nativePreviewWindowId = UnsafeMutableRawPointer(Unmanaged.passRetained(localVideo).toOpaque()) ControlsViewModel.shared.isVideoEnabled.readCurrentAndObserve{ (video) in self.remoteVideo.isHidden = video != true @@ -112,7 +121,6 @@ class ActiveCallView: UIView { // = currentCall displayNameDurationSipAddress.addSubview(sipAddress) sipAddress.matchParentSideBorders().alignUnder(view: displayNameTop,withMargin:sip_address_margin_top).done() - let upperSection = UIStackView() upperSection.distribution = .equalSpacing upperSection.alignment = .center upperSection.spacing = record_pause_button_margin @@ -145,10 +153,9 @@ class ActiveCallView: UIView { // = currentCall stack.addArrangedSubview(remotelyRecordedIndicator) - remotelyRecordedIndicator.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done() + remotelyRecordedIndicator.matchParentSideBorders().height(CGFloat(ActiveCallView.remote_recording_height)).done() // Center Section : Avatar + video + record/pause buttons + videos - let centerSection = UIView() centerSection.layer.cornerRadius = ActiveCallView.center_view_corner_radius centerSection.clipsToBounds = true centerSection.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get() @@ -203,8 +210,8 @@ class ActiveCallView: UIView { // = currentCall } else { self.remoteVideo.removeFromSuperview() self.localVideo.removeFromSuperview() - centerSection.addSubview(self.remoteVideo) - centerSection.addSubview(self.localVideo) + self.centerSection.addSubview(self.remoteVideo) + self.centerSection.addSubview(self.localVideo) } self.remoteVideo.matchParentDimmensions().done() self.localVideo.alignParentBottom(withMargin: ActiveCallView.local_video_margins).alignParentRight(withMargin: ActiveCallView.local_video_margins).done() @@ -217,7 +224,6 @@ class ActiveCallView: UIView { // = currentCall displayNameBottom.alignParentLeft(withMargin:ActiveCallView.bottom_displayname_margin_left).alignParentRight().alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done() stack.addArrangedSubview(centerSection) - centerSection.matchParentSideBorders().alignUnder(view:upperSection,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done() addSubview(stack) stack.matchParentDimmensions().done() diff --git a/Classes/Voip/Views/Fragments/AudioRoutesView.swift b/Classes/Swift/Voip/Views/Fragments/AudioRoutesView.swift similarity index 100% rename from Classes/Voip/Views/Fragments/AudioRoutesView.swift rename to Classes/Swift/Voip/Views/Fragments/AudioRoutesView.swift diff --git a/Classes/Voip/Views/Fragments/CallStatsView.swift b/Classes/Swift/Voip/Views/Fragments/CallStatsView.swift similarity index 89% rename from Classes/Voip/Views/Fragments/CallStatsView.swift rename to Classes/Swift/Voip/Views/Fragments/CallStatsView.swift index 142cc03d6..27214817d 100644 --- a/Classes/Voip/Views/Fragments/CallStatsView.swift +++ b/Classes/Swift/Voip/Views/Fragments/CallStatsView.swift @@ -26,21 +26,16 @@ import linphonesw let side_margins = 10.0 let margin_top = 50 let corner_radius = 20.0 - let view_height = 600 let audio_video_margin = 20 - init(superView:UIView, callData:CallData, onDismissAction : @escaping ()->Void) { + init(superView:UIView, callData:CallData, marginTop:CGFloat, onDismissAction : @escaping ()->Void) { super.init(frame:.zero) backgroundColor = VoipTheme.voip_translucent_popup_background layer.cornerRadius = corner_radius clipsToBounds = true superView.addSubview(self) - snp.makeConstraints { make in - make.left.equalToSuperview().offset(side_margins) - make.right.equalToSuperview().offset(-side_margins) - make.height.equalTo(view_height) - make.bottom.equalToSuperview().offset(-side_margins) - } + matchParentSideBorders(insetedByDx: side_margins).alignParentTop(withMargin: marginTop).alignParentBottom().done() + callData.callState.observe { state in if (state == Call.State.End) { onDismissAction() diff --git a/Classes/Voip/Views/Fragments/CallsList/CallsListView.swift b/Classes/Swift/Voip/Views/Fragments/CallsList/CallsListView.swift similarity index 79% rename from Classes/Voip/Views/Fragments/CallsList/CallsListView.swift rename to Classes/Swift/Voip/Views/Fragments/CallsList/CallsListView.swift index 6fad29f47..33fdcf922 100644 --- a/Classes/Voip/Views/Fragments/CallsList/CallsListView.swift +++ b/Classes/Swift/Voip/Views/Fragments/CallsList/CallsListView.swift @@ -33,6 +33,8 @@ import linphonesw var callsDataObserver : MutableLiveDataOnChangeClosure<[CallData]>? = nil + + init() { super.init(title: VoipTexts.call_action_calls_list) @@ -49,19 +51,22 @@ import linphonesw // Merge Calls let mergeIntoLocalConference = CallControlButton(width: buttons_size,height: buttons_size, buttonTheme: VoipTheme.call_merge, onClickAction: { self.removeFromSuperview() - CallsViewModel.shared.mergeCallsIntoLocalConference() + if (ConferenceViewModel.shared.conferenceExists.value == true) { + ConferenceViewModel.shared.addCallsToConference() + } else { + CallsViewModel.shared.mergeCallsIntoLocalConference() + } }) addSubview(mergeIntoLocalConference) mergeIntoLocalConference.centerX(withDx: buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done() - CallsViewModel.shared.callsData.readCurrentAndObserve{ (callsData) in - if let callsData = callsData { - mergeIntoLocalConference.isEnabled = callsData.count >= 2 && Core.get().conference?.isIn != true - } else { - mergeIntoLocalConference.isEnabled = false - } + CallsViewModel.shared.callsData.readCurrentAndObserve { _ in self.callsListTableView.reloadData() + mergeIntoLocalConference.isEnabled = self.mergeToConferencePossible() + } + ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { _ in + mergeIntoLocalConference.isEnabled = self.mergeToConferencePossible() } @@ -86,6 +91,23 @@ import linphonesw menuView.isHidden = true } + + + func numberOfCallsNotInConf() -> Int { + let core = Core.get() + var result = 0 + core.calls.forEach { call in + if (call.conference == nil && core.findConferenceInformationFromUri(uri: call.remoteAddress!) == nil) { + result += 1 + } + } + return result + } + + func mergeToConferencePossible() -> Bool { // 2 calls or more not in conf or 1 call or more and 1 conf + let callsNotInConf = numberOfCallsNotInConf() + return (ConferenceViewModel.shared.conferenceExists.value == true && callsNotInConf >= 1) || (ConferenceViewModel.shared.conferenceExists.value != true && callsNotInConf >= 2 ) + } func toggleMenu(forCell:VoipCallCell) { diff --git a/Classes/Voip/Views/Fragments/CallsList/VoipCallCell.swift b/Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallCell.swift similarity index 87% rename from Classes/Voip/Views/Fragments/CallsList/VoipCallCell.swift rename to Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallCell.swift index 22c92def4..b2fc5ec5d 100644 --- a/Classes/Voip/Views/Fragments/CallsList/VoipCallCell.swift +++ b/Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallCell.swift @@ -37,7 +37,6 @@ class VoipCallCell: UITableViewCell { var onMenuClickAction : (()->Void) = {} let callStatusIcon = UIImageView() let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:LightDarkColor(VoipTheme.voip_contact_avatar_calls_list,VoipTheme.voip_contact_avatar_calls_list), textStyle: VoipTheme.call_generated_avatar_small) - let conferenceAvatar = UIImageView(image:UIImage(named:"voip_multiple_contacts_avatar")) let displayName = StyledLabel(VoipTheme.call_list_active_name_font) let sipAddress = StyledLabel(VoipTheme.call_list_active_sip_uri_font) var menuButton : CallControlButton? = nil @@ -53,16 +52,14 @@ class VoipCallCell: UITableViewCell { data.isPaused.value == true ? UIImage(named:"voip_call_header_paused") : UIImage(named:"voip_call_header_active") if (data.isInRemoteConference.value == true) { - avatar.isHidden = true - conferenceAvatar.isHidden = false displayName.text = data.remoteConferenceSubject.value + //sipAddress.text = data.call.conference?.participantList.map{ String($0.address?.addressBookEnhancedDisplayName())}.joined(separator: ",") + avatar.fillFromAddress(address: data.call.remoteAddress!,isGroup:true) } else { displayName.text = data.call.remoteAddress?.addressBookEnhancedDisplayName() avatar.fillFromAddress(address: data.call.remoteAddress!) - avatar.isHidden = false - conferenceAvatar.isHidden = true + sipAddress.text = data.call.remoteAddress?.asStringUriOnly() } - sipAddress.text = data.call.remoteAddress?.asStringUriOnly() displayName.applyStyle(data.isPaused.value == true ? VoipTheme.call_list_name_font : VoipTheme.call_list_active_name_font) sipAddress.applyStyle(data.isPaused.value == true ? VoipTheme.call_list_sip_uri_font : VoipTheme.call_list_active_sip_uri_font) menuButton?.applyTintedIcons(tintedIcons: data.isPaused.value == true ? VoipTheme.voip_call_list_menu.tintableStateIcons : VoipTheme.voip_call_list_active_menu.tintableStateIcons) @@ -80,17 +77,14 @@ class VoipCallCell: UITableViewCell { contentView.addSubview(avatar) avatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: avatar_left_margin).done() - - contentView.addSubview(conferenceAvatar) - conferenceAvatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: avatar_left_margin).done() - + let nameAddress = UIView() nameAddress.addSubview(displayName) nameAddress.addSubview(sipAddress) displayName.alignParentTop().done() sipAddress.alignUnder(view: displayName).done() contentView.addSubview(nameAddress) - nameAddress.toRightOf(avatar,withLeftMargin:texts_left_margin).toRightOf(conferenceAvatar,withLeftMargin:texts_left_margin).wrapContentY().centerY().done() + nameAddress.toRightOf(avatar,withLeftMargin:texts_left_margin).wrapContentY().centerY().done() menuButton = CallControlButton(buttonTheme: VoipTheme.voip_call_list_active_menu, onClickAction: { self.owningCallsListView?.toggleMenu(forCell: self) diff --git a/Classes/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift b/Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift similarity index 100% rename from Classes/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift rename to Classes/Swift/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift diff --git a/Classes/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift similarity index 100% rename from Classes/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift rename to Classes/Swift/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift diff --git a/Classes/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift similarity index 99% rename from Classes/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift rename to Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift index e0bc2d937..a1683ee9e 100644 --- a/Classes/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift @@ -62,7 +62,7 @@ class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICol model.conferenceParticipantDevices.readCurrentAndObserve { (_) in self.grid.reloadData() } - model.isConferencePaused.readCurrentAndObserve { (paused) in + model.isConferenceLocallyPaused.readCurrentAndObserve { (paused) in self.pauseCallButtons.forEach { $0.isSelected = paused == true } diff --git a/Classes/Voip/Views/Fragments/Conference/VoipConferenceDisplayModeSelectionView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceDisplayModeSelectionView.swift similarity index 100% rename from Classes/Voip/Views/Fragments/Conference/VoipConferenceDisplayModeSelectionView.swift rename to Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceDisplayModeSelectionView.swift diff --git a/Classes/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift similarity index 99% rename from Classes/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift rename to Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift index 1b6509c03..b14fdb48d 100644 --- a/Classes/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift +++ b/Classes/Swift/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift @@ -53,7 +53,7 @@ class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionVi model.conferenceParticipantDevices.readCurrentAndObserve { (_) in self.grid.reloadData() } - model.isConferencePaused.readCurrentAndObserve { (paused) in + model.isConferenceLocallyPaused.readCurrentAndObserve { (paused) in self.pauseCallButtons.forEach { $0.isSelected = paused == true } diff --git a/Classes/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift b/Classes/Swift/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift similarity index 100% rename from Classes/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift rename to Classes/Swift/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift diff --git a/Classes/Voip/Views/Fragments/ControlsView.swift b/Classes/Swift/Voip/Views/Fragments/ControlsView.swift similarity index 100% rename from Classes/Voip/Views/Fragments/ControlsView.swift rename to Classes/Swift/Voip/Views/Fragments/ControlsView.swift diff --git a/Classes/Voip/Views/Fragments/DismissableView.swift b/Classes/Swift/Voip/Views/Fragments/DismissableView.swift similarity index 100% rename from Classes/Voip/Views/Fragments/DismissableView.swift rename to Classes/Swift/Voip/Views/Fragments/DismissableView.swift diff --git a/Classes/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift b/Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift similarity index 100% rename from Classes/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift rename to Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift diff --git a/Classes/Voip/Views/Fragments/LocalVideoView.swift b/Classes/Swift/Voip/Views/Fragments/LocalVideoView.swift similarity index 100% rename from Classes/Voip/Views/Fragments/LocalVideoView.swift rename to Classes/Swift/Voip/Views/Fragments/LocalVideoView.swift diff --git a/Classes/Voip/Views/Fragments/NumpadView.swift b/Classes/Swift/Voip/Views/Fragments/NumpadView.swift similarity index 91% rename from Classes/Voip/Views/Fragments/NumpadView.swift rename to Classes/Swift/Voip/Views/Fragments/NumpadView.swift index e949e82f8..d8f65364e 100644 --- a/Classes/Voip/Views/Fragments/NumpadView.swift +++ b/Classes/Swift/Voip/Views/Fragments/NumpadView.swift @@ -34,18 +34,14 @@ import linphonesw let side_padding = 50.0 - init(superView:UIView, callData:CallData, onDismissAction : @escaping ()->Void) { + init(superView:UIView, callData:CallData, marginTop:CGFloat, onDismissAction : @escaping ()->Void) { super.init(frame:.zero) backgroundColor = VoipTheme.voip_translucent_popup_background layer.cornerRadius = corner_radius clipsToBounds = true superView.addSubview(self) - snp.makeConstraints { make in - make.left.equalToSuperview().offset(side_margins) - make.right.equalToSuperview().offset(-side_margins) - make.height.equalTo(pad_height) - make.bottom.equalToSuperview().offset(-side_margins) - } + matchParentSideBorders(insetedByDx: side_margins).alignParentTop(withMargin: marginTop).alignParentBottom().done() + callData.callState.observe { state in if (state == Call.State.End) { onDismissAction() diff --git a/Classes/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift b/Classes/Swift/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift similarity index 81% rename from Classes/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift rename to Classes/Swift/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift index b81357bb7..509cb31cd 100644 --- a/Classes/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift +++ b/Classes/Swift/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift @@ -36,7 +36,7 @@ import linphonesw let edit = CallControlButton(buttonTheme: VoipTheme.voip_edit, onClickAction: { - // Todo (not implemented in Android yet as of 22.11.21) + self.gotoParticipantsListSelection() }) super.headerView.addSubview(edit) edit.centerY().done() @@ -94,4 +94,19 @@ import linphonesw fatalError("init(coder:) has not been implemented") } + func gotoParticipantsListSelection() { + let view: ChatConversationCreateView = self.VIEW(ChatConversationCreateView.compositeViewDescription()); + let addresses = ConferenceViewModel.shared.conferenceParticipants.value!.map { (data) in String(data.participant.address!.asStringUriOnly()) } + view.tableController.contactsGroup = (addresses as NSArray).mutableCopy() as? NSMutableArray + view.isForEditing = false + view.isForVoipConference = true + view.isForOngoingVoipConference = true + view.tableController.notFirstTime = true + view.isGroupChat = true + PhoneMainView.instance().changeCurrentView(view.compositeViewDescription()) + } + + + + } diff --git a/Classes/Voip/Views/Fragments/ParticipantsList/VoipParticipantCell.swift b/Classes/Swift/Voip/Views/Fragments/ParticipantsList/VoipParticipantCell.swift similarity index 100% rename from Classes/Voip/Views/Fragments/ParticipantsList/VoipParticipantCell.swift rename to Classes/Swift/Voip/Views/Fragments/ParticipantsList/VoipParticipantCell.swift diff --git a/Classes/Voip/Views/Fragments/PausedCallOrConferenceView.swift b/Classes/Swift/Voip/Views/Fragments/PausedCallOrConferenceView.swift similarity index 100% rename from Classes/Voip/Views/Fragments/PausedCallOrConferenceView.swift rename to Classes/Swift/Voip/Views/Fragments/PausedCallOrConferenceView.swift diff --git a/Classes/Voip/Views/Fragments/RemotelyRecording.swift b/Classes/Swift/Voip/Views/Fragments/RemotelyRecording.swift similarity index 96% rename from Classes/Voip/Views/Fragments/RemotelyRecording.swift rename to Classes/Swift/Voip/Views/Fragments/RemotelyRecording.swift index a3e957865..abaaba42c 100644 --- a/Classes/Voip/Views/Fragments/RemotelyRecording.swift +++ b/Classes/Swift/Voip/Views/Fragments/RemotelyRecording.swift @@ -31,7 +31,7 @@ class RemotelyRecordingView: UIView { var isRemotelyRecorded: MutableLiveData? = nil { didSet { isRemotelyRecorded?.readCurrentAndObserve(onChange: { (isRemotelyRecording) in - self.isHidden = !(isRemotelyRecording == true) + self.isHidden = isRemotelyRecording != true }) } } diff --git a/Classes/Voip/Views/Fragments/VoipExtraButtonsView.swift b/Classes/Swift/Voip/Views/Fragments/VoipExtraButtonsView.swift similarity index 98% rename from Classes/Voip/Views/Fragments/VoipExtraButtonsView.swift rename to Classes/Swift/Voip/Views/Fragments/VoipExtraButtonsView.swift index 2a7deca61..6c180a3ca 100644 --- a/Classes/Voip/Views/Fragments/VoipExtraButtonsView.swift +++ b/Classes/Swift/Voip/Views/Fragments/VoipExtraButtonsView.swift @@ -114,7 +114,7 @@ class VoipExtraButtonsView: UIStackView { addArrangedSubview(row2) row2.matchParentSideBorders().done() - ConferenceViewModel.shared.isInConference.readCurrentAndObserve { (isIn) in + ConferenceViewModel.shared.conferenceExists.readCurrentAndObserve { (isIn) in participants.isHidden = isIn != true layoutselect.isHidden = isIn != true transfer.isHidden = isIn == true diff --git a/Classes/Voip/Views/SharedLayoutConstants.swift b/Classes/Swift/Voip/Views/SharedLayoutConstants.swift similarity index 85% rename from Classes/Voip/Views/SharedLayoutConstants.swift rename to Classes/Swift/Voip/Views/SharedLayoutConstants.swift index 163d8ba07..b03960573 100644 --- a/Classes/Voip/Views/SharedLayoutConstants.swift +++ b/Classes/Swift/Voip/Views/SharedLayoutConstants.swift @@ -21,7 +21,8 @@ import Foundation class SharedLayoutConstants { - static let buttons_bottom_margin = 15 + static let buttons_bottom_margin = UIDevice.hasNotch() ? 30 : 15 static let margin_call_view_side_controls_buttons = 12 + static let bottom_margin_notch_clearance = UIDevice.hasNotch() ? 30.0 : 0.0 } diff --git a/Classes/Voip/VoipDialog.swift b/Classes/Swift/Voip/VoipDialog.swift similarity index 98% rename from Classes/Voip/VoipDialog.swift rename to Classes/Swift/Voip/VoipDialog.swift index 8488e8764..7ece3a8d8 100644 --- a/Classes/Voip/VoipDialog.swift +++ b/Classes/Swift/Voip/VoipDialog.swift @@ -69,7 +69,7 @@ class VoipDialog : UIView{ b.layer.cornerRadius = button_radius b.clipsToBounds = true buttonsView.addArrangedSubview(b) - b.applyTitleStyle(VoipTheme.big_button) + b.applyTitleStyle(VoipTheme.form_button_bold) let action = $0.action b.onClick { self.removeFromSuperview() diff --git a/Classes/Voip/Widgets/Avatar.swift b/Classes/Swift/Voip/Widgets/Avatar.swift similarity index 85% rename from Classes/Voip/Widgets/Avatar.swift rename to Classes/Swift/Voip/Widgets/Avatar.swift index 930dd8731..78b6ddffb 100644 --- a/Classes/Voip/Widgets/Avatar.swift +++ b/Classes/Swift/Voip/Widgets/Avatar.swift @@ -42,8 +42,11 @@ class Avatar : UIImageView { } - func fillFromAddress(address:Address) { - if let image = address.contact()?.avatar() { + func fillFromAddress(address:Address, isGroup:Bool = false) { + if (isGroup) { + self.image = UIImage(named:"voip_multiple_contacts_avatar")?.withPadding(padding: 50) + initialsLabel.isHidden = true + } else if let image = address.contact()?.avatar() { self.image = image initialsLabel.isHidden = true } else { @@ -52,7 +55,7 @@ class Avatar : UIImageView { initialsLabel.isHidden = false } } - + } diff --git a/Classes/Voip/Widgets/BouncingCounter.swift b/Classes/Swift/Voip/Widgets/BouncingCounter.swift similarity index 100% rename from Classes/Voip/Widgets/BouncingCounter.swift rename to Classes/Swift/Voip/Widgets/BouncingCounter.swift diff --git a/Classes/Voip/Widgets/ButtonWithStateBackgrounds.swift b/Classes/Swift/Voip/Widgets/ButtonWithStateBackgrounds.swift similarity index 100% rename from Classes/Voip/Widgets/ButtonWithStateBackgrounds.swift rename to Classes/Swift/Voip/Widgets/ButtonWithStateBackgrounds.swift diff --git a/Classes/Voip/Widgets/CallControlButton.swift b/Classes/Swift/Voip/Widgets/CallControlButton.swift similarity index 88% rename from Classes/Voip/Widgets/CallControlButton.swift rename to Classes/Swift/Voip/Widgets/CallControlButton.swift index 03a8a4835..474926248 100644 --- a/Classes/Voip/Widgets/CallControlButton.swift +++ b/Classes/Swift/Voip/Widgets/CallControlButton.swift @@ -79,14 +79,7 @@ class CallControlButton : ButtonWithStateBackgrounds { } - func applyTintedIcons(tintedIcons: [UInt: TintableIcon]) { - tintedIcons.keys.forEach { (stateRawValue) in - let tintedIcon = tintedIcons[stateRawValue]! - UIImage(named:tintedIcon.name).map { - setImage($0.tinted(with: tintedIcon.tintColor?.get()),for: UIButton.State(rawValue: stateRawValue)) - } - } - } + } diff --git a/Classes/Voip/Widgets/FormButton.swift b/Classes/Swift/Voip/Widgets/FormButton.swift similarity index 69% rename from Classes/Voip/Widgets/FormButton.swift rename to Classes/Swift/Voip/Widgets/FormButton.swift index 309334afc..618491852 100644 --- a/Classes/Voip/Widgets/FormButton.swift +++ b/Classes/Swift/Voip/Widgets/FormButton.swift @@ -38,13 +38,23 @@ class FormButton : ButtonWithStateBackgrounds { } } - init () { - super.init(backgroundStateColors: VoipTheme.primary_colors_background) + init (backgroundStateColors: [UInt: LightDarkColor], bold:Bool = true) { + super.init(backgroundStateColors: backgroundStateColors) layer.cornerRadius = button_radius clipsToBounds = true - applyTitleStyle(VoipTheme.big_button) + applyTitleStyle(bold ? VoipTheme.form_button_bold : VoipTheme.form_button_light) height(button_height).done() addSidePadding() } + convenience init (title:String, backgroundStateColors: [UInt: LightDarkColor], bold:Bool = true, fixedSize:Bool = true) { + self.init(backgroundStateColors: backgroundStateColors,bold:bold) + self.title = title + setTitle(title, for: .normal) + if (!fixedSize) { + addSidePadding() + } + } + + } diff --git a/Classes/Voip/Widgets/RotatingSpinner.swift b/Classes/Swift/Voip/Widgets/RotatingSpinner.swift similarity index 100% rename from Classes/Voip/Widgets/RotatingSpinner.swift rename to Classes/Swift/Voip/Widgets/RotatingSpinner.swift diff --git a/Classes/Voip/Widgets/StyledCheckBox.swift b/Classes/Swift/Voip/Widgets/StyledCheckBox.swift similarity index 100% rename from Classes/Voip/Widgets/StyledCheckBox.swift rename to Classes/Swift/Voip/Widgets/StyledCheckBox.swift diff --git a/Classes/Voip/Widgets/StyledDatePicker.swift b/Classes/Swift/Voip/Widgets/StyledDatePicker.swift similarity index 87% rename from Classes/Voip/Widgets/StyledDatePicker.swift rename to Classes/Swift/Voip/Widgets/StyledDatePicker.swift index fc51cfd3b..cd2aedb8d 100644 --- a/Classes/Voip/Widgets/StyledDatePicker.swift +++ b/Classes/Swift/Voip/Widgets/StyledDatePicker.swift @@ -26,8 +26,17 @@ class StyledDatePicker: UIView { let chevron_margin = 10 let form_input_height = 38.0 - - var liveValue:MutableLiveData? + let datePicker = UIDatePicker() + + var liveValue:MutableLiveData? { + didSet { + if let liveValue = liveValue { + datePicker.date = liveValue.value! + self.valueChanged(datePicker: datePicker) + } + } + + } let formattedLabel = StyledLabel(VoipTheme.conference_scheduling_font) var pickerMode:UIDatePicker.Mode = .date @@ -35,12 +44,10 @@ class StyledDatePicker: UIView { super.init(coder: coder) } - init (liveValue:MutableLiveData, pickerMode:UIDatePicker.Mode, readOnly:Bool = false) { + init (liveValue:MutableLiveData? = nil, pickerMode:UIDatePicker.Mode, readOnly:Bool = false) { super.init(frame: .zero) - self.liveValue = liveValue self.pickerMode = pickerMode - let datePicker = UIDatePicker() addSubview(datePicker) datePicker.datePickerMode = pickerMode datePicker.addTarget(self, action: #selector(valueChanged), for: .valueChanged) @@ -58,15 +65,13 @@ class StyledDatePicker: UIView { setFormInputBackground(readOnly:readOnly) height(form_input_height).done() - - datePicker.date = liveValue.value! - self.valueChanged(datePicker: datePicker) - + if (readOnly) { formattedLabel.textColor = formattedLabel.textColor.withAlphaComponent(0.5) } isUserInteractionEnabled = !readOnly - + self.liveValue = liveValue + } diff --git a/Classes/Voip/Widgets/StyledLabel.swift b/Classes/Swift/Voip/Widgets/StyledLabel.swift similarity index 100% rename from Classes/Voip/Widgets/StyledLabel.swift rename to Classes/Swift/Voip/Widgets/StyledLabel.swift diff --git a/Classes/Voip/Widgets/StyledSwitch.swift b/Classes/Swift/Voip/Widgets/StyledSwitch.swift similarity index 100% rename from Classes/Voip/Widgets/StyledSwitch.swift rename to Classes/Swift/Voip/Widgets/StyledSwitch.swift diff --git a/Classes/Voip/Widgets/StyledTextView.swift b/Classes/Swift/Voip/Widgets/StyledTextView.swift similarity index 100% rename from Classes/Voip/Widgets/StyledTextView.swift rename to Classes/Swift/Voip/Widgets/StyledTextView.swift diff --git a/Classes/Voip/Widgets/StyledValuePicker.swift b/Classes/Swift/Voip/Widgets/StyledValuePicker.swift similarity index 100% rename from Classes/Voip/Widgets/StyledValuePicker.swift rename to Classes/Swift/Voip/Widgets/StyledValuePicker.swift diff --git a/Classes/Voip/Widgets/UICallTimer.swift b/Classes/Swift/Voip/Widgets/UICallTimer.swift similarity index 100% rename from Classes/Voip/Widgets/UICallTimer.swift rename to Classes/Swift/Voip/Widgets/UICallTimer.swift diff --git a/Classes/Voip/Widgets/VoipExtraButton.swift b/Classes/Swift/Voip/Widgets/VoipExtraButton.swift similarity index 100% rename from Classes/Voip/Widgets/VoipExtraButton.swift rename to Classes/Swift/Voip/Widgets/VoipExtraButton.swift diff --git a/Classes/Utils/Utils.m b/Classes/Utils/Utils.m index b9cbfaaa1..6a4d8e433 100644 --- a/Classes/Utils/Utils.m +++ b/Classes/Utils/Utils.m @@ -631,6 +631,13 @@ } + (void)setDisplayNameLabel:(UILabel *)label forAddress:(const LinphoneAddress *)addr { + + const LinphoneConferenceInfo * ci = linphone_core_find_conference_information_from_uri(LC, (LinphoneAddress *)addr); + if (ci != nil) { + label.text = [NSString stringWithUTF8String:linphone_conference_info_get_subject(ci)]; + return; + } + Contact *contact = [FastAddressBook getContactWithAddress:addr]; if (contact) { [ContactDisplay setDisplayNameLabel:label forContact:contact]; @@ -640,6 +647,14 @@ } + (void)setDisplayNameLabel:(UILabel *)label forAddress:(const LinphoneAddress *)addr withAddressLabel:(UILabel*)addressLabel{ + + const LinphoneConferenceInfo * ci = linphone_core_find_conference_information_from_uri(LC, (LinphoneAddress *)addr); + if (ci != nil) { + label.text = [NSString stringWithUTF8String:linphone_conference_info_get_subject(ci)]; + addressLabel.text = NSLocalizedString(@"Conference",nil); + return; + } + Contact *contact = [FastAddressBook getContactWithAddress:addr]; NSString *tmpAddress = nil; char *uri = linphone_address_as_string_uri_only(addr); diff --git a/Classes/linphone-Bridging-Header.h b/Classes/linphone-Bridging-Header.h index c09603899..ba7081a2a 100644 --- a/Classes/linphone-Bridging-Header.h +++ b/Classes/linphone-Bridging-Header.h @@ -12,3 +12,6 @@ #import "StatusBarView.h" #import "LinphoneUI/UIBouncingView.h" #import "PhoneMainView.h" +#import "UICamSwitch.h" +#import "UIChatBubbleTextCell.h" + diff --git a/Resources/images/conference_schedule_calendar_default.png b/Resources/images/conference_schedule_calendar_default.png new file mode 100644 index 000000000..59fe950fb Binary files /dev/null and b/Resources/images/conference_schedule_calendar_default.png differ diff --git a/Resources/images/conference_schedule_participants_default.png b/Resources/images/conference_schedule_participants_default.png new file mode 100644 index 000000000..a58af55e1 Binary files /dev/null and b/Resources/images/conference_schedule_participants_default.png differ diff --git a/Resources/images/conference_schedule_time_default.png b/Resources/images/conference_schedule_time_default.png new file mode 100644 index 000000000..7a9534617 Binary files /dev/null and b/Resources/images/conference_schedule_time_default.png differ diff --git a/Resources/images/voip_call_add.png b/Resources/images/voip_call_add.png index 8237f3305..f4d80e5f5 100644 Binary files a/Resources/images/voip_call_add.png and b/Resources/images/voip_call_add.png differ diff --git a/Resources/images/voip_call_forward.png b/Resources/images/voip_call_forward.png index cc61de482..46a319028 100644 Binary files a/Resources/images/voip_call_forward.png and b/Resources/images/voip_call_forward.png differ diff --git a/Resources/images/voip_calls_list.png b/Resources/images/voip_calls_list.png index a3d69b84e..a832384ca 100644 Binary files a/Resources/images/voip_calls_list.png and b/Resources/images/voip_calls_list.png differ diff --git a/Resources/images/voip_conference_new_selected.png b/Resources/images/voip_conference_new_selected.png new file mode 100644 index 000000000..54ea3e2b0 Binary files /dev/null and b/Resources/images/voip_conference_new_selected.png differ diff --git a/Settings/InAppSettings.bundle/Call.plist b/Settings/InAppSettings.bundle/Call.plist index ff9d16866..2ce526c4a 100644 --- a/Settings/InAppSettings.bundle/Call.plist +++ b/Settings/InAppSettings.bundle/Call.plist @@ -14,6 +14,16 @@ DefaultValue + + Type + PSToggleSwitchSpecifier + Title + Notify and get notified when call is recorded + Key + record_aware + DefaultValue + + Type PSToggleSwitchSpecifier diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj index afad71813..031fbca04 100644 --- a/linphone.xcodeproj/project.pbxproj +++ b/linphone.xcodeproj/project.pbxproj @@ -51,7 +51,6 @@ 24E1C7C01F9A235600D3F981 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1C7B91F9A235500D3F981 /* Contacts.framework */; }; 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; }; 340751971506459A00B89C47 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 340751961506459A00B89C47 /* CoreTelephony.framework */; }; - 34216F401547EBCD00EA9777 /* VideoZoomHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 34216F3F1547EBCD00EA9777 /* VideoZoomHandler.m */; }; 344ABDF114850AE9007420B6 /* libc++.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 344ABDEF14850AE9007420B6 /* libc++.1.dylib */; settings = {ATTRIBUTES = (Weak, ); }; }; 570742581D5A0691004B9C84 /* ShopView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 570742561D5A0691004B9C84 /* ShopView.xib */; }; 570742611D5A09B8004B9C84 /* ShopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5707425F1D5A09B8004B9C84 /* ShopView.m */; }; @@ -610,7 +609,7 @@ 63E27A321C4FECD000D332AE /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63E27A311C4FECD000D332AE /* LaunchScreen.xib */; }; 63E27A521C50EDB000D332AE /* hold.mkv in Resources */ = {isa = PBXBuildFile; fileRef = 63E27A511C50EB2700D332AE /* hold.mkv */; }; 63E59A3F1ADE70D900646FB3 /* InAppProductsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E59A3E1ADE70D900646FB3 /* InAppProductsManager.m */; }; - 63E802DB1C625AEF000D5509 /* (null) in Resources */ = {isa = PBXBuildFile; }; + 63E802DB1C625AEF000D5509 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; 63EC8D391D7438660066547B /* AssistantLinkView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63EC8D3B1D7438660066547B /* AssistantLinkView.xib */; }; 63F1DF441BCE618E00EDED90 /* UIAddressTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F1DF431BCE618E00EDED90 /* UIAddressTextField.m */; }; 63FB30351A680E73008CA393 /* UIRoundedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FB30341A680E73008CA393 /* UIRoundedImageView.m */; }; @@ -656,6 +655,7 @@ 8CF25D9E1F9F76BD00BEA0C1 /* chat_group_informations@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8CF25D9C1F9F76BD00BEA0C1 /* chat_group_informations@2x.png */; }; 9C0B30F54D61774AFD1473CE /* Pods_linphone.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B464E44A606CB50A65A96FE2 /* Pods_linphone.framework */; }; C60B66682721AFFA0026AC7D /* CallStatisticsData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60B66672721AFFA0026AC7D /* CallStatisticsData.swift */; }; + C60C9F37278C3D36009A8F5B /* Pair.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60C9F36278C3D36009A8F5B /* Pair.swift */; }; C60D265627299C94006238BB /* ControlsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60D265527299C94006238BB /* ControlsViewModel.swift */; }; C60D265827299F70006238BB /* CoreExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60D265727299F6F006238BB /* CoreExtensions.swift */; }; C60D265C272AA0BD006238BB /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60D265B272AA0BD006238BB /* UIImageExtensions.swift */; }; @@ -771,7 +771,7 @@ C683B20E2722702300D4E15C /* VoipTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C683B20D2722702300D4E15C /* VoipTheme.swift */; }; C683B213272276CF00D4E15C /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C683B212272276CF00D4E15C /* UIColorExtensions.swift */; }; C690CCB1275764CD00609077 /* ConferenceSchedulingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C690CCB0275764CD00609077 /* ConferenceSchedulingView.swift */; }; - C690CCB42757683800609077 /* NavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C690CCB32757683800609077 /* NavigationView.swift */; }; + C690CCB42757683800609077 /* BackNextNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C690CCB32757683800609077 /* BackNextNavigationView.swift */; }; C6A1BB3526E8815400540D50 /* menu_info.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3126E8815300540D50 /* menu_info.png */; }; C6A1BB3626E8815400540D50 /* menu_forward_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3226E8815400540D50 /* menu_forward_default.png */; }; C6A1BB3726E8815400540D50 /* menu_copy_text_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3326E8815400540D50 /* menu_copy_text_default.png */; }; @@ -781,6 +781,16 @@ C6A1BB4126E889AD00540D50 /* forward_message_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4026E889AD00540D50 /* forward_message_default.png */; }; C6A1BB4326E88F7C00540D50 /* menu_resend_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4226E88F7C00540D50 /* menu_resend_default.png */; }; C6A1BB4526E890BD00540D50 /* file_voice_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4426E890BD00540D50 /* file_voice_default.png */; }; + C6AF920E275D38090087ACDE /* ScheduledConferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6AF920D275D38090087ACDE /* ScheduledConferencesView.swift */; }; + C6AF9210275D4DD60087ACDE /* ScheduledConferencesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6AF920F275D4DD60087ACDE /* ScheduledConferencesCell.swift */; }; + C6AF9212275D61420087ACDE /* conference_schedule_participants_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6AF9211275D613E0087ACDE /* conference_schedule_participants_default.png */; }; + C6AF9214275D67EB0087ACDE /* conference_schedule_time_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6AF9213275D67EB0087ACDE /* conference_schedule_time_default.png */; }; + C6AF9218275E13790087ACDE /* ScheduledConferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6AF9217275E13790087ACDE /* ScheduledConferencesViewModel.swift */; }; + C6AF921A275E2E010087ACDE /* ConferenceWaitingRoomFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6AF9219275E2E010087ACDE /* ConferenceWaitingRoomFragment.swift */; }; + C6AF921C275E4AF50087ACDE /* ICSBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6AF921B275E4AF50087ACDE /* ICSBubbleView.swift */; }; + C6AF921E275E51860087ACDE /* conference_schedule_calendar_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6AF921D275E51860087ACDE /* conference_schedule_calendar_default.png */; }; + C6AF9226275F3D890087ACDE /* voip_conference_new_selected.png in Resources */ = {isa = PBXBuildFile; fileRef = C6AF9225275F3D890087ACDE /* voip_conference_new_selected.png */; }; + C6AF922A275F6BA10087ACDE /* ConferenceHistoryDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6AF9229275F6BA10087ACDE /* ConferenceHistoryDetailsView.swift */; }; C6B04D61274B954500F70559 /* ParticipantsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B04D60274B954500F70559 /* ParticipantsListView.swift */; }; C6B04D63274B95D500F70559 /* VoipParticipantCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B04D62274B95D400F70559 /* VoipParticipantCell.swift */; }; C6B04D67274BD61300F70559 /* VoipConferenceActiveSpeakerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B04D66274BD61200F70559 /* VoipConferenceActiveSpeakerView.swift */; }; @@ -1051,8 +1061,6 @@ 32CA4F630368D1EE00C91783 /* linphone_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = linphone_Prefix.pch; sourceTree = ""; }; 340751961506459A00B89C47 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; 3411568BE5527EB500F75EBB /* Pods-msgNotificationService.distributionadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationService.distributionadhoc.xcconfig"; path = "Target Support Files/Pods-msgNotificationService/Pods-msgNotificationService.distributionadhoc.xcconfig"; sourceTree = ""; }; - 34216F3E1547EBCD00EA9777 /* VideoZoomHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VideoZoomHandler.h; path = LinphoneUI/VideoZoomHandler.h; sourceTree = ""; }; - 34216F3F1547EBCD00EA9777 /* VideoZoomHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VideoZoomHandler.m; path = LinphoneUI/VideoZoomHandler.m; sourceTree = ""; }; 344ABDEF14850AE9007420B6 /* libc++.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.1.dylib"; path = "usr/lib/libc++.1.dylib"; sourceTree = SDKROOT; }; 344ABDF014850AE9007420B6 /* libstdc++.6.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libstdc++.6.dylib"; path = "usr/lib/libstdc++.6.dylib"; sourceTree = SDKROOT; }; 507103607396F28FF4427108 /* Pods-msgNotificationContent.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationContent.distribution.xcconfig"; path = "Target Support Files/Pods-msgNotificationContent/Pods-msgNotificationContent.distribution.xcconfig"; sourceTree = ""; }; @@ -1815,6 +1823,7 @@ B9F41097CE0124A05554DB9C /* Pods-msgNotificationService.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationService.distribution.xcconfig"; path = "Target Support Files/Pods-msgNotificationService/Pods-msgNotificationService.distribution.xcconfig"; sourceTree = ""; }; C589627B9D9D2A4F9C816051 /* Pods-msgNotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationService.debug.xcconfig"; path = "Target Support Files/Pods-msgNotificationService/Pods-msgNotificationService.debug.xcconfig"; sourceTree = ""; }; C60B66672721AFFA0026AC7D /* CallStatisticsData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallStatisticsData.swift; sourceTree = ""; }; + C60C9F36278C3D36009A8F5B /* Pair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pair.swift; sourceTree = ""; }; C60D265527299C94006238BB /* ControlsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlsViewModel.swift; sourceTree = ""; }; C60D265727299F6F006238BB /* CoreExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreExtensions.swift; sourceTree = ""; }; C60D265B272AA0BD006238BB /* UIImageExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; @@ -1931,7 +1940,7 @@ C683B20D2722702300D4E15C /* VoipTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipTheme.swift; sourceTree = ""; }; C683B212272276CF00D4E15C /* UIColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtensions.swift; sourceTree = ""; }; C690CCB0275764CD00609077 /* ConferenceSchedulingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConferenceSchedulingView.swift; sourceTree = ""; }; - C690CCB32757683800609077 /* NavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationView.swift; sourceTree = ""; }; + C690CCB32757683800609077 /* BackNextNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackNextNavigationView.swift; sourceTree = ""; }; C6A1BB3126E8815300540D50 /* menu_info.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_info.png; sourceTree = ""; }; C6A1BB3226E8815400540D50 /* menu_forward_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_forward_default.png; sourceTree = ""; }; C6A1BB3326E8815400540D50 /* menu_copy_text_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_copy_text_default.png; sourceTree = ""; }; @@ -1942,6 +1951,16 @@ C6A1BB4026E889AD00540D50 /* forward_message_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = forward_message_default.png; sourceTree = ""; }; C6A1BB4226E88F7C00540D50 /* menu_resend_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_resend_default.png; sourceTree = ""; }; C6A1BB4426E890BD00540D50 /* file_voice_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_voice_default.png; sourceTree = ""; }; + C6AF920D275D38090087ACDE /* ScheduledConferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledConferencesView.swift; sourceTree = ""; }; + C6AF920F275D4DD60087ACDE /* ScheduledConferencesCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledConferencesCell.swift; sourceTree = ""; }; + C6AF9211275D613E0087ACDE /* conference_schedule_participants_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = conference_schedule_participants_default.png; sourceTree = ""; }; + C6AF9213275D67EB0087ACDE /* conference_schedule_time_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = conference_schedule_time_default.png; sourceTree = ""; }; + C6AF9217275E13790087ACDE /* ScheduledConferencesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledConferencesViewModel.swift; sourceTree = ""; }; + C6AF9219275E2E010087ACDE /* ConferenceWaitingRoomFragment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConferenceWaitingRoomFragment.swift; sourceTree = ""; }; + C6AF921B275E4AF50087ACDE /* ICSBubbleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ICSBubbleView.swift; sourceTree = ""; }; + C6AF921D275E51860087ACDE /* conference_schedule_calendar_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = conference_schedule_calendar_default.png; sourceTree = ""; }; + C6AF9225275F3D890087ACDE /* voip_conference_new_selected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = voip_conference_new_selected.png; sourceTree = ""; }; + C6AF9229275F6BA10087ACDE /* ConferenceHistoryDetailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConferenceHistoryDetailsView.swift; path = Classes/Swift/Conference/views/ConferenceHistoryDetailsView.swift; sourceTree = SOURCE_ROOT; }; C6B04D60274B954500F70559 /* ParticipantsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticipantsListView.swift; sourceTree = ""; }; C6B04D62274B95D400F70559 /* VoipParticipantCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoipParticipantCell.swift; sourceTree = ""; }; C6B04D66274BD61200F70559 /* VoipConferenceActiveSpeakerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoipConferenceActiveSpeakerView.swift; sourceTree = ""; }; @@ -2262,9 +2281,8 @@ 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( - C6EA2F492752237C008E60F8 /* Conference */, - C65A5D41272184CE005BA038 /* SwiftUtil */, - C65A5D2D2721683B005BA038 /* Voip */, + 614C087623D1A35E00217F80 /* linphone-Bridging-Header.h */, + C6649AAF275D28F400615896 /* Swift */, 22E0A81D111C44E100B04932 /* AboutView.h */, 22E0A81C111C44E100B04932 /* AboutView.m */, 636316D31A1DEBCB0009B839 /* AboutView.xib */, @@ -2380,14 +2398,6 @@ D3ED3E851586291B006C0DE4 /* TabBarView.m */, D38187FB15FE355D00C3EDCA /* TabBarView.xib */, D326483415887D4400930C67 /* Utils */, - 34216F3E1547EBCD00EA9777 /* VideoZoomHandler.h */, - 34216F3F1547EBCD00EA9777 /* VideoZoomHandler.m */, - C6DA657B261C950C0020CB43 /* VFSUtil.swift */, - 614C087723D1A35F00217F80 /* ProviderDelegate.swift */, - 614C087923D1A37400217F80 /* CallManager.swift */, - 6134812C2406CECC00695B41 /* ConfigManager.swift */, - 6134812E2407B35200695B41 /* AppManager.swift */, - 614C087623D1A35E00217F80 /* linphone-Bridging-Header.h */, ); path = Classes; sourceTree = ""; @@ -2514,7 +2524,7 @@ path = LinphoneUI; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + 29B97314FDCFA39411CA2CEA = { isa = PBXGroup; children = ( 8C23BCB71D82AAC3005F19BB /* linphone.entitlements */, @@ -2654,6 +2664,10 @@ 633FEBE11D3CD5570014B822 /* images */ = { isa = PBXGroup; children = ( + C6AF9225275F3D890087ACDE /* voip_conference_new_selected.png */, + C6AF921D275E51860087ACDE /* conference_schedule_calendar_default.png */, + C6AF9213275D67EB0087ACDE /* conference_schedule_time_default.png */, + C6AF9211275D613E0087ACDE /* conference_schedule_participants_default.png */, C6D1E42327595988008EB388 /* security_toggle_icon_green.png */, C6D1E42227595987008EB388 /* security_toggle_icon_grey.png */, C6D1E41D2759396B008EB388 /* voip_checkbox_checked.png */, @@ -3359,21 +3373,37 @@ C65A5D3427216CAB005BA038 /* ViewModel */ = { isa = PBXGroup; children = ( - C65A5D3727216CC0005BA038 /* MutableLiveData.swift */, C6EA2F6C2754E3DC008E60F8 /* MediatorLiveData.swift */, ); path = ViewModel; sourceTree = ""; }; - C65A5D41272184CE005BA038 /* SwiftUtil */ = { + C65A5D41272184CE005BA038 /* Util */ = { isa = PBXGroup; children = ( - C690CCB22757674700609077 /* GenericViews */, + C690CCB32757683800609077 /* BackNextNavigationView.swift */, C6EA2F5E2754CFB0008E60F8 /* TimestampUtils.swift */, - C683B211272276C100D4E15C /* Extensions */, + C65A5D3727216CC0005BA038 /* MutableLiveData.swift */, + C60C9F36278C3D36009A8F5B /* Pair.swift */, C65A5D3427216CAB005BA038 /* ViewModel */, ); - path = SwiftUtil; + path = Util; + sourceTree = ""; + }; + C6649AAF275D28F400615896 /* Swift */ = { + isa = PBXGroup; + children = ( + C6DA657B261C950C0020CB43 /* VFSUtil.swift */, + 614C087723D1A35F00217F80 /* ProviderDelegate.swift */, + 614C087923D1A37400217F80 /* CallManager.swift */, + 6134812C2406CECC00695B41 /* ConfigManager.swift */, + 6134812E2407B35200695B41 /* AppManager.swift */, + C6EA2F492752237C008E60F8 /* Conference */, + C683B211272276C100D4E15C /* Extensions */, + C65A5D41272184CE005BA038 /* Util */, + C65A5D2D2721683B005BA038 /* Voip */, + ); + path = Swift; sourceTree = ""; }; C6710F4D2722900A00ED888F /* Widgets */ = { @@ -3507,14 +3537,6 @@ path = Extensions; sourceTree = ""; }; - C690CCB22757674700609077 /* GenericViews */ = { - isa = PBXGroup; - children = ( - C690CCB32757683800609077 /* NavigationView.swift */, - ); - path = GenericViews; - sourceTree = ""; - }; C6D52B4727481D3F00904660 /* Conference */ = { isa = PBXGroup; children = ( @@ -3532,7 +3554,7 @@ children = ( C6EA2F4C2754C691008E60F8 /* data */, C6EA2F4B2754C683008E60F8 /* views */, - C6EA2F4D2754C69F008E60F8 /* viewmodels */, + C6EA2F4D2754C69F008E60F8 /* models */, ); path = Conference; sourceTree = ""; @@ -3542,6 +3564,11 @@ children = ( C690CCB0275764CD00609077 /* ConferenceSchedulingView.swift */, C61E409A275A20A300CCE602 /* ConferenceSchedulingSummaryView.swift */, + C6AF920D275D38090087ACDE /* ScheduledConferencesView.swift */, + C6AF920F275D4DD60087ACDE /* ScheduledConferencesCell.swift */, + C6AF921B275E4AF50087ACDE /* ICSBubbleView.swift */, + C6AF9229275F6BA10087ACDE /* ConferenceHistoryDetailsView.swift */, + C6AF9219275E2E010087ACDE /* ConferenceWaitingRoomFragment.swift */, ); path = views; sourceTree = ""; @@ -3556,12 +3583,13 @@ path = data; sourceTree = ""; }; - C6EA2F4D2754C69F008E60F8 /* viewmodels */ = { + C6EA2F4D2754C69F008E60F8 /* models */ = { isa = PBXGroup; children = ( C6EA2F662754DC45008E60F8 /* ConferenceSchedulingViewModel.swift */, + C6AF9217275E13790087ACDE /* ScheduledConferencesViewModel.swift */, ); - path = viewmodels; + path = models; sourceTree = ""; }; D326483415887D4400930C67 /* Utils */ = { @@ -3905,7 +3933,7 @@ fr, hu, ); - mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + mainGroup = 29B97314FDCFA39411CA2CEA; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -3971,7 +3999,7 @@ 633FEEE01D3CD55A0014B822 /* numpad_8_over~ipad@2x.png in Resources */, C6710FA42722B20000ED888F /* voip_single_contact_avatar.png in Resources */, 633FEDDC1D3CD5590014B822 /* call_start_body_disabled~ipad.png in Resources */, - 63E802DB1C625AEF000D5509 /* (null) in Resources */, + 63E802DB1C625AEF000D5509 /* BuildFile in Resources */, 633FEE2E1D3CD5590014B822 /* color_F.png in Resources */, C6710FA72722B20000ED888F /* voip_call_more.png in Resources */, 633FEDC51D3CD5590014B822 /* call_hangup_disabled@2x.png in Resources */, @@ -3999,6 +4027,7 @@ 633FEE101D3CD5590014B822 /* chat_list_indicator~ipad.png in Resources */, 633FEF331D3CD55A0014B822 /* route_speaker_selected@2x.png in Resources */, 633FEE6C1D3CD5590014B822 /* footer_dialer_disabled.png in Resources */, + C6AF921E275E51860087ACDE /* conference_schedule_calendar_default.png in Resources */, 633FEF231D3CD55A0014B822 /* route_bluetooth_default@2x.png in Resources */, C6A1BB4526E890BD00540D50 /* file_voice_default.png in Resources */, 633FED9C1D3CD5590014B822 /* add_field_default.png in Resources */, @@ -4053,6 +4082,7 @@ 633FEE491D3CD5590014B822 /* delete_default@2x.png in Resources */, 633FEF291D3CD55A0014B822 /* route_earpiece_default@2x.png in Resources */, C6710FAF2722B20000ED888F /* voip_conference_new.png in Resources */, + C6AF9226275F3D890087ACDE /* voip_conference_new_selected.png in Resources */, 633FEE271D3CD5590014B822 /* checkbox_checked@2x.png in Resources */, 61586B85217A17070038AC45 /* menu_assistant.png in Resources */, 633FEDCC1D3CD5590014B822 /* call_quality_indicator_0.png in Resources */, @@ -4146,6 +4176,7 @@ 615A2821217F6FBF0060F920 /* security_alert_indicator@2x.png in Resources */, 633FEE1E1D3CD5590014B822 /* chat_start_body_disabled.png in Resources */, 639CEB001A1DF4E4004DE38F /* UIHistoryCell.xib in Resources */, + C6AF9214275D67EB0087ACDE /* conference_schedule_time_default.png in Resources */, 633FEE841D3CD5590014B822 /* led_error.png in Resources */, 633FEDEA1D3CD5590014B822 /* call_status_outgoing.png in Resources */, 633FEF511D3CD55A0014B822 /* valid_disabled@2x.png in Resources */, @@ -4171,6 +4202,7 @@ 633FEE1B1D3CD5590014B822 /* chat_start_body_default@2x.png in Resources */, C6710FB22722B20000ED888F /* voip_calls_list.png in Resources */, 633FEE021D3CD5590014B822 /* cancel_edit_default.png in Resources */, + C6AF9212275D61420087ACDE /* conference_schedule_participants_default.png in Resources */, 633FEEE31D3CD55A0014B822 /* numpad_9_default.png in Resources */, 633FEE651D3CD5590014B822 /* footer_chat_disabled@2x.png in Resources */, 633FEEDD1D3CD55A0014B822 /* numpad_8_over.png in Resources */, @@ -4772,22 +4804,22 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManager/IQKeyboardManager.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/bctoolbox-ios.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/bctoolbox.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/belcard.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/belle-sip.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/belr.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/lime.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/linphone.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/linphonetester.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/mediastreamer2.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/msamr.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/mscodec2.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/msopenh264.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/mssilk.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/mswebrtc.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/msx264.framework", - "${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/ortp.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/bctoolbox-ios.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/bctoolbox.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/belcard.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/belle-sip.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/belr.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/lime.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/linphone.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/linphonetester.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/mediastreamer2.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/msamr.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/mscodec2.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/msopenh264.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/mssilk.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/mswebrtc.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/msx264.framework", + "${PODS_ROOT}/../../linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/ortp.framework", "${BUILT_PRODUCTS_DIR}/linphone-sdk/linphonesw.framework", ); name = "[CP] Embed Pods Frameworks"; @@ -4876,7 +4908,6 @@ 636BC9971B5F921B00C754CE /* UIIconButton.m in Sources */, 63423C0A1C4501D000D9A050 /* Contact.m in Sources */, C6710FE12722F0E400ED888F /* Avatar.swift in Sources */, - 34216F401547EBCD00EA9777 /* VideoZoomHandler.m in Sources */, 614C087823D1A35F00217F80 /* ProviderDelegate.swift in Sources */, CF7602D7210867E800749F76 /* RecordingsListView.m in Sources */, D3F83F8E15822ABE00336684 /* PhoneMainView.m in Sources */, @@ -4933,12 +4964,14 @@ D3EA540D1598528B0037DC6B /* ChatsListTableView.m in Sources */, D3EA5411159853750037DC6B /* UIChatCell.m in Sources */, D31B4B21159876C0002E6C72 /* UICompositeView.m in Sources */, + C6AF921A275E2E010087ACDE /* ConferenceWaitingRoomFragment.swift in Sources */, 8C9C5E0D1F83B2EF006987FA /* ChatConversationCreateCollectionViewController.m in Sources */, 631098491D4660580041F2B3 /* CountryListView.m in Sources */, D32B9DFC15A2F131000B6DEC /* FastAddressBook.m in Sources */, C6DB1DE22757E35F00A22704 /* StyledTextView.swift in Sources */, D350F20E15A43BB100149E54 /* AssistantView.m in Sources */, D3F795D615A582810077328B /* ChatConversationView.m in Sources */, + C6AF921C275E4AF50087ACDE /* ICSBubbleView.swift in Sources */, C60D265627299C94006238BB /* ControlsViewModel.swift in Sources */, D32B6E2915A5BC440033019F /* ChatConversationTableView.m in Sources */, C6D09F4B27438707003C2173 /* CallsListView.swift in Sources */, @@ -4978,20 +5011,23 @@ C6586149273E595700A0DBFC /* VoipExtraButtonsView.swift in Sources */, 633E41821D74259000320475 /* AssistantLinkView.m in Sources */, C6710F5727229DEE00ED888F /* TextStyle.swift in Sources */, + C6AF9218275E13790087ACDE /* ScheduledConferencesViewModel.swift in Sources */, D3807FE815C2894A005BE9BC /* IASKAppSettingsViewController.m in Sources */, D3807FEC15C2894A005BE9BC /* IASKSpecifierValuesViewController.m in Sources */, 8CA70AE41F9E39E400A3D2EB /* UIChatConversationInfoTableViewCell.m in Sources */, - C690CCB42757683800609077 /* NavigationView.swift in Sources */, + C690CCB42757683800609077 /* BackNextNavigationView.swift in Sources */, D3807FEE15C2894A005BE9BC /* IASKSettingsReader.m in Sources */, C6C65E8B2727274A00E48FC6 /* NumpadView.swift in Sources */, C6F2D501273B0EFC0071BA52 /* VoipDialog.swift in Sources */, C6710F53272297C400ED888F /* VoipTexts.swift in Sources */, D3807FF015C2894A005BE9BC /* IASKSettingsStore.m in Sources */, C6710FEB2726874D00ED888F /* ButtonTheme.swift in Sources */, + C6AF9210275D4DD60087ACDE /* ScheduledConferencesCell.swift in Sources */, 8CA70AD11F9E0AE100A3D2EB /* ChatConversationInfoView.m in Sources */, D3807FF215C2894A005BE9BC /* IASKSettingsStoreFile.m in Sources */, D3807FF415C2894A005BE9BC /* IASKSettingsStoreUserDefaults.m in Sources */, C6710FDC2722C3BB00ED888F /* UIVIewExtensions.swift in Sources */, + C60C9F37278C3D36009A8F5B /* Pair.swift in Sources */, C6F2D4F32739475C0071BA52 /* ActiveCallView.swift in Sources */, 639E9C801C0DB13D00019A75 /* UICheckBoxTableView.m in Sources */, CF7602E72108759A00749F76 /* UIRecordingCell.m in Sources */, @@ -5000,6 +5036,7 @@ C6710FE92723DD7D00ED888F /* CallControlButton.swift in Sources */, D3807FF815C2894A005BE9BC /* IASKPSSliderSpecifierViewCell.m in Sources */, C6EA2F592754C91C008E60F8 /* ScheduledConferenceData.swift in Sources */, + C6AF922A275F6BA10087ACDE /* ConferenceHistoryDetailsView.swift in Sources */, C6EA2F672754DC45008E60F8 /* ConferenceSchedulingViewModel.swift in Sources */, D3807FFA15C2894A005BE9BC /* IASKPSTextFieldSpecifierViewCell.m in Sources */, D3807FFC15C2894A005BE9BC /* IASKPSTitleValueSpecifierViewCell.m in Sources */, @@ -5008,6 +5045,7 @@ C6C65E89272723DC00E48FC6 /* UIVIewControllerExtensions.swift in Sources */, D3807FFE15C2894A005BE9BC /* IASKSlider.m in Sources */, D380800015C2894A005BE9BC /* IASKSwitch.m in Sources */, + C6AF920E275D38090087ACDE /* ScheduledConferencesView.swift in Sources */, D380800215C2894A005BE9BC /* IASKTextField.m in Sources */, D380801315C299D0005BE9BC /* ColorSpaceUtilites.m in Sources */, C64A854E2667B67200252AD2 /* EphemeralSettingsView.m in Sources */, @@ -5604,7 +5642,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.36+bd3b432\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.94+f4cb5df\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5731,7 +5769,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.36+bd3b432\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.94+f4cb5df\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5857,7 +5895,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.36+bd3b432\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.94+f4cb5df\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5982,7 +6020,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.36+bd3b432\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.94+f4cb5df\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;