Compare commits

...
Sign in to create a new pull request.

246 commits

Author SHA1 Message Date
QuentinArguillere
8f4a80500e Update changelog and podfile for 5.0.2 release 2023-03-16 11:19:36 +01:00
Christophe Deschamps
0913df3ac8 Hide non secure contacts when creating chatroom and lime is forced 2023-03-14 12:21:06 +01:00
Benoit Martins
b82f8bb84e Fix video player in RecordingsList 2023-03-14 11:59:18 +01:00
QuentinArguillere
470ec60d99 When deleting account, replace previous system alert pop up with a custom confirmation dialog that includes (if the account was a linphone account) a link to susbscribe.linphone.org for permanent deletion 2023-03-13 17:15:12 +01:00
Christophe Deschamps
211714167a Hide in call - more button - (3 dosts) when call stats or numpad is displayed 2023-03-09 11:51:18 +01:00
QuentinArguillere
590ebdbd66 Fix crash when creating a chatroom. Crash was caused by a weak reference on a search result in the magic search singleton. 2023-03-06 11:12:55 +01:00
Simon Morlat
a1dfc7c6c3 Remove msx264 references (no longer maintained). 2023-03-03 09:40:14 +01:00
Simon Morlat
8315ac543e Fix memory leaks. 2023-02-13 12:00:08 +01:00
Christophe Deschamps
bac9d2b98e Avatar icon when creating a contact (and more generally with empty display name) 2023-02-07 12:31:10 +01:00
QuentinArguillere
8cbeac0e54 Fix bad “Codecs” translation for English version that would end up written in arabic 2023-02-02 09:23:56 +01:00
QuentinArguillere
45c596dcec Update changeling, pod file and xcodeproj for 5.0.1 2023-02-02 09:23:27 +01:00
Christophe Deschamps
eca3dbe9d9 When pressing camera in audio only mode conference, enable video in SendReceive or ReceiveOnly mode in accordance with video activation policy of core 2023-01-31 17:08:35 +01:00
Benoit Martins
79cb76ac75 Add margin for chat bubble text in chat conversation view 2023-01-17 10:29:24 +01:00
Christophe Deschamps
d34ba5df7c Fix participant video display when second participant joins with video activated 2023-01-12 15:54:16 +01:00
Christophe Deschamps
8d873e496d Fix second particpant joining 2023-01-12 15:54:16 +01:00
Simon Morlat
aefe5e1ad0 Fix an issue where Callkit was accidentally requesting termination of a call while resuming another one. 2023-01-11 15:08:51 +01:00
Christophe Deschamps
5fe2af9d50 Hide active speaker miniature 2023-01-10 10:57:30 +00:00
Benoit Martins
fd627b4c4a Align encryptedButton with messages in chatConverstaionView 2023-01-10 10:03:00 +01:00
Benoit Martins
4c93d335ff Acquire a new reference to the chat message if old reference is nil 2023-01-09 10:38:35 +01:00
Benoit Martins
c47a50f5d1 Check if VFS mode is desactivated before writing media to gallery 2023-01-09 10:38:35 +01:00
Benoit Martins
ac4397cbbe Remove constraints auto download and write to gallery in setting view 2023-01-09 10:38:35 +01:00
QuentinArguillere
64c0e84cfa Set lime server URL for linphone accounts 2023-01-03 15:14:13 +01:00
QuentinArguillere
29bcbf64c0 Remove mistaken line from changeling 2022-12-06 17:43:49 +01:00
QuentinArguillere
b99327dbe5 Update Changelog and Podfile for 5.0.0 release 2022-12-06 14:28:22 +01:00
Benoit Martins
952b27c94f Minor changes of VFS_enabled_mode 2022-12-06 12:04:20 +01:00
Benoit Martins
5407a18b57 Save VFS and Auto_download_mode changes 2022-12-06 12:04:20 +01:00
Benoit Martins
9e52922fff In chat setting, when user enable VFS or set auto_download_mode on Never, disable auto_write_to_gallery_mode item.
When enable auto_write_to_gallery_mode, app disable VFS and auto_download_mode items.
2022-12-06 12:04:20 +01:00
Christophe Deschamps
4ade9f2c60 Automatically initiate video call (if remote party is in auti accept mode) 2022-12-06 11:40:54 +01:00
QuentinArguillere
abf598b654 Use linphone_core_search_chat_room rather than linphone_core_get_chat_room, in order to fix chatrooms being empty when using multiple accounts 2022-12-05 16:40:31 +01:00
Christophe Deschamps
0a05baea1e Fix pause screen not showing in some situation in Paused conferences 2022-12-05 10:33:34 +01:00
QuentinArguillere
4cd9da44de Add security to participant_imdn_status callback to avoid potential crash 2022-12-05 10:08:00 +01:00
Christophe Deschamps
a341bb6aec - Allow date picker to select today's date in the first place
- supports pre-ios14 device for date/time picking
2022-12-05 09:32:17 +01:00
Christophe Deschamps
ff5bafb7c0 When adding calls to a conference - enter the conference again if it was paused 2022-12-04 18:38:59 +01:00
Christophe Deschamps
9ba95b024c Disable call merge into conference if not admin of conference 2022-12-04 18:20:36 +01:00
Christophe Deschamps
05b905a3f1 Hide audio route selection view when clicked outsize 2022-12-02 17:09:11 +01:00
Christophe Deschamps
e8c2e92fc9 Added call back when bluetooth device is added or removed 2022-12-02 16:50:23 +01:00
Christophe Deschamps
82289f4523 Fix user interaction on send via chat switch on conference scheduling 2022-12-02 16:32:22 +01:00
Christophe Deschamps
6365c66d55 Fix ConferenceParticipantDeviceData not updated in certain circonstances 2022-12-02 16:14:17 +01:00
QuentinArguillere
61e645c238 Add post_quantum_secure shield icon to the project resources (file was actually already there but not included in the project) 2022-12-02 16:08:15 +01:00
Christophe Deschamps
9d5836b991 Hide video stats in conference call with video sending disabled 2022-12-02 15:28:54 +01:00
Christophe Deschamps
b78b4a1e6b Fix navigation when hanging up conference after editing participants list 2022-12-02 11:12:57 +01:00
Christophe Deschamps
6850282208 Conference waiting room : Dismiss layout picked if view is clicked elsewhere 2022-12-02 11:03:50 +01:00
Christophe Deschamps
9803678d6a Avoid unecessary setting of outgoing/incoming mutable value 2022-12-02 10:30:12 +01:00
QuentinArguillere
f2b36ed7f8 When Poping views, make sure we add the targeted one if we couldn’t already find it in the stack 2022-12-02 10:16:33 +01:00
QuentinArguillere
d39fb0661b Update version to 5.0.0 for incoming release 2022-12-01 17:44:46 +01:00
QuentinArguillere
b378682d0c Enable/disable phone’s idle timer when entering/leaving a conference 2022-12-01 17:39:53 +01:00
Benoit Martins
7da10d500f Change "VIEW(ChatConversationView).sharingMedia = nil" to "_sharingMedia = nil" in CharConversationView 2022-12-01 16:32:49 +01:00
Benoit Martins
4b5eaa3bc5 Fix image sharing from gallery, and fix view navigation 2022-12-01 16:32:49 +01:00
Benoit Martins
78c21a5c2b Change job iOS Tag to run on : macos-xcode13 2022-12-01 16:18:27 +01:00
Benoit Martins
fecbb85a81 Fix imdmIcon and ephemeral (icon/timer) in conversation when user want to transfer or reply messages 2022-12-01 16:02:02 +01:00
Benoit Martins
8582d93555 Fix for iPhone SE, the Security Badge of UIConfirmationDialog not displaying 2022-11-30 09:27:44 +01:00
Christophe Deschamps
a74d042a6a Restart video previow upon rotation to avoid crash 2022-11-28 16:42:40 +00:00
Christophe Deschamps
ad9163320c Landcape handling of video self view in 1-1 calls 2022-11-28 16:42:40 +00:00
Benoit Martins
5ab6a7ce1b Remove DTMF sound in Dialer View 2022-11-28 17:06:20 +01:00
QuentinArguillere
7ccdcbebcf Ensure that the result of linphone_event_log_get_chat_message(_event) is not nil before trying to use it 2022-11-28 17:00:29 +01:00
Benoit Martins
0fe94354d7 Fix ChatConversationView having no message after playing video received in chat 2022-11-28 16:52:59 +01:00
QuentinArguillere
3dc7d4fb15 Make sure we have callbacks before proceeding with chatroom deletions (could cause a crash when deleting chatrooms quickly after login and/or changing views) 2022-11-28 14:19:31 +01:00
Benoit Martins
a98aa42ca4 Disable features (Group chat and Security Chat) without Linphone account 2022-11-28 12:01:49 +01:00
Benoit Martins
84a817783d Removed IASKPSTitleValueSpecifierViewCell to align each cell of SettingView 2022-11-28 11:45:57 +01:00
Benoit Martins
6e846f092c Hide record button in conference views 2022-11-23 15:33:08 +01:00
Christophe Deschamps
fd9afd66fa Fix crash on marking chatroom as read 2022-11-23 13:34:10 +01:00
Christophe Deschamps
e5505a836c Merge branch 'fix/various_refacto_sync_android' into fix/various_refacto_sync_android2 2022-11-23 10:43:00 +01:00
Christophe Deschamps
781a9b07d1 Fix video freeze when alone in conference A/S mode 2022-11-23 09:21:21 +01:00
Christophe Deschamps
6a4ef41ab5 Various fixes in view model update propagation 2022-11-23 08:59:42 +01:00
Christophe Deschamps
c09519bcc1 Fixed dual spinner joining conference + set color to make it visible 2022-11-22 23:51:40 +01:00
Christophe Deschamps
9ab5a0f106 Enable full screen joining video conference with video 2022-11-22 23:51:40 +01:00
Christophe Deschamps
eff9964b12 Refactored to ConferenceWaitingRoomView 2022-11-22 23:51:40 +01:00
Christophe Deschamps
0202c7aa6b UTF8 handling in conference names in history 2022-11-22 23:51:40 +01:00
Christophe Deschamps
4cb99a62a0 Added local group call subject when merging calls 2022-11-22 23:51:40 +01:00
Christophe Deschamps
fc3f48363f Avoid getting back to waiting room after conf ends 2022-11-22 23:51:40 +01:00
Christophe Deschamps
3800aafd1f Full screen toggle 2022-11-22 23:51:40 +01:00
Christophe Deschamps
3a3c04bb23 Fix resuming call putting other calls on hold 2022-11-22 23:51:40 +01:00
Christophe Deschamps
8607469f68 Refactorisation of call views + various fixes 2022-11-22 23:51:15 +01:00
Christophe Deschamps
987ea21e05 Multi call various fixes 2022-11-22 23:50:58 +01:00
QuentinArguillere
b6ff317551 Check that contact address has a valid username when trying to match phone numbers 2022-11-22 17:25:23 +01:00
QuentinArguillere
9977644872 Add small 1px margin to make sure we scroll when Composing message appear while we just opened a chatroom 2022-11-22 17:24:44 +01:00
QuentinArguillere
d447158a25 Set _chatRoom to NULL when ChatConversationView disappears to avoir keeping invalid references 2022-11-22 17:24:10 +01:00
Benoit Martins
c3746801ac Remove Incoming condition in IncomingOutgoingCommonView for reset outgoing call timer 2022-11-22 15:50:35 +01:00
Benoit Martins
dbd56b5c38 Merge remote-tracking branch 'refs/remotes/origin/release/4.7' 2022-11-22 15:41:56 +01:00
Simon Morlat
c49013aa2b Fix an issue where audio does not work when a VoIP Call is resumed after a GSM call has been terminated by the remote party. 2022-11-22 14:34:20 +01:00
Benoit Martins
fc9c9b9508 Set fullscreen when start or join a conference 2022-11-22 14:28:22 +01:00
Benoit Martins
6cb4ea55f6 Fix scrollBadge and markAsRead in chat conversation 2022-11-22 11:12:27 +01:00
QuentinArguillere
54fb56d93c Fix crash when entering a chat conversation with audio messages that were not properly downloaded 2022-11-22 11:01:28 +01:00
Benoit Martins
ac49ee5d56 Merge remote-tracking branch 'refs/remotes/origin/release/4.7' 2022-11-22 09:57:43 +01:00
QuentinArguillere
03af1afac1 When calling setComposingVisible, check that the “is composing” message is visible as well as blocking text before scrolling to the bottom of the chat conversation view 2022-11-21 17:26:38 +01:00
Christophe Deschamps
233865747e Reworked IMDN cell calculation to make it independent from EventList 2022-11-21 15:56:30 +01:00
Christophe Deschamps
1291e7dee4 Protect _chatRoom dependent sub function calls 2022-11-21 14:54:19 +01:00
Benoit Martins
61ec8a37dd Merge remote-tracking branch 'refs/remotes/origin/release/4.7' 2022-11-21 13:20:55 +01:00
Benoit Martins
bd42b8855c Add hide_link_phone_number in RC setting for hide link_account view 2022-11-21 13:16:05 +01:00
Benoit Martins
7a78a14bbf Add hide_link_phone_number in RC setting for hide link_account view 2022-11-21 12:07:18 +01:00
Benoit Martins
236a8ee52f Fix recording list. Change of cell background color 2022-11-21 11:43:31 +01:00
Christophe Deschamps
62f7d8a620 0010414: Ios Build 2.1 (3) Probléme d'affichage de la ligne de notification d'écriture du correspondant. 2022-11-21 11:25:44 +01:00
Christophe Deschamps
8a47583130 set composig visible when _chatRoom is resolved 2022-11-21 11:23:01 +01:00
Christophe Deschamps
59098c6db0 Texts update 2022-11-18 09:29:23 +01:00
Christophe Deschamps
3043fc5c7a Do not force speaker on video activation/deactivation in conference 2022-11-18 09:27:26 +01:00
Christophe Deschamps
3e6bffd723 Added further missing translations 2022-11-18 09:00:50 +01:00
Christophe Deschamps
0cd72844b6 Missing translation 2022-11-18 08:46:19 +01:00
Christophe Deschamps
d5a6a233af Fix freeze in call by swiping screen right 2022-11-17 18:20:30 +01:00
Christophe Deschamps
65573ba1be Switch from audio only to active speaker using toggle video button (instead of disabling it) 2022-11-17 17:59:14 +01:00
Benoit Martins
669851065a Fix scrollToBottom for ChatConversation TableView 2022-11-17 17:24:22 +01:00
Benoit Martins
6fa24fff6d ScrollToBottom the ChatConversation TableView when composeLabel is visible 2022-11-17 17:24:22 +01:00
Benoit Martins
670dabaf77 Update popup ZRTP 2022-11-17 17:15:59 +01:00
Christophe Deschamps
93eecbc304 Appearance (dark/light) handling 2022-11-17 16:08:34 +00:00
Christophe Deschamps
e02580bade Removed redundant post of kLinphoneMessageReceived notification when chat conversation is opened 2022-11-17 13:44:27 +01:00
Christophe Deschamps
581d2dbf50 Removed ChatConversationTableView entire reloading upon entry addition 2022-11-17 13:44:27 +01:00
Christophe Deschamps
f7ad67390a Fix PQ Call Stats 2022-11-17 13:20:39 +01:00
Benoit Martins
c72d8913b8 Add a new button in the account link popup to never ask again user to link his account 2022-11-17 10:43:30 +01:00
Christophe Deschamps
490f9e96db Hide remote video view in 1-1 call when it is paused by remote 2022-11-15 18:11:02 +01:00
QuentinArguillere
5fb42ce09f When configuring a linphone account, set the core media encryption to SRTP by default 2022-11-15 16:30:07 +01:00
Christophe Deschamps
736059f699 Fix compilation 2022-11-15 07:35:11 +01:00
Christophe Deschamps
b63779b65e Enable camera in background context when app goes in background 2022-11-15 07:30:37 +01:00
Christophe Deschamps
0fc1f024d3 Enable camera on conference calls when app comes to foreground 2022-11-14 21:19:11 +01:00
QuentinArguillere
8b5668e8ce Prevent from from going to sleep when video is active in call 2022-11-14 16:46:55 +01:00
Christophe Deschamps
8909ae7279 Fix meeting icon in size menu 2022-11-10 17:56:49 +01:00
Christophe Deschamps
be5e303b09 Handle pause in A/S view when 1 or 2 participants 2022-11-10 17:30:27 +01:00
Christophe Deschamps
82704471ac Fix layout change when reaching more than 6 participants 2022-11-10 08:31:56 +01:00
Christophe Deschamps
988840d9a9 Fix compilation 2022-11-09 10:24:45 +01:00
Christophe Deschamps
a120a067c9 Revert "Fix crash upon device rotation when playing back voice memo"
This reverts commit d326b7f56e.
2022-11-08 18:22:56 +01:00
Christophe Deschamps
555428d62d Fix layout of chat conversation upon rotation 2022-11-08 18:09:40 +01:00
Christophe Deschamps
d326b7f56e Fix crash upon device rotation when playing back voice memo 2022-11-08 18:07:53 +01:00
Christophe Deschamps
59c90fe65a Handle meeting invite notification creation/update/cancellation 2022-11-08 15:25:12 +01:00
QuentinArguillere
3edcfb4222 Fix contact list in chat/conference creation view in order to include contacts from ldap/remote provisioning that have a sip uri 2022-11-08 11:55:54 +01:00
QuentinArguillere
77c4fb1747 Fix potential background crash when link popup was supposed to appear 2022-11-04 19:43:45 +01:00
QuentinArguillere
50f656d1ee Rework of non-native contacts: fix bug that would not display sip adresses in detail view, and fix bug that would lose the “createdFromLdapOrProvisioning” tag of non-native contacts after first launch. 2022-11-04 15:22:55 +01:00
Christophe Deschamps
06cbe9fa1d Fix avatars light/dark mode 2022-11-03 17:04:28 +01:00
QuentinArguillere
f3bc7add35 Check for nil chatroom in ChatConversationView.m::CellForRowAtIndexPath to avoid recurring crashes in background. Temporary fix. 2022-11-03 15:29:28 +01:00
Christophe Deschamps
9fcfe2b8de Cosmetic on chat contextual menu 2022-11-03 14:50:31 +01:00
Christophe Deschamps
d04f961559 Handle ICSs in chat list last message 2022-11-03 14:14:08 +01:00
QuentinArguillere
1e1c0fd3d3 Fix magic search to also display contacts that were created through remote provisioning, not just LDAP or native 2022-11-03 13:51:13 +01:00
Christophe Deschamps
7ddfff9c32 Unified avatar with VoIP 2022-11-03 13:35:38 +01:00
Christophe Deschamps
ecdb22dc5d (Ugly) work around to display differentiated avatars for numbers and usernames, until rest of the app is re-written in swift 2022-11-03 12:56:44 +01:00
Christophe Deschamps
d340db5680 Fix removal of UIChatCreateCollectionViewCell.xib 2022-11-03 12:05:24 +01:00
Christophe Deschamps
960046b87e Coscmetic on group chat info view 2022-11-03 10:13:14 +01:00
Christophe Deschamps
79a2235368 Cosmetic on conference list display 2022-11-03 10:09:47 +01:00
Christophe Deschamps
df53cc73e9 Avatar icon colour fixes 2022-11-03 08:39:02 +01:00
Christophe Deschamps
44cf121007 Cosmetic fixes on conference summary view 2022-11-03 08:37:37 +01:00
Christophe Deschamps
14ddbaa58a New message indicator icon in orange background 2022-11-03 00:28:44 +01:00
Christophe Deschamps
0c687d1dce Removed separator in popup menus (chat conversation, and chat bubble) 2022-11-03 00:09:41 +01:00
Christophe Deschamps
f00daf1997 Do not hide keyboard upon contact selection in group chat/call creation 2022-11-02 23:58:02 +01:00
Christophe Deschamps
c80ad228ef Reworked UICollectionView for displaying selected contacts in group chat and group calls 2022-11-02 23:47:49 +01:00
Christophe Deschamps
70b2671f19 No default date/time when scheduling a conference 2022-11-02 21:51:46 +01:00
Christophe Deschamps
3c8af14495 Conference subject hint changing if groupcall or meeting 2022-11-02 21:51:26 +01:00
Christophe Deschamps
51f23e91bb Implement onActiveSpeakerParticipantDevice api on conference 2022-11-01 22:29:06 +01:00
Christophe Deschamps
cd7951e590 Fix chat room creation view split 2022-10-21 17:21:20 +02:00
Christophe Deschamps
a6638d3c9b Update new class header 2022-10-21 10:57:31 +02:00
Christophe Deschamps
0e947803ce Set unique file name per message to avoid local display conflicts 2022-10-21 10:53:05 +02:00
Christophe Deschamps
666b2c7f48 Fix conference creation button state in conference list 2022-10-19 10:31:30 +02:00
Christophe Deschamps
ac32d80b51 Voipdialog anchor on iPads 2022-10-19 10:27:14 +02:00
Christophe Deschamps
e2dc5d4ad3 Show tabbar in chat on iPads (no back button) 2022-10-19 10:23:53 +02:00
Christophe Deschamps
eda3fa114a Merge branch 'fix/conf_fixes' into release/4.7 2022-10-19 09:59:24 +02:00
Christophe Deschamps
fd3afda60c Bulk bug fixes conference 2022-10-19 09:57:52 +02:00
Christophe Deschamps
670ad21770 Conference creation on iPad 2022-10-19 09:53:58 +02:00
Christophe Deschamps
0353180bd0 Conference creation on iPad 2022-10-19 09:53:01 +02:00
Christophe Deschamps
b402f08006 Send cancel conference iCS 2022-10-19 08:55:38 +02:00
QuentinArguillere
b495e4b8bb Disable basic chat button in contact history when force_lime_chat_rooms=1 2022-10-17 13:25:35 +02:00
Christophe Deschamps
a0a1e1a417 Bulk bug fixes conference 2022-10-14 18:31:23 +02:00
QuentinArguillere
821f2e30b7 Rename : “only_secure_chatrooms”=>”force_lime_chat_rooms” and “fetch_native_contacts”=>”enable_native_address_book” to match android 2022-10-14 10:13:48 +02:00
Christophe Deschamps
d0f83d6d1d Fix freeze tapping settings in waiting room 2022-10-12 17:30:20 +02:00
Christophe Deschamps
f705e14664 Removed double tap listener on audio click / landscape 2022-10-12 07:27:14 +02:00
Christophe Deschamps
35c772db3a Meeting edition fixes 2022-10-11 17:10:55 +02:00
QuentinArguillere
a57da3d499 Add [rtp]accept_any_encryption=1 and [video]max_conference_size=vga to linphonerc-factory 2022-10-11 16:41:18 +02:00
QuentinArguillere
b9ef7a83b5 Go to chatrooms after receiving “on_conference_joined” callback rather than when chatroom is created 2022-10-07 16:03:26 +02:00
Christophe Deschamps
559a9d8bc3 Handle video/no video updates from Active Speaker 2022-10-03 14:56:21 +02:00
Christophe Deschamps
f8d3a196f5 Fix display of conferences in history on ipads 2022-09-29 13:41:51 +02:00
Christophe Deschamps
298778e20d Fix AS layout not updating when joining a conf with more than 2 participants already 2022-09-29 13:23:36 +02:00
QuentinArguillere
b4103d2f4f Add “only_secure_chatrooms” parameter to rc. When set to “1”, only encrypted chatrooms can be created 2022-09-27 17:21:16 +02:00
QuentinArguillere
4c75c4222d Add “fetch_native_contacts” parameters to the rc-factory. If set to 0, we won’t ask for permission to use the phone native contacts 2022-09-26 10:46:58 +02:00
Christophe Deschamps
d8163fb55b Option to schedule meeting from group chat 2022-09-23 00:45:02 +02:00
Christophe Deschamps
68597369ed Send cancel information when deleting a meeting self-organized 2022-09-23 00:40:20 +02:00
Christophe Deschamps
388e3482a9 Multiple deletion of meetings (function activated by long click on meeting cell) 2022-09-22 23:58:36 +02:00
Christophe Deschamps
811a859430 Swipe to delete on conferences view 2022-09-22 23:58:25 +02:00
Christophe Deschamps
96543cdaca Fix (for good) background color for Me participant + repositionned the Me sticker bottom right in AS Landscape iso top right 2022-09-19 16:05:02 +02:00
Christophe Deschamps
23633d1ea4 Send conference invitation via secure chat when possible 2022-09-19 15:43:27 +02:00
Christophe Deschamps
cf78ae8ff9 Display a message when attempting to launch cancelled conference from history 2022-09-19 15:18:15 +02:00
Christophe Deschamps
2b252a04ff Added handling of meeting cancellation and edition 2022-09-19 11:37:55 +02:00
Christophe Deschamps
d54c9ef5a9 Set special color for local participants in conference 2022-09-19 11:37:42 +02:00
Christophe Deschamps
aaea844c4c Added remote mute indicator in AS Conference view with 2 participants 2022-09-19 11:37:36 +02:00
Christophe Deschamps
507060fb31 Video conference : use video preview when alone and in active speaker view 2022-09-08 17:38:36 +02:00
Christophe Deschamps
863731f775 Wrap display name of participants in conference grid mode 2022-09-08 13:16:10 +02:00
Christophe Deschamps
6a8c5a4440 Tidy up Avatar 2022-09-08 13:16:03 +02:00
QuentinArguillere
360c88b764 Change uri of video conference factory to “sip:videoconference-factory@sip.linphone.org” 2022-09-01 18:01:31 +02:00
QuentinArguillere
a4bfa37454 Remove line committed by mistake 2022-08-30 15:32:13 +02:00
QuentinArguillere
68bece526b Put set LDAP search loading view to be semi transparent, and add a message when more results are available 2022-08-30 15:32:08 +02:00
QuentinArguillere
8efce6e446 Do not apply international prefix when parsing uri that will not be used for a chat/call 2022-08-30 15:06:09 +02:00
Christophe Deschamps
4333e97932 Add more space to call view in landscape mode 2022-08-24 09:34:40 +02:00
Christophe Deschamps
77b9287195 Conference Active Speaker adjustments :
- Leave local participant always first (portrait) or top (landscape)
- Layout for 0, 1 and more participants
2022-08-24 09:17:11 +02:00
QuentinArguillere
a4bc64f2b3 Use linphone_core_interpret_url_2 and use the account params useInternationalPrefixForCallsAndChat 2022-08-23 16:30:17 +02:00
QuentinArguillere
2a54eae049 Add Post quantum ZRTP support, encryption section to the call stats. Add zrtp_key_agreements_suites for post quantum in the factory rc 2022-08-23 16:01:43 +02:00
Christophe Deschamps
2279a23db1 Added dropdown patch into Podfile 2022-08-19 16:01:37 +02:00
Christophe Deschamps
8743a01b88 Filter for terminated/scheduled conferences 2022-08-19 12:23:31 +02:00
Christophe Deschamps
8ed7ba7bee IMDM cell cosmetics 2022-08-19 12:21:48 +02:00
Christophe Deschamps
240d28e234 Display Meeting in recording list instead of conference server address 2022-08-19 12:21:40 +02:00
Christophe Deschamps
d352034e5e Display avatar icon instead of initials when username is a phone number 2022-08-19 12:21:28 +02:00
Christophe Deschamps
3c12854dc6 Conference participants list cosmetics 2022-08-19 12:21:19 +02:00
Christophe Deschamps
47b7820b58 Meeting invitations bubble :
- Fix description displaying (dynamic height)
- Fix reply/forward header overlap
- Remove copy text option from contextual menu of ICS
- Improved chat bubble context menu icons quality
- Do not show IMDM option menu in 1-1 chat room
- Remove extra space at bottom of contextual menu in chat bubbles
2022-08-19 12:21:10 +02:00
Christophe Deschamps
ed56de2527 Send conference invitations only if scheduled for laster 2022-08-19 12:20:32 +02:00
Christophe Deschamps
57399ec722 Conference scheduling graphical fixes :
- mandatory indicator icon truncated
- text entry colors (hint/text)
- Send via option moved inside scheduling form
- mandatory indicator placement
- conference summary cosmetics
2022-08-19 12:20:24 +02:00
Christophe Deschamps
57b676c775 - Ability to mute/unmute when outside a conference
- Call stats & Numpad views above controls
- Group call icon in call list cosmetic
- Calls List : fix icon size & layout issue on some iOS Devices
- Black background on video full screen conference
2022-08-19 12:20:14 +02:00
Christophe Deschamps
07e0f0ca80 Fix conference resume button 2022-08-19 12:20:06 +02:00
Christophe Deschamps
981a7c5a60 Cosmetic on dots menu & micro icon on conversation view 2022-08-19 12:19:56 +02:00
Christophe Deschamps
8ba4230c19 Conference Active Speaker view landscape mode 2022-08-19 12:19:48 +02:00
Christophe Deschamps
12f9de3384 Landscape layout in conference waiting room
If video is activated in waiting room with audio only mode selected, active speaker mode becomes selected
2022-08-19 12:19:39 +02:00
Christophe Deschamps
6af2602639 Fix local video content mode in 1-1 video calls 2022-08-19 12:19:25 +02:00
Christophe Deschamps
81964daa0c Prevent infinite loop on displaying ICS invitation 2022-08-19 12:19:06 +02:00
Quentin Monnier
7cdcdd4a4d bug fix :
-display/hide call stats when clicking on the network quality icon in the status bar of the Active Call View
2022-08-19 11:13:14 +02:00
Quentin Monnier
be1e726a0c bug fix:
-resume the active call by clicking on the play button and not by clicking anywhere on the screen when it is paused
2022-08-19 11:13:08 +02:00
Quentin Monnier
8d3fead61d bug fix:
-shows the "backToCallButton" when accessing the "TransferCallView" and the "AddCallView" from the "ExtraButtonMenu" in active calls
-delete the first blank character in the entered DTMF
-disable the hide action of the "ExtraButtonMenu" when clicking between buttons, in the background
-closes the "NumpadView" and the "CallStatsView" when the call ends
-displays the "StatusBar" in "Incoming/OutgoingCallView"
-define the height of the "EneteredDtmfTextView" (no longer automatic since the blank character has been removed)
2022-08-19 11:13:02 +02:00
QuentinArguillere
5dda082a75 Do not display accounts that have the “hidden” custom parameters enabled 2022-08-11 09:39:27 +02:00
QuentinArguillere
a77d6bb1b6 Fix chatroom when entering background then re-entering foreground during a call 2022-07-28 15:36:45 +02:00
QuentinArguillere
dcc832436e Allow the removal of account international prefix when it has been set 2022-07-28 15:36:28 +02:00
Christophe Deschamps
1559bf46c7 Activate CallKit & Audio Session upon immediate conf locally created 2022-07-27 17:43:41 +02:00
QuentinArguillere
4ec4723fbb When destroying the linphone core, also destroy the magic search singleton This fixes bugs that would break the LDAP contact search after a remote provisioning. 2022-07-27 15:23:05 +02:00
QuentinArguillere
d25cbbf609 To avoid possible race condition, reload contacts in the global state change callbacks 2022-07-27 15:22:59 +02:00
Christophe Deschamps
90c367ccca Workarounds for active speaker: do not display 'me' device name when speaking + showing ourselves when alone 2022-07-22 12:09:49 +02:00
Christophe Deschamps
3e22ea3a80 Fix Toast not dismissing 2022-07-21 12:22:21 +02:00
Christophe Deschamps
49bee7c6a0 ICS Reply 2022-07-11 23:44:22 +02:00
Christophe Deschamps
907cc94fc6 Conference side menu icon 2022-07-11 23:44:13 +02:00
Christophe Deschamps
b04a306443 Fix locally paused state in view model 2022-07-11 23:44:06 +02:00
Christophe Deschamps
ffe795bf02 Merge branch 'release/4.7' of gitlab.linphone.org:BC/public/linphone-iphone into release/4.7 2022-07-11 20:52:24 +02:00
Christophe Deschamps
5ee6695cdc Adapt to latest sdk change related to conference + implemented joining spinner + refactored conference miniatures update 2022-07-11 20:52:15 +02:00
QuentinArguillere
d14a0d3c10 In group chatrooms, added the “add to contact” contextual action when interacting with a message coming from someone that is not registered in your phone contacts 2022-07-08 17:21:31 +02:00
QuentinArguillere
9612e21162 Fix “Add to contact” action for encrypted chatrooms 2022-07-08 17:11:12 +02:00
QuentinArguillere
81688b416b Do not display “Infos” action for messages in 1-1 chatrooms 2022-07-05 13:51:40 +02:00
QuentinArguillere
da08af7e3f “Back” button in chatrooms should now behave more in line with what is expected : return to the chatlist view in most cases 2022-07-05 13:50:15 +02:00
QuentinArguillere
9ab70c60fe In chat conversation, hide keyboard when the action pop-up bubble (reply, transfer, etc) appears, and show keyboard when “reply’ is selected. This matches the usual behaviours of other messaging apps 2022-07-04 17:24:33 +02:00
Christophe Deschamps
e3ed160c54 Dropdown picker more user-friendly 2022-07-03 19:00:52 +02:00
Christophe Deschamps
7d71bd500e Merge branch 'release/4.7' of gitlab.linphone.org:BC/public/linphone-iphone into release/4.7 2022-07-03 19:00:47 +02:00
QuentinArguillere
6ca276b718 Update iOS version on Podfile from 9.0 to 10.0 2022-06-30 15:57:52 +02:00
Christophe Deschamps
0d0c0225f1 Fix calendar event pre-set fields 2022-06-30 15:21:13 +02:00
Christophe Deschamps
6adeb43fdc Added calendar sharing of ICS from bubble chat 2022-06-30 12:27:41 +02:00
Christophe Deschamps
761a47ad87 Prevent keyboard from hiding description field in conference scheduling view 2022-06-29 13:12:50 +02:00
Christophe Deschamps
343a30d93b Admin set/unset notification in conference 2022-06-29 08:53:00 +02:00
QuentinArguillere
54071f9d15 Avoid crash in settings menu for iOS < 13.0 2022-06-28 17:21:01 +02:00
QuentinArguillere
924b499496 Disable voip & remote push notification by default when using the assistant to login with an external sip account 2022-06-28 17:20:17 +02:00
QuentinArguillere
10b28cd6cf Fix canAdminEphemeral function in ChatConversationViews so that only encrypted chatrooms can possibly return “true”. Also fix bug causing the wrong row to be called in chatroom popup action menu 2022-06-27 16:50:38 +02:00
QuentinArguillere
33b07703ca Always display LDAP contacts in priority. If a local contact matches, partially or totally, the LDAP contact, still display both. 2022-06-27 11:43:33 +02:00
QuentinArguillere
74c69b3779 Always Hide edit/delete buttons for LDAP contact details view (could still appear if accessed through a “previous” button) 2022-06-27 11:43:27 +02:00
QuentinArguillere
ef8e1baa92 Fix typo in LDAP.plist file 2022-06-24 16:54:55 +02:00
QuentinArguillere
6e93601508 Fix typos in LDAP settings menu that would cause a partial loss of the configuration when exiting the menu 2022-06-24 16:18:15 +02:00
QuentinArguillere
0e31b555c1 Implement notification mute for group & encrypted chatrooms 2022-06-23 14:48:51 +02:00
QuentinArguillere
930dd74c89 Fix bugs when rotating from portrait to landscape mode : edit/delete action shown as available for LDAP contacts, or Contact Name appearing despite being currently in edit mode 2022-06-23 13:33:16 +02:00
QuentinArguillere
f847d8cfe3 Fix bug that could cause a clipping/hiding of a contact name depending on the phone orientation 2022-06-23 13:33:06 +02:00
QuentinArguillere
263b460372 When hitting the “back” button on a contact details view, redirect to the ContactListView by default. Only return to the history details view if we specifically came from there. 2022-06-23 13:33:00 +02:00
QuentinArguillere
4c690f9274 Now allow duplicate contacts to be displayed (useful if you have a local contact AND the contact exists with more informations in a LDAP server) 2022-06-23 13:32:54 +02:00
QuentinArguillere
ff14c18f1c Fix GUI when remote provisioning fails due to the user already having accounts 2022-06-23 13:32:49 +02:00
Christophe Deschamps
97708e9096 Fallback to AS if MaxPart > 6 only if Grid is active 2022-06-23 09:49:02 +02:00
192 changed files with 4660 additions and 2183 deletions

View file

@ -7,11 +7,12 @@ variables:
job-ios:
stage: build
tags: [ "macmini-m1-xcode13" ]
tags: [ "macos-xcode13" ]
script:
- pod install --repo-update
- pwd
- sed 's/fileprivate let tableView =/public let tableView =/g' ./Pods/DropDown/DropDown/src/DropDown.swift > tmp.swift && mv -f tmp.swift ./Pods/DropDown/DropDown/src/DropDown.swift
- xcodebuild archive -scheme $archive_scheme -archivePath ./$archive_path -configuration Release -workspace ./linphone.xcworkspace -UseModernBuildSystem=YES -destination 'generic/platform=iOS'
- xcodebuild -exportArchive -archivePath ./$archive_path -exportPath ./$export_path -exportOptionsPlist ./$export_options_plist -allowProvisioningUpdates -UseModernBuildSystem=YES -destination 'generic/platform=iOS'

View file

@ -10,15 +10,61 @@ Group changes to describe their impact on the project, as follows:
Fixed for any bug fixes.
Security to invite users to upgrade in case of vulnerabilities.
## [5.0.2] - 2023-16-03
### Changed
- Update linphone SDK to 5.2.32
### Fixed
- Performance issue causing a global slowing of the app, especially at launch
- Fix several memory leaks and crashes
## [5.0.1] - 2023-10-01
### Changed
- Update linphone SDK to 5.2.11
### Fixed
- Makes sure sip.linphone.org accounts have a LIME X3DH server URL for E2E chat messages encryption
- Fix potential crash when displaying images received in a chatroom
- Fix bug that would cause the previous call to be terminated when resuming another call that was paused
- Fix participant video display in conferences when a second participant joined with video enabled
## [5.0.0] - 2022-12-06
### Added
- Post Quantum encryption when using ZRTP
- Conference creation with scheduling, video, different layouts, showing who is speaking and who is muted, etc...
- Group calls directly from group chat rooms
- Chat rooms can be individually muted (no notification when receiving a chat message)
- Outgoing call video in early-media if requested by callee
- Call recordings can be exported
- Setting to prevent international prefix from account to be applied to call & chat
- Add a "Never ask again" option to the "Link my account" pop-up when starting the app
### Changed
- In-call views have been re-designed
- Improved how contact avatars are generated
- 3-dots menu even for basic chat rooms with more options
- Update linphone SDK to 5.2.0
### Fixed
- Chatroom appearing as empty when being logged on multiple accounts
- Chatroom appearing as empty after playing a video file inside it
- Fix potential crash when entering a chatroom
- Fix potential crash when accessing to the delivery infos of a message in a group chat.
- IMDN logo not properly displayed when transfering or replying to a message with media (voice message, photo...)
- Clarified view when sending an image from the galery
- Various audio route fixes for CallKit and IOS 16
## [4.6.4] - 2021-08-06
## [4.6.4] - 2022-08-06
### Changed
- Update linphone SDK to 5.1.42
### Fixed
- Prevent possible application freeze and crash when creating a new chatroom, depending on the phone's contacts.
## [4.6.3] - 2021-02-06
## [4.6.3] - 2022-02-06
### Added
- New "Contacts" menu in the settings, which allows the use of LDAP configurations
@ -33,7 +79,7 @@ Group changes to describe their impact on the project, as follows:
- Display bug when changing audio device
## [4.6.2] - 2021-07-03
## [4.6.2] - 2022-07-03
### Fixed
- Bug preventing the activation of the phone speaker during calls
@ -41,7 +87,7 @@ Group changes to describe their impact on the project, as follows:
- Bug causing IMDNs to be missing in some chatrooms
- Update linphone SDK to 5.1.7
## [4.6.1] - 2021-04-03
## [4.6.1] - 2022-04-03
### Fixed
- Crash in chatroom info view after entering background and re-entering foreground
@ -49,7 +95,7 @@ Group changes to describe their impact on the project, as follows:
- Hard to see text (written in black) on dark mode
- Removed duplicate push authorization request pop up on install
## [4.6.0] - 2021-31-02
## [4.6.0] - 2022-31-02
### Added
- Reply to chat message feature (with original message preview)

View file

@ -127,7 +127,9 @@ static UICompositeViewDescription *compositeDescription = nil;
if (!mustRestoreView) {
new_account = NULL;
number_of_accounts_before = bctbx_list_size(linphone_core_get_account_list(LC));
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
number_of_accounts_before = bctbx_list_size(accounts);
bctbx_free(accounts);
[self resetTextFields];
[self changeView:_welcomeView back:FALSE animation:FALSE];
}
@ -519,6 +521,10 @@ static UICompositeViewDescription *compositeDescription = nil;
#endif
linphone_push_notification_config_set_provider(pushConfig, PROVIDER_NAME);
if (strcmp(creatorDomain, "sip.linphone.org")==0) {
linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionSRTP);
}
new_account = linphone_core_create_account(LC, accountParams);
linphone_account_params_unref(accountParams);
@ -705,7 +711,7 @@ static UICompositeViewDescription *compositeDescription = nil;
LinphoneAccount *default_account = linphone_core_create_account(LC, default_account_params);
const char *identity = linphone_account_params_get_identity(linphone_account_get_params(default_account));
if (identity) {
LinphoneAddress *default_addr = linphone_core_interpret_url(LC, identity);
LinphoneAddress *default_addr = linphone_core_interpret_url_2(LC, identity, false);
if (default_addr) {
const char *domain = linphone_address_get_domain(default_addr);
const char *username = linphone_address_get_username(default_addr);
@ -1011,10 +1017,13 @@ static UICompositeViewDescription *compositeDescription = nil;
[LinphoneManager.instance lpConfigSetInt:[NSDate new].timeIntervalSince1970
forKey:@"must_link_account_time"];
[LinphoneManager.instance configurePushProviderForAccounts];
if (number_of_accounts_before < bctbx_list_size(linphone_core_get_account_list(LC))) {
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
if (number_of_accounts_before < bctbx_list_size(accounts)) {
LOGI(@"A proxy config was set up with the remote provisioning, skip assistant");
[self onDialerClick:nil];
}
bctbx_free(accounts);
_waitView.hidden = true;
if (nextView == nil) {
@ -1149,7 +1158,11 @@ static UICompositeViewDescription *compositeDescription = nil;
_outgoingView = DialerView.compositeViewDescription;
[self configureAccount];
} else if (status == LinphoneAccountCreatorStatusAccountExist) {
_outgoingView = AssistantLinkView.compositeViewDescription;
if([LinphoneManager.instance lpConfigIntForKey:@"hide_link_phone_number"]){
_outgoingView = DialerView.compositeViewDescription;
}else{
_outgoingView = AssistantLinkView.compositeViewDescription;
}
[self configureAccount];
} else {
if (resp) {
@ -1584,10 +1597,11 @@ UIColor *previousColor = (UIColor*)[sender backgroundColor]; \
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[self presentViewController:errView animated:YES completion:nil];
_waitView.hidden = TRUE;
} else {
linphone_core_set_provisioning_uri(LC, [self addSchemeToProvisiionninUriIMissing:[self findTextField:ViewElement_URL].text].UTF8String);
[self resetLiblinphone:TRUE];

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -175,13 +175,13 @@
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KRQ-Fm-3cQ" userLabel="addedContacts" customClass="UICollectionView">
<rect key="frame" x="8" y="110" width="398" height="70"/>
<rect key="frame" x="8" y="110" width="398" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<accessibility key="accessibilityConfiguration" label="addedContacts"/>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="6">
<rect key="frame" x="5" y="178" width="403" height="610"/>
<rect key="frame" x="5" y="158" width="403" height="610"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="separatorColor" red="0.67030966281890869" green="0.71867996454238892" blue="0.75078284740447998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -97,7 +97,7 @@
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XSI-9T-NtW" userLabel="addButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="382" y="4" width="24" height="21"/>
<rect key="frame" x="382" y="2" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -218,7 +218,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="15" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="%@ is composing..." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="fpY-Fv-ht2" userLabel="composeLabel">
<rect key="frame" x="0.0" y="1" width="414" height="22"/>
<rect key="frame" x="10" y="-5" width="406" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label=""/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -364,7 +364,7 @@
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tjL-Vc-5gN" userLabel="encryptedButton">
<rect key="frame" x="359" y="10" width="34" height="40"/>
<rect key="frame" x="372" y="8" width="34" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<state key="normal" image="security_1_indicator.png"/>
<connections>
@ -608,7 +608,6 @@
</state>
<state key="selected" image="vr_on.png"/>
<connections>
<action selector="onPictureClick:" destination="-1" eventType="touchUpInside" id="qhP-B0-dkG"/>
<action selector="onVrStart:" destination="-1" eventType="touchUpInside" id="yWJ-st-yz2"/>
</connections>
</button>

View file

@ -12,6 +12,7 @@
<connections>
<outlet property="addButton" destination="6" id="91"/>
<outlet property="allButton" destination="4" id="27"/>
<outlet property="ldapMoreResultsLabel" destination="cDH-mL-cHP" id="3d9-gp-Hog"/>
<outlet property="linphoneButton" destination="5" id="31"/>
<outlet property="loadingLabel" destination="qSa-Ba-dq9" id="CPa-pO-OQD"/>
<outlet property="loadingView" destination="CM2-Aq-Q3g" id="uie-SJ-TKf"/>
@ -179,6 +180,14 @@
<outlet property="delegate" destination="TJG-JZ-YRR" id="V1N-gI-U4J"/>
</connections>
</tableView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="More results are available, refine search to see them" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cDH-mL-cHP" userLabel="ldapMoreResultsLabel">
<rect key="frame" x="8" y="110" width="359" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No contact found in your address book" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JR3-k7-gVP" userLabel="emptyTableLabel">
<rect key="frame" x="0.0" y="110" width="375" height="449"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
@ -187,7 +196,7 @@
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view hidden="YES" contentMode="scaleToFill" id="CM2-Aq-Q3g">
<view hidden="YES" alpha="0.80000000000000004" contentMode="scaleToFill" id="CM2-Aq-Q3g">
<rect key="frame" x="0.0" y="110" width="375" height="449"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
@ -209,7 +218,7 @@
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<point key="canvasLocation" x="6.5217391304347831" y="142.29910714285714"/>
<point key="canvasLocation" x="5.7971014492753632" y="141.96428571428569"/>
</view>
<tableViewController id="TJG-JZ-YRR" userLabel="tableController" customClass="ContactsListTableView">
<connections>

View file

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_0" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -11,6 +14,7 @@
<outlet property="addressLabel" destination="EoB-ux-sD7" id="Ajw-2s-M6X"/>
<outlet property="avatarImage" destination="23" id="43"/>
<outlet property="backButton" destination="9" id="Pqj-y9-hqc"/>
<outlet property="chatButton" destination="obZ-W7-q8P" id="96n-Oe-Gm0"/>
<outlet property="contactLabel" destination="25" id="rTL-Ut-42o"/>
<outlet property="emptyLabel" destination="hvz-CS-NME" id="Qws-r1-XMh"/>
<outlet property="encryptedChatView" destination="JU4-bf-tVI" id="j6f-qz-VKd"/>
@ -26,19 +30,19 @@
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="HKr-sq-hGv" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4">
<rect key="frame" x="0.0" y="42" width="667" height="267"/>
<rect key="frame" x="-1" y="41" width="389" height="735"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view alpha="0.90000000000000002" tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6" userLabel="topBar">
<rect key="frame" x="0.0" y="0.0" width="667" height="66"/>
<rect key="frame" x="0.0" y="0.0" width="389" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9" userLabel="backButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="0.0" y="0.0" width="128" height="66"/>
<rect key="frame" x="0.0" y="0.0" width="74" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -50,7 +54,7 @@
</connections>
</button>
<button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="50" userLabel="addButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="539" y="0.0" width="128" height="66"/>
<rect key="frame" x="314" y="0.0" width="75" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Add to contact"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -62,14 +66,14 @@
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view>
<view tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="33" userLabel="headerView">
<rect key="frame" x="0.0" y="66" width="667" height="250"/>
<rect key="frame" x="0.0" y="66" width="389" height="250"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="8" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="23" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="244" y="8" width="178" height="100"/>
<rect key="frame" x="141" y="8" width="104" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
@ -77,7 +81,7 @@
</accessibility>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="John Doe" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="25" userLabel="contactLabel">
<rect key="frame" x="0.0" y="110" width="667" height="40"/>
<rect key="frame" x="0.0" y="110" width="389" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/>
@ -85,7 +89,7 @@
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" tag="10" contentMode="scaleAspectFit" fixedFrame="YES" image="linphone_user.png" translatesAutoresizingMaskIntoConstraints="NO" id="mfN-Ai-9RX" userLabel="linphoneImage" customClass="UIRoundedImageView">
<rect key="frame" x="642" y="124" width="15" height="15"/>
<rect key="frame" x="363" y="124" width="16" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
@ -93,7 +97,7 @@
</accessibility>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="11" contentMode="left" fixedFrame="YES" text="johndoe@sip.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="EoB-ux-sD7" userLabel="addressLabel">
<rect key="frame" x="0.0" y="158" width="667" height="23"/>
<rect key="frame" x="0.0" y="158" width="389" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
@ -101,11 +105,11 @@
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k0D-99-OKO" userLabel="optionsView">
<rect key="frame" x="0.0" y="189" width="667" height="44"/>
<rect key="frame" x="0.0" y="189" width="389" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" tag="13" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5eX-W0-T4B" userLabel="callButton">
<rect key="frame" x="179" y="0.0" width="51" height="51"/>
<rect key="frame" x="98" y="0.0" width="51" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="call_start_body_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -117,7 +121,7 @@
</connections>
</button>
<button opaque="NO" tag="12" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="obZ-W7-q8P" userLabel="chatButton">
<rect key="frame" x="317" y="0.0" width="50" height="51"/>
<rect key="frame" x="174" y="0.0" width="50" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="chat_start_body_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -129,11 +133,11 @@
</connections>
</button>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JU4-bf-tVI" userLabel="encryptedChatView">
<rect key="frame" x="456" y="-1" width="50" height="51"/>
<rect key="frame" x="251" y="-2" width="49" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="E2n-mF-saI" userLabel="encryptedChatButton" customClass="UIIconButton">
<rect key="frame" x="-1" y="0.0" width="51" height="51"/>
<rect key="frame" x="0.0" y="-2" width="51" height="53"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Chat"/>
<state key="normal" image="chat_start_body_default.png">
@ -154,35 +158,35 @@
</subviews>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<tableView clipsSubviews="YES" tag="6" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" rowHeight="30" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="k6N-Av-eOu">
<rect key="frame" x="0.0" y="316" width="667" height="0.0"/>
<rect key="frame" x="0.0" y="316" width="389" height="419"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<connections>
<outlet property="dataSource" destination="baU-d4-eu3" id="p7o-Mx-Kmc"/>
<outlet property="delegate" destination="baU-d4-eu3" id="iS5-xg-0C2"/>
</connections>
</tableView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="40" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No log selected" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hvz-CS-NME" userLabel="emptyLabel">
<rect key="frame" x="0.0" y="66" width="667" height="201"/>
<rect key="frame" x="0.0" y="65" width="389" height="670"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dEJ-xc-518" userLabel="waitView">
<rect key="frame" x="0.0" y="0.0" width="667" height="267"/>
<rect key="frame" x="0.0" y="0.0" width="389" height="735"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="NK3-ME-9jd" userLabel="activityIndicatorView">
<rect key="frame" x="326" y="122" width="20" height="20"/>
<rect key="frame" x="186" y="352" width="20" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</activityIndicatorView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<gestureRecognizers/>
</view>
</subviews>
@ -193,23 +197,23 @@
<point key="canvasLocation" x="-3.2000000000000002" y="22.488755622188908"/>
</view>
<view contentMode="scaleToFill" id="LBc-mh-ozk" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NHC-7w-48z">
<rect key="frame" x="0.0" y="42" width="667" height="333"/>
<rect key="frame" x="-2" y="42" width="391" height="800"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Rtv-hu-bCz" userLabel="topBar">
<rect key="frame" x="0.0" y="0.0" width="667" height="66"/>
<rect key="frame" x="0.0" y="0.0" width="391" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="3" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="imageView:JOe-5t-C7f:image" translatesAutoresizingMaskIntoConstraints="NO" id="JOe-5t-C7f" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="667" height="66"/>
<rect key="frame" x="0.0" y="0.0" width="391" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NJl-Lb-CU6" userLabel="backButton" customClass="UIIconButton">
<rect key="frame" x="0.0" y="0.0" width="71" height="66"/>
<rect key="frame" x="0.0" y="0.0" width="41" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -221,7 +225,7 @@
</connections>
</button>
<button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="O7r-6t-b7w" userLabel="addButton" customClass="UIIconButton">
<rect key="frame" x="594" y="0.0" width="73" height="66"/>
<rect key="frame" x="346" y="0.0" width="45" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Add to contact"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -235,7 +239,7 @@
</subviews>
</view>
<tableView clipsSubviews="YES" tag="6" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" rowHeight="30" sectionHeaderHeight="44" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="2jK-gw-ULv">
<rect key="frame" x="0.0" y="168" width="667" height="165"/>
<rect key="frame" x="0.0" y="167" width="391" height="633"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
@ -244,11 +248,11 @@
</connections>
</tableView>
<view tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Mwp-y3-g1b" userLabel="headerView">
<rect key="frame" x="0.0" y="66" width="667" height="102"/>
<rect key="frame" x="0.0" y="66" width="391" height="102"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="8" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="d9m-G0-1u3" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="28" y="8" width="88" height="86"/>
<rect key="frame" x="16" y="8" width="51" height="86"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
@ -256,7 +260,7 @@
</accessibility>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Qbg-hm-bd7" userLabel="contactLabel">
<rect key="frame" x="160" y="8" width="356" height="50"/>
<rect key="frame" x="94" y="8" width="207" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/>
@ -264,7 +268,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="11" contentMode="left" fixedFrame="YES" text="johndoe@sip.linphone.org" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="XJa-f6-K0y" userLabel="addressLabel">
<rect key="frame" x="160" y="56" width="356" height="38"/>
<rect key="frame" x="94" y="54" width="207" height="39"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
@ -272,11 +276,11 @@
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="teU-AB-8hO" userLabel="optionsView">
<rect key="frame" x="0.0" y="29" width="667" height="44"/>
<rect key="frame" x="0.0" y="29" width="391" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" tag="13" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pBo-Oo-bAW" userLabel="callButton">
<rect key="frame" x="491" y="0.0" width="51" height="51"/>
<rect key="frame" x="271" y="0.0" width="51" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="call_start_body_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -288,7 +292,7 @@
</connections>
</button>
<button opaque="NO" tag="12" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iDG-Mn-jm2" userLabel="chatButton">
<rect key="frame" x="551" y="0.0" width="51" height="51"/>
<rect key="frame" x="303" y="0.0" width="51" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="chat_start_body_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -300,11 +304,11 @@
</connections>
</button>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="m90-u6-x3J" userLabel="encryptedChatView">
<rect key="frame" x="611" y="0.0" width="51" height="51"/>
<rect key="frame" x="336" y="-1" width="51" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8fY-hz-ECC" userLabel="encryptedChatButton" customClass="UIIconButton">
<rect key="frame" x="0.0" y="0.0" width="51" height="51"/>
<rect key="frame" x="0.0" y="-2" width="51" height="53"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Chat"/>
<state key="normal" image="chat_start_body_default.png">
@ -325,7 +329,7 @@
</subviews>
</view>
<imageView hidden="YES" userInteractionEnabled="NO" tag="10" contentMode="scaleAspectFit" fixedFrame="YES" image="linphone_user.png" translatesAutoresizingMaskIntoConstraints="NO" id="G2O-Yh-fZA" userLabel="linphoneImage">
<rect key="frame" x="123" y="8" width="30" height="25"/>
<rect key="frame" x="71" y="8" width="19" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
@ -336,7 +340,7 @@
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="40" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No log selected" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IHY-Yg-pkN" userLabel="emptyLabel">
<rect key="frame" x="0.0" y="66" width="667" height="267"/>
<rect key="frame" x="0.0" y="65" width="391" height="735"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -344,11 +348,11 @@
<nil key="highlightedColor"/>
</label>
<view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X29-vB-VIz" userLabel="waitView">
<rect key="frame" x="0.0" y="0.0" width="667" height="333"/>
<rect key="frame" x="0.0" y="0.0" width="391" height="800"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="7l5-ZU-CbW" userLabel="activityIndicatorView">
<rect key="frame" x="326" y="155" width="20" height="20"/>
<rect key="frame" x="185" y="385" width="21" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</activityIndicatorView>
</subviews>
@ -384,106 +388,113 @@
<image name="contact_add_disabled.png" width="55.200000762939453" height="47.200000762939453"/>
<image name="imageView:JOe-5t-C7f:image" width="2" height="2">
<mutableData key="keyedArchiveRepresentation">
YnBsaXN0MDDUAQIDBAUGUlNYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK8QEQcI
ERYbHCAhKCsuOEBESExPVSRudWxs1AkKCwwNDg8QViRjbGFzc1xOU0ltYWdlRmxhZ3NWTlNSZXBzV05T
Q29sb3KAEBIAwAAAgAKACtISCRMVWk5TLm9iamVjdHOhFIADgAnSEgkXGqIYGYAEgAWACBAA0h0JHh9f
EBROU1RJRkZSZXByZXNlbnRhdGlvboAGgAdPEQI+TU0AKgAAAAzh4eHhAA8BAAADAAAAAQACAAABAQAD
AAAAAQACAAABAgADAAAAAQAIAAABAwADAAAAAQABAAABBgADAAAAAQABAAABCgADAAAAAQABAAABEQAE
AAAAAQAAAAgBEgADAAAAAQABAAABFQADAAAAAQABAAABFgADAAAAAQACAAABFwAEAAAAAQAAAAQBHAAD
AAAAAQABAAABKAADAAAAAQACAAABUwADAAAAAQABAACHcwAHAAABeAAAAMYAAAAAAAABeGFwcGwCEAAA
bW50ckdSQVlYWVogB9UABwABAAAAAAAAYWNzcEFQUEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPbW
AAEAAAAA0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE
ZGVzYwAAALQAAAB1Y3BydAAAASwAAAAnd3RwdAAAAVQAAAAUa1RSQwAAAWgAAAAOZGVzYwAAAAAAAAAb
Q2FsaWJyYXRlZCBHcmF5IENvbG9yc3BhY2UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5
cmlnaHQgQXBwbGUgQ29tcHV0ZXIsIEluYy4AAFhZWiAAAAAAAADzUQABAAAAARbMY3VydgAAAAAAAAAB
AjMAANIiIyQlWiRjbGFzc25hbWVYJGNsYXNzZXNfEBBOU0JpdG1hcEltYWdlUmVwoyQmJ1pOU0ltYWdl
UmVwWE5TT2JqZWN00iIjKSpXTlNBcnJheaIpJ9IiIywtXk5TTXV0YWJsZUFycmF5oywpJ9UvMDEyCTM0
NTY3V05TV2hpdGVcTlNDb21wb25lbnRzXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZUQw
IDAAQzAgMBADgAuAD9Q5OjsJPD0+P1ROU0lEVU5TSUNDV05TTW9kZWwQCYAMEACADtJBCUJDV05TLmRh
dGFPERFoAAARaGFwcGwCAAAAbW50ckdSQVlYWVogB9wACAAXAA8ALgAPYWNzcEFQUEwAAAAAbm9uZQAA
AAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAFZGVzYwAAAMAAAAB5ZHNjbQAAATwAAAfoY3BydAAACSQAAAAjd3RwdAAA
CUgAAAAUa1RSQwAACVwAAAgMZGVzYwAAAAAAAAAfR2VuZXJpYyBHcmF5IEdhbW1hIDIuMiBQcm9maWxl
YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T
S2V5ZWRBcmNoaXZlctEICVRyb290gAGvEBALDBccEyEmJy4xND5GR0tOVSRudWxs1Q0ODxAREhMUFRZW
JGNsYXNzXk5TUmVzaXppbmdNb2RlXE5TSW1hZ2VGbGFnc1ZOU1JlcHNXTlNDb2xvcoAPEAASAMAAAIAC
gArSGA0ZG1pOUy5vYmplY3RzoRqAA4AJ0hgNHSCiHh+ABIAFgAjTDSIjJCUTXxAUTlNUSUZGUmVwcmVz
ZW50YXRpb25fEBlOU0ludGVybmFsTGF5b3V0RGlyZWN0aW9ugAeABk8RAj5NTQAqAAAADOHh4eEADwEA
AAMAAAABAAIAAAEBAAMAAAABAAIAAAECAAMAAAABAAgAAAEDAAMAAAABAAEAAAEGAAMAAAABAAEAAAEK
AAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAEAAAEWAAMAAAABAAIAAAEX
AAQAAAABAAAABAEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFTAAMAAAABAAEAAIdzAAcAAAF4AAAAxgAA
AAAAAAF4YXBwbAIQAABtbnRyR1JBWVhZWiAH1QAHAAEAAAAAAABhY3NwQVBQTAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAARkZXNjAAAAtAAAAHVjcHJ0AAABLAAAACd3dHB0AAABVAAAABRrVFJDAAABaAAA
AA5kZXNjAAAAAAAAABtDYWxpYnJhdGVkIEdyYXkgQ29sb3JzcGFjZQAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1sdWMAAAAAAAAAHwAAAAxza1NLAAAALgAAAYRkYURLAAAAOAAA
AbJjYUVTAAAAOAAAAep2aVZOAAAAQAAAAiJwdEJSAAAASgAAAmJ1a1VBAAAALAAAAqxmckZVAAAAPgAA
AthodUhVAAAANAAAAxZ6aFRXAAAAHgAAA0puYk5PAAAAOgAAA2hjc0NaAAAAKAAAA6JoZUlMAAAAJAAA
A8ppdElUAAAATgAAA+5yb1JPAAAAKgAABDxkZURFAAAATgAABGZrb0tSAAAAIgAABLRzdlNFAAAAOAAA
AbJ6aENOAAAAHgAABNZqYUpQAAAAJgAABPRlbEdSAAAAKgAABRpwdFBPAAAAUgAABURubE5MAAAAQAAA
BZZlc0VTAAAATAAABdZ0aFRIAAAAMgAABiJ0clRSAAAAJAAABlRmaUZJAAAARgAABnhockhSAAAAPgAA
Br5wbFBMAAAASgAABvxydVJVAAAAOgAAB0ZlblVTAAAAPAAAB4BhckVHAAAALAAAB7wAVgFhAGUAbwBi
AGUAYwBuAOEAIABzAGkAdgDhACAAZwBhAG0AYQAgADIALAAyAEcAZQBuAGUAcgBpAHMAawAgAGcAcgDl
ACAAMgAsADIAIABnAGEAbQBtAGEAcAByAG8AZgBpAGwARwBhAG0AbQBhACAAZABlACAAZwByAGkAcwBv
AHMAIABnAGUAbgDoAHIAaQBjAGEAIAAyAC4AMgBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA4QBt
ACAAQwBoAHUAbgBnACAARwBhAG0AbQBhACAAMgAuADIAUABlAHIAZgBpAGwAIABHAGUAbgDpAHIAaQBj
AG8AIABkAGEAIABHAGEAbQBhACAAZABlACAAQwBpAG4AegBhAHMAIAAyACwAMgQXBDAEMwQwBDsETAQ9
BDAAIABHAHIAYQB5AC0EMwQwBDwEMAAgADIALgAyAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1
AGUAIABnAHIAaQBzACAAZwBhAG0AbQBhACAAMgAsADIAwQBsAHQAYQBsAOEAbgBvAHMAIABzAHoA/ABy
AGsAZQAgAGcAYQBtAG0AYQAgADIALgAykBp1KHBwlo5RSV6mACAAMgAuADIAIIJyX2ljz4/wAEcAZQBu
AGUAcgBpAHMAawAgAGcAcgDlACAAZwBhAG0AbQBhACAAMgAsADIALQBwAHIAbwBmAGkAbABPAGIAZQBj
AG4A4QAgAWEAZQBkAOEAIABnAGEAbQBhACAAMgAuADIF0gXQBd4F1AAgBdAF5AXVBegAIAXbBdwF3AXZ
ACAAMgAuADIAUAByAG8AZgBpAGwAbwAgAGcAcgBpAGcAaQBvACAAZwBlAG4AZQByAGkAYwBvACAAZABl
AGwAbABhACAAZwBhAG0AbQBhACAAMgAsADIARwBhAG0AYQAgAGcAcgBpACAAZwBlAG4AZQByAGkAYwED
ACAAMgAsADIAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQAdQBmAGUAbgAtAFAAcgBv
AGYAaQBsACAARwBhAG0AbQBhACAAMgAsADLHfLwYACDWjMDJACCsELnIACAAMgAuADIAINUEuFzTDMd8
Zm6QGnBwXqZ8+2VwACAAMgAuADIAIGPPj/Blh072TgCCLDCwMOwwpDCsMPMw3gAgADIALgAyACAw1zDt
MNUwoTCkMOsDkwO1A70DuQO6A8wAIAOTA7oDwQO5ACADkwOsA7wDvAOxACAAMgAuADIAUABlAHIAZgBp
AGwAIABnAGUAbgDpAHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwAgAGQAYQAgAEcAYQBt
AG0AYQAgADIALAAyAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwAgAGcAYQBtAG0AYQAgADIALAAy
AC0AcAByAG8AZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGcAYQBt
AG0AYQAgAGQAZQAgAGcAcgBpAHMAZQBzACAAMgAsADIOIw4xDgcOKg41DkEOAQ4hDiEOMg5ADgEOIw4i
DkwOFw4xDkgOJw5EDhsAIAAyAC4AMgBHAGUAbgBlAGwAIABHAHIAaQAgAEcAYQBtAGEAIAAyACwAMgBZ
AGwAZQBpAG4AZQBuACAAaABhAHIAbQBhAGEAbgAgAGcAYQBtAG0AYQAgADIALAAyACAALQBwAHIAbwBm
AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABw
AHIAbwBmAGkAbABVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAHMAegBhAHIAbwFb
AGMAaQAgAGcAYQBtAG0AYQAgADIALAAyBB4EMQRJBDAETwAgBEEENQRABDAETwAgBDMEMAQ8BDwEMAAg
ADIALAAyAC0EPwRABD4ERAQ4BDsETABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAEcAYQBtAG0AYQAg
ADIALgAyACAAUAByAG8AZgBpAGwAZQY6BicGRQYnACAAMgAuADIAIAZEBkgGRgAgBjEGRQYnBi8GSgAg
BjkGJwZFdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxMgAAWFlaIAAAAAAAAPNRAAEAAAAB
FsxjdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEH
AQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZ
AeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1
AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRj
BHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYn
BjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghG
CFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrF
CtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2p
DcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1
ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixSt
FM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjV
GPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1w
HZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7IiciVSKC
Iq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgN
KD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4W
Lkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSe
NNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6
Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9SsRLDEtT
S5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2
VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0n
XXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmbo
Zz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6
cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwh
fIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VHhauGDoZyhteHO4ef
iASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2
lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBp
oNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24
ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7Lrun
vCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJuco4
yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls
2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG
6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH
+lf65/t3/Af8mP0p/br+S/7c/23//4AN0iIjRUZdTlNNdXRhYmxlRGF0YaNFRydWTlNEYXRh0iIjSUpc
TlNDb2xvclNwYWNloksnXE5TQ29sb3JTcGFjZdIiI01OV05TQ29sb3KiTSfSIiNQUVdOU0ltYWdlolAn
XxAPTlNLZXllZEFyY2hpdmVy0VRVVHJvb3SAAQAIABEAGgAjAC0AMgA3AEsAUQBaAGEAbgB1AH0AfwCE
AIYAiACNAJgAmgCcAJ4AowCmAKgAqgCsAK4AswDKAMwAzgMQAxUDIAMpAzwDQANLA1QDWQNhA2QDaQN4
A3wDhwOPA5wDqQO+A8MDxwPJA8sDzQPWA9sD4QPpA+sD7QPvA/ED9gP+FWoVbBVxFX8VgxWKFY8VnBWf
FawVsRW5FbwVwRXJFcwV3hXhFeYAAAAAAAACAQAAAAAAAABWAAAAAAAAAAAAAAAAAAAV6A
AAB0ZXh0AAAAAENvcHlyaWdodCBBcHBsZSBDb21wdXRlciwgSW5jLgAAWFlaIAAAAAAAAPNRAAEAAAAB
FsxjdXJ2AAAAAAAAAAECMwAA0igpKitaJGNsYXNzbmFtZVgkY2xhc3Nlc18QEE5TQml0bWFwSW1hZ2VS
ZXCjKiwtWk5TSW1hZ2VSZXBYTlNPYmplY3TSKCkvMFdOU0FycmF5oi8t0igpMjNeTlNNdXRhYmxlQXJy
YXmjMi8t1TU2NzgNOTo7PD1XTlNXaGl0ZVxOU0NvbXBvbmVudHNcTlNDb2xvclNwYWNlXxASTlNDdXN0
b21Db2xvclNwYWNlRDAgMABDMCAwEAOAC4AO1D9AQQ1CQ0RFVE5TSURVTlNJQ0NXTlNNb2RlbBAJgAwQ
AIANTxERnAAAEZxhcHBsAgAAAG1udHJHUkFZWFlaIAfcAAgAFwAPAC4AD2Fjc3BBUFBMAAAAAG5vbmUA
AAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAABWRlc2MAAADAAAAAeWRzY20AAAE8AAAIGmNwcnQAAAlYAAAAI3d0cHQA
AAl8AAAAFGtUUkMAAAmQAAAIDGRlc2MAAAAAAAAAH0dlbmVyaWMgR3JheSBHYW1tYSAyLjIgUHJvZmls
ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAAC4AAAGEZGFESwAAADoA
AAGyY2FFUwAAADgAAAHsdmlWTgAAAEAAAAIkcHRCUgAAAEoAAAJkdWtVQQAAACwAAAKuZnJGVQAAAD4A
AALaaHVIVQAAADQAAAMYemhUVwAAABoAAANMa29LUgAAACIAAANmbmJOTwAAADoAAAOIY3NDWgAAACgA
AAPCaGVJTAAAACQAAAPqcm9STwAAACoAAAQOZGVERQAAAE4AAAQ4aXRJVAAAAE4AAASGc3ZTRQAAADgA
AATUemhDTgAAABoAAAUMamFKUAAAACYAAAUmZWxHUgAAACoAAAVMcHRQTwAAAFIAAAV2bmxOTAAAAEAA
AAXIZXNFUwAAAEwAAAYIdGhUSAAAADIAAAZUdHJUUgAAACQAAAaGZmlGSQAAAEYAAAaqaHJIUgAAAD4A
AAbwcGxQTAAAAEoAAAcuYXJFRwAAACwAAAd4cnVSVQAAADoAAAekZW5VUwAAADwAAAfeAFYBYQBlAG8A
YgBlAGMAbgDhACAAcwBpAHYA4QAgAGcAYQBtAGEAIAAyACwAMgBHAGUAbgBlAHIAaQBzAGsAIABnAHIA
5QAgADIALAAyACAAZwBhAG0AbQBhAC0AcAByAG8AZgBpAGwARwBhAG0AbQBhACAAZABlACAAZwByAGkA
cwBvAHMAIABnAGUAbgDoAHIAaQBjAGEAIAAyAC4AMgBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA
4QBtACAAQwBoAHUAbgBnACAARwBhAG0AbQBhACAAMgAuADIAUABlAHIAZgBpAGwAIABHAGUAbgDpAHIA
aQBjAG8AIABkAGEAIABHAGEAbQBhACAAZABlACAAQwBpAG4AegBhAHMAIAAyACwAMgQXBDAEMwQwBDsE
TAQ9BDAAIABHAHIAYQB5AC0EMwQwBDwEMAAgADIALgAyAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkA
cQB1AGUAIABnAHIAaQBzACAAZwBhAG0AbQBhACAAMgAsADIAwQBsAHQAYQBsAOEAbgBvAHMAIABzAHoA
/AByAGsAZQAgAGcAYQBtAG0AYQAgADIALgAykBp1KHBwlo5RSV6mADIALgAygnJfaWPPj/DHfLwYACDW
jMDJACCsELnIACAAMgAuADIAINUEuFzTDMd8AEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAZwBhAG0A
bQBhACAAMgAsADIALQBwAHIAbwBmAGkAbABPAGIAZQBjAG4A4QAgAWEAZQBkAOEAIABnAGEAbQBhACAA
MgAuADIF0gXQBd4F1AAgBdAF5AXVBegAIAXbBdwF3AXZACAAMgAuADIARwBhAG0AYQAgAGcAcgBpACAA
ZwBlAG4AZQByAGkAYwEDACAAMgAsADIAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQA
dQBmAGUAbgAtAFAAcgBvAGYAaQBsACAARwBhAG0AbQBhACAAMgAsADIAUAByAG8AZgBpAGwAbwAgAGcA
cgBpAGcAaQBvACAAZwBlAG4AZQByAGkAYwBvACAAZABlAGwAbABhACAAZwBhAG0AbQBhACAAMgAsADIA
RwBlAG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwAMgAgAGcAYQBtAG0AYQBwAHIAbwBmAGkAbGZukBpw
cF6mfPtlcAAyAC4AMmPPj/Blh072TgCCLDCwMOwwpDCsMPMw3gAgADIALgAyACAw1zDtMNUwoTCkMOsD
kwO1A70DuQO6A8wAIAOTA7oDwQO5ACADkwOsA7wDvAOxACAAMgAuADIAUABlAHIAZgBpAGwAIABnAGUA
bgDpAHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwAgAGQAYQAgAEcAYQBtAG0AYQAgADIA
LAAyAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8A
ZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGcAYQBtAG0AYQAgAGQA
ZQAgAGcAcgBpAHMAZQBzACAAMgAsADIOIw4xDgcOKg41DkEOAQ4hDiEOMg5ADgEOIw4iDkwOFw4xDkgO
Jw5EDhsAIAAyAC4AMgBHAGUAbgBlAGwAIABHAHIAaQAgAEcAYQBtAGEAIAAyACwAMgBZAGwAZQBpAG4A
ZQBuACAAaABhAHIAbQBhAGEAbgAgAGcAYQBtAG0AYQAgADIALAAyACAALQBwAHIAbwBmAGkAaQBsAGkA
RwBlAG4AZQByAGkBDQBrAGkAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABwAHIAbwBmAGkA
bABVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAHMAegBhAHIAbwFbAGMAaQAgAGcA
YQBtAG0AYQAgADIALAAyBjoGJwZFBicAIAAyAC4AMgAgBkQGSAZGACAGMQZFBicGLwZKACAGOQYnBkUE
HgQxBEkEMARPACAEQQQ1BEAEMARPACAEMwQwBDwEPAQwACAAMgAsADIALQQ/BEAEPgREBDgEOwRMAEcA
ZQBuAGUAcgBpAGMAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABQAHIAbwBmAGkAbABlAAB0
ZXh0AAAAAENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDEyAABYWVogAAAAAAAA81EAAQAAAAEWzGN1cnYA
AAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwA
gQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkB
HwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB
+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYD
IQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwE
mgSoBLYExATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkG
agZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIII
lgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsL
Igs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgO
Ew4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8R
bRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIV
NBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZ
axmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHewe
Fh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwoj
OCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo
1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu
7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01
hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8
pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANE
R0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpM
cky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtV
KFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpe
bF69Xw9fYV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+lo
P2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwckty
pnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9
oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6J
M4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSV
X5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobai
JqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxav
i7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9
j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bM
Ncy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvb
gNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXr
cOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8
B/yY/Sn9uv5L/tz/bf//0igpSElcTlNDb2xvclNwYWNlokotXE5TQ29sb3JTcGFjZdIoKUxNV05TQ29s
b3KiTC3SKClPUFdOU0ltYWdlok8tAAgAEQAaACQAKQAyADcASQBMAFEAUwBmAGwAdwB+AI0AmgChAKkA
qwCtALIAtAC2ALsAxgDIAMoAzADRANQA1gDYANoA4QD4ARQBFgEYA1oDXwNqA3MDhgOKA5UDngOjA6sD
rgOzA8IDxgPRA9kD5gPzBAgEDQQRBBMEFQQXBCAEJQQrBDMENQQ3BDkEOxXbFeAV7RXwFf0WAhYKFg0W
EhYaAAAAAAAAAgEAAAAAAAAAUQAAAAAAAAAAAAAAAAAAFh0
</mutableData>
</image>
<image name="linphone_user.png" width="41.599998474121094" height="42.400001525878906"/>
<image name="security_toogle_icon_green.png" width="33.599998474121094" height="38.400001525878906"/>
<systemColor name="secondarySystemBackgroundColor">
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -29,15 +29,15 @@
@property(nonatomic, strong) NSMutableArray *addresses;
@property(nonatomic, strong) NSMutableArray *phoneOrAddr;
@property(nonatomic, strong) NSMutableArray *addressesCached;
@property(readonly, nonatomic) NSMutableDictionary *ldapContactAddressBookMap;
@property(readonly, nonatomic) NSMutableDictionary *ldapAndProvisioningContactAddressBookMap;
@end
@implementation ChatConversationCreateTableView
- (void)viewWillAppear:(BOOL)animated {
if (!_ldapContactAddressBookMap) {
_ldapContactAddressBookMap = [NSMutableDictionary dictionary];
if (!_ldapAndProvisioningContactAddressBookMap) {
_ldapAndProvisioningContactAddressBookMap = [NSMutableDictionary dictionary];
}
[super viewWillAppear:animated];
@ -57,7 +57,7 @@
_addresses = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count];
_phoneOrAddr = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count];
_addressesCached = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count];
_addressesCached = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count];
[[NSNotificationCenter defaultCenter]
addObserver:self
@ -97,64 +97,76 @@
_loadingView.hidden = TRUE;
}
-(BOOL) isSecureChatable:(const LinphoneFriend*)friend {
if (!friend)
return false;
const LinphonePresenceModel *model = linphone_friend_get_presence_model(friend);
return model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh);
}
- (void) buildChatContactTable {
bctbx_list_t *results = [MagicSearchSingleton.instance getLastSearchResults];
while (results) {
LinphoneSearchResult *result = results->data;
bctbx_list_t *result_list = [MagicSearchSingleton.instance getLastSearchResults];
bctbx_list_t *it;
LinphoneAccount *account = linphone_core_get_default_account(LC);
for (it = result_list; it != NULL; it = it->next) {
LinphoneSearchResult *result = it->data;
const LinphoneAddress *addr = linphone_search_result_get_address(result);
const LinphoneFriend* friend = linphone_search_result_get_friend(result);
const char *phoneNumber = linphone_search_result_get_phone_number(result);
if ([LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"] && ![self isSecureChatable:friend]) {
continue;
}
const char *phoneNumber = NULL;
Contact *contact = nil;
char *uri = nil;
NSString *address = nil;
if (addr) {
uri = linphone_address_as_string_uri_only(addr);
address = [NSString stringWithUTF8String:uri];
contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:address]];
}
const LinphoneFriend* friend = linphone_search_result_get_friend(result);
if (!addr || (!contact && friend)) {
phoneNumber = linphone_search_result_get_phone_number(result);
contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:address use_prefix:[CallManager.instance applyInternationalPrefix]]];
if (!contact && friend) {
contact = [[Contact alloc] initWithFriend:friend];
[contact setCreatedFromLdapOrProvisioning:TRUE];
[_ldapAndProvisioningContactAddressBookMap setObject:contact forKey:address];
}
} else if (friend){
if (!phoneNumber) {
results = results->next;
continue;
}
LinphoneAccount *account = linphone_core_get_default_account(LC);
if (account) {
const char *normalizedPhoneNumber = linphone_account_normalize_phone_number(account, phoneNumber);
char *normalizedPhoneNumber = linphone_account_normalize_phone_number(account, phoneNumber);
if (!normalizedPhoneNumber) {
// get invalid phone number, continue
results = results->next;
continue;
}
addr = linphone_account_normalize_sip_uri(account, normalizedPhoneNumber);
bctbx_free(normalizedPhoneNumber);
uri = linphone_address_as_string_uri_only(addr);
address = [NSString stringWithUTF8String:uri];
contact = [[Contact alloc] initWithFriend:friend];
[contact setCreatedFromLdap:TRUE];
[_ldapContactAddressBookMap setObject:contact forKey:address];
[contact setCreatedFromLdapOrProvisioning:TRUE];
[_ldapAndProvisioningContactAddressBookMap setObject:contact forKey:address];
linphone_address_unref(addr);
}
}
if (uri) ms_free(uri);
if (!addr) {
results = results->next;
continue;
}
ms_free(uri);
[_addresses addObject:address];
[_phoneOrAddr addObject:phoneNumber ? [NSString stringWithUTF8String:phoneNumber] : address];
[_addressesCached addObject:[NSString stringWithFormat:@"%d",linphone_search_result_get_capabilities(result)]];
results = results->next;
}
bctbx_list_free(result_list);
[self.tableView reloadData];
_reloadMagicSearch = FALSE;
}
@ -168,7 +180,7 @@
[_addresses removeAllObjects];
[_phoneOrAddr removeAllObjects];
[_addressesCached removeAllObjects];
[_ldapContactAddressBookMap removeAllObjects];
[_ldapAndProvisioningContactAddressBookMap removeAllObjects];
[self.tableView reloadData];
_reloadMagicSearch = _reloadMagicSearch || [filter length]==0 || ![[MagicSearchSingleton.instance currentFilter] isEqualToString:filter];
@ -192,7 +204,7 @@
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
return 60.0;
return 60.0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
@ -203,12 +215,12 @@
NSString *key = [_addresses objectAtIndex:indexPath.row];
NSString *phoneOrAddr = [_phoneOrAddr objectAtIndex:indexPath.row];
Contact *contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:key]];
Contact *contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:key use_prefix:[CallManager.instance applyInternationalPrefix]]];
if (!contact) {
contact = [_ldapContactAddressBookMap objectForKey:key];
contact = [_ldapAndProvisioningContactAddressBookMap objectForKey:key];
}
const LinphonePresenceModel *model = contact.friend ? linphone_friend_get_presence_model(contact.friend) : NULL;
const LinphonePresenceModel *model = contact.friend ? linphone_friend_get_presence_model(contact.friend) : NULL;
Boolean linphoneContact = [FastAddressBook contactHasValidSipDomain:contact]
|| (model && linphone_presence_model_get_basic_status(model) == LinphonePresenceBasicStatusOpen);
LinphoneAddress *addr = [LinphoneUtils normalizeSipOrPhoneAddress:key];
@ -216,30 +228,32 @@
return cell;
cell.linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] || !linphoneContact;
cell.securityImage.hidden = !(model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh));
int capabilities = [[_addressesCached objectAtIndex:indexPath.row] intValue];
BOOL greyCellForEncryptedChat = _isEncrypted ? capabilities > 1 : TRUE;
BOOL greyCellForGroupChat = _isGroupChat ? capabilities > 0 : TRUE;
cell.userInteractionEnabled = cell.greyView.hidden = greyCellForEncryptedChat && greyCellForGroupChat;
cell.displayNameLabel.text = [contact createdFromLdap] ? [contact displayName] : [FastAddressBook displayNameForAddress:addr];
cell.securityImage.hidden = !(model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh));
int capabilities = [[_addressesCached objectAtIndex:indexPath.row] intValue];
BOOL greyCellForEncryptedChat = _isEncrypted ? capabilities > 1 : TRUE;
BOOL greyCellForGroupChat = _isGroupChat ? capabilities > 0 : TRUE;
cell.userInteractionEnabled = cell.greyView.hidden = greyCellForEncryptedChat && greyCellForGroupChat;
cell.displayNameLabel.text = [contact createdFromLdapOrProvisioning] ? [contact displayName] : [FastAddressBook displayNameForAddress:addr];
char *str = linphone_address_as_string(addr);
cell.addressLabel.text = linphoneContact ? [NSString stringWithUTF8String:str] : phoneOrAddr;
ms_free(str);
cell.selectedImage.hidden = ![_contactsGroup containsObject:cell.addressLabel.text];
[cell.avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
[cell.avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
cell.contentView.userInteractionEnabled = false;
cell.contentView.backgroundColor = UIColor.clearColor;
cell.backgroundColor = UIColor.clearColor;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIChatCreateCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!cell.userInteractionEnabled)
return;
if (!cell.userInteractionEnabled)
return;
LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
if (!(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount))) || !_isGroupChat) {
LinphoneAddress *addr = linphone_address_new(cell.addressLabel.text.UTF8String);
[PhoneMainView.instance getOrCreateOneToOneChatRoom:addr waitView:_waitView isEncrypted:_isEncrypted];
[PhoneMainView.instance getOrCreateOneToOneChatRoom:addr waitView:_waitView isEncrypted:_isEncrypted];
if (!addr) {
LOGE(@"Chat room could not be created on server, because null address.");
[ChatConversationInfoView displayCreationError];
@ -251,8 +265,6 @@
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSInteger index = 0;
_searchBar.text = @"";
[self searchBar:_searchBar textDidChange:@""];
if(cell.selectedImage.hidden) {
if(![_contactsGroup containsObject:cell.addressLabel.text]) {
[_contactsGroup addObject:cell.addressLabel.text];

View file

@ -55,6 +55,9 @@
- (IBAction)onNextClick:(id)sender;
- (IBAction)onChiffreClick:(id)sender;
-(void) unfragmentCompositeDescription;
-(void) fragmentCompositeDescription;
@end
#endif /* ChatConversationCreateView_h */

View file

@ -45,6 +45,21 @@ static UICompositeViewDescription *compositeDescription = nil;
return self.class.compositeViewDescription;
}
-(void) unfragmentCompositeDescription {
if (!IPAD)
return;
compositeDescription.isLeftFragment = true;
compositeDescription.otherFragment = nil;
}
-(void) fragmentCompositeDescription {
if (!IPAD)
return;
compositeDescription.otherFragment = IPAD ? NSStringFromClass(ChatsListView.class) : nil;
compositeDescription.isLeftFragment = false;
}
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
@ -54,7 +69,7 @@ static UICompositeViewDescription *compositeDescription = nil;
[self.view addGestureRecognizer:tap];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.itemSize = CGSizeMake(100.0 , 50.0);
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize;
_collectionController.collectionView = _collectionView;
_collectionController = (ChatConversationCreateCollectionViewController *)[[UICollectionViewController alloc] initWithCollectionViewLayout:layout];
_collectionView.dataSource = self;
@ -75,6 +90,10 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(viewUpdateEvent:)
name:kLinphoneChatCreateViewChange
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(displayModeChanged)
name:kDisplayModeChanged
object:nil];
LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
_chiffreOptionView.hidden = !(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount)));
if ([LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"]) {
@ -83,8 +102,17 @@ static UICompositeViewDescription *compositeDescription = nil;
CGRect frame = _allButton.frame;
frame.origin.x = _linphoneButton.frame.origin.x;
_allButton.frame = frame;
}
if ([LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"]) {
_chiffreOptionView.hidden = true;
_isEncrypted = true;
_tableController.isEncrypted = true;
_allButton.hidden = true;
_linphoneButton.hidden = true;
_selectedButtonImage.hidden = true;
}
if (_isForVoipConference) {
_switchView.hidden = true;
@ -95,13 +123,28 @@ static UICompositeViewDescription *compositeDescription = nil;
} else {
[_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal];
}
_topBar.backgroundColor = VoipTheme.toolbar_color;
} else {
_voipTitle.hidden = true;
[_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal];
_topBar.backgroundColor = UIColor.secondarySystemBackgroundColor;
}
[self displayModeChanged];
}
- (void)displayModeChanged{
[self.tableController.tableView reloadData];
if (_isForVoipConference) {
_topBar.backgroundColor = [VoipTheme.voipToolbarBackgroundColor get];
self.view.backgroundColor = [VoipTheme.voipBackgroundBWColor get];
_tableController.tableView.backgroundColor = [VoipTheme.voipBackgroundBWColor get];
_tableController.searchBar.backgroundColor = [VoipTheme.voipBackgroundBWColor get];
_tableController.collectionView.backgroundColor = [VoipTheme.voipBackgroundBWColor get];
} else {
_topBar.backgroundColor = UIColor.secondarySystemBackgroundColor;
self.view.backgroundColor = [VoipTheme.backgroundWhiteBlack get];
_tableController.tableView.backgroundColor = [VoipTheme.backgroundWhiteBlack get];
_tableController.searchBar.backgroundColor = [VoipTheme.backgroundWhiteBlack get];
_tableController.collectionView.backgroundColor = [VoipTheme.backgroundWhiteBlack get];
}
}
- (void)viewUpdateEvent:(NSNotification *)notif {
@ -116,17 +159,17 @@ static UICompositeViewDescription *compositeDescription = nil;
frame.origin.x = self.view.frame.size.width * 0.192;
}
_chiffreOptionView.frame = frame;
_isEncrypted = FALSE;
_isEncrypted = [LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"]; // false by default
CGRect buttonFrame = _chiffreButton.frame;
_tableController.isEncrypted = _isEncrypted;
// no encrypted by default
buttonFrame.origin.x = 2;
[_chiffreImage setImage:[UIImage imageNamed:@"security_toogle_background_grey.png"]];
_chiffreButton.frame = buttonFrame;
if (!_isEncrypted) {
buttonFrame.origin.x = 2;
[_chiffreImage setImage:[UIImage imageNamed:@"security_toogle_background_grey.png"]];
_chiffreButton.frame = buttonFrame;
}
_waitView.hidden = YES;
_backButton.hidden = IPAD;
_backButton.hidden = IPAD && !(_isForVoipConference||_isForOngoingVoipConference);
if(_tableController.contactsGroup.count == 0) {
if (!_isForEditing)
_nextButton.enabled = FALSE;
@ -149,9 +192,8 @@ static UICompositeViewDescription *compositeDescription = nil;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (IPAD)
[NSNotificationCenter.defaultCenter removeObserver:self];
[super viewWillDisappear:animated];
[NSNotificationCenter.defaultCenter removeObserver:self];
}
#pragma mark - Chat room functions
@ -168,7 +210,12 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onBackClick:(id)sender {
[_tableController.contactsGroup removeAllObjects];
if (_isForVoipConference) {
[PhoneMainView.instance popToView:ConferenceSchedulingView.compositeViewDescription];
if (_isForOngoingVoipConference) {
[PhoneMainView.instance popToView:VIEW(ConferenceCallView).compositeViewDescription];
[ControlsViewModelBridge showParticipants];
} else {
[PhoneMainView.instance popToView:ConferenceSchedulingView.compositeViewDescription];
}
} else {
if (_tableController.isForEditing)
[PhoneMainView.instance popToView:ChatConversationInfoView.compositeViewDescription];
@ -180,7 +227,7 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onNextClick:(id)sender {
if (_isForVoipConference) {
if (_isForOngoingVoipConference) {
[PhoneMainView.instance changeCurrentView:VIEW(ActiveCallOrConferenceView).compositeViewDescription];
[PhoneMainView.instance popToView:VIEW(ConferenceCallView).compositeViewDescription];
[ConferenceViewModelBridge updateParticipantsListWithAddresses:_tableController.contactsGroup];
} else {
[PhoneMainView.instance changeCurrentView:VIEW(ConferenceSchedulingSummaryView).compositeViewDescription];
@ -260,7 +307,7 @@ typedef enum { ContactsAll, ContactsLinphone, ContactsMAX } ContactsCategory;
return NO;
}
#pragma mark - UICollectionViewDataSource
#pragma mark - UICollectionViewDataSource & Delegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _tableController.contactsGroup.count;
}
@ -282,9 +329,10 @@ typedef enum { ContactsAll, ContactsLinphone, ContactsMAX } ContactsCategory;
ms_free(phone);
} else
addr = linphone_address_new(uri.UTF8String);
cell = [cell initWithName:[FastAddressBook displayNameForAddress:addr]];
[cell.nameLabel setText:[FastAddressBook displayNameForAddress:addr]];
linphone_address_unref(addr);
return cell;
}
@end

View file

@ -32,7 +32,7 @@
NSString *messageText;
}
@property(nonatomic) LinphoneChatMessage *msg;
@property(nonatomic) LinphoneEventLog *event;
@property(nonatomic) bctbx_list_t *displayedList;
@property(nonatomic) bctbx_list_t *receivedList;
@property(nonatomic) bctbx_list_t *notReceivedList;

View file

@ -48,17 +48,12 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)viewDidLoad {
[super viewDidLoad];
_msg = NULL;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
int index = [VIEW(ChatConversationView).tableController indexOfMesssage:_msg];
if (index < 0)
[PhoneMainView.instance popToView:ChatConversationView.compositeViewDescription];
_cell = (UIChatBubbleTextCell *)[VIEW(ChatConversationView).tableController tableView:VIEW(ChatConversationView).tableController.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
_cell = [VIEW(ChatConversationView).tableController buildMessageCell:_event];
_cell.frame = CGRectMake(-10,0,_msgView.frame.size.width,_msgView.frame.size.height);
_cell.isFirst = true;
_cell.isLast = true;
@ -90,18 +85,21 @@ static UICompositeViewDescription *compositeDescription = nil;
}
- (void)updateImdnList {
if (_msg && linphone_chat_message_get_chat_room(_msg)) {
_displayedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDisplayed);
_receivedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDeliveredToUser);
_notReceivedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDelivered);
_errorList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateNotDelivered);
[_tableView reloadData];
if (_event) {
LinphoneChatMessage *_msg = linphone_event_log_get_chat_message(_event);
if (_msg) {
_displayedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDisplayed);
_receivedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDeliveredToUser);
_notReceivedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDelivered);
_errorList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateNotDelivered);
[_tableView reloadData];
}
}
}
- (void)fitContent {
LinphoneChatMessage *_msg = linphone_event_log_get_chat_message(_event);
CGSize messageSize = [UIChatBubbleTextCell ViewHeightForMessage:_msg withWidth:self.view.frame.size.width];
[_msgView setFrame:CGRectMake(_msgView.frame.origin.x,
_msgView.frame.origin.y,
@ -299,7 +297,8 @@ static UICompositeViewDescription *compositeDescription = nil;
f.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
f.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
if (linphone_chat_message_is_ephemeral(_msg)) {
LinphoneChatMessage *_msg = _event ? linphone_event_log_get_chat_message(_event) : nil;
if (_msg && linphone_chat_message_is_ephemeral(_msg)) {
long duration = linphone_chat_message_get_ephemeral_expire_time(_msg) == 0 ?
linphone_chat_room_get_ephemeral_lifetime(linphone_chat_message_get_chat_room(_msg)) :
linphone_chat_message_get_ephemeral_expire_time(_msg)-[NSDate date].timeIntervalSince1970;

View file

@ -35,6 +35,7 @@
@property(nonatomic) LinphoneChatRoom *room;
@property(nonatomic) LinphoneChatRoomCbs *chatRoomCbs;
@property(nonatomic) const char *peerAddress;
@property(nonatomic) const char *localAddress;
@property (weak, nonatomic) IBOutlet UIIconButton *nextButton;
@property (weak, nonatomic) IBOutlet UIRoundBorderedButton *quitButton;

View file

@ -84,6 +84,7 @@ static UICompositeViewDescription *compositeDescription = nil;
_room = NULL;
_chatRoomCbs = NULL;
_peerAddress = NULL;
_localAddress = NULL;
}
- (void)viewWillAppear:(BOOL)animated {
@ -153,10 +154,11 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)onLinphoneCoreReady:(NSNotification *)notif {
if ((LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneGlobalOn) {
if (!_create && _peerAddress) {
if (!_create && _peerAddress && _localAddress) {
LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress);
if (peerAddr) {
_room = linphone_core_get_chat_room([LinphoneManager getLc], peerAddr);
LinphoneAddress *localAddr = linphone_core_create_address([LinphoneManager getLc], _localAddress);
if (peerAddr && localAddr) {
_room = linphone_core_search_chat_room([LinphoneManager getLc], NULL, localAddr, peerAddr, NULL);
}
[self configure];
}
@ -270,6 +272,7 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onBackClick:(id)sender {
if(_create) {
ChatConversationCreateView *view = VIEW(ChatConversationCreateView);
[view fragmentCompositeDescription];
view.tableController.contactsGroup = [_contacts mutableCopy];
view.tableController.notFirstTime = TRUE;
view.isForEditing = FALSE;
@ -296,6 +299,7 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onAddClick:(id)sender {
if (_create || _imAdmin) {
ChatConversationCreateView *view = VIEW(ChatConversationCreateView);
[view fragmentCompositeDescription];
view.tableController.notFirstTime = TRUE;
view.isForEditing = !_create;
view.isGroupChat = TRUE;

View file

@ -25,6 +25,7 @@
#import "UICheckBoxTableView.h"
@interface FileContext : NSObject
@property NSMutableArray <NSString *> *typesArray;
@property NSMutableArray <NSData *> *datasArray;
@ -70,5 +71,6 @@
- (void) dismissMessagesPopups;
- (void) scrollToMessage:(LinphoneChatMessage *)message;
- (int) indexOfMesssage:(LinphoneChatMessage *)message;
- (void *)buildMessageCell:(LinphoneEventLog *) event;
@end

View file

@ -20,7 +20,6 @@
#import "LinphoneManager.h"
#import "ChatConversationTableView.h"
#import "ChatConversationImdnView.h"
#import "UIChatBubbleTextCell.h"
#import "UIChatBubblePhotoCell.h"
#import "UIChatNotifiedEventCell.h"
#import "PhoneMainView.h"
@ -84,7 +83,6 @@
LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(_chatRoom);
bool oneToOne = capabilities & LinphoneChatRoomCapabilitiesOneToOne;
bctbx_list_t *chatRoomEvents = linphone_chat_room_get_history_events(_chatRoom, 0);
int unread_count = 0;
bctbx_list_t *head = chatRoomEvents;
@ -142,12 +140,11 @@
- (void)addEventEntry:(LinphoneEventLog *)event {
[eventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]];
[totalEventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]];
[totalEventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]];
int pos = (int)eventList.count - 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:pos inSection:0];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
[self.tableView endUpdates];
}
@ -331,28 +328,32 @@ static const int BASIC_EVENT_LIST=15;
}
}
-(UIChatBubbleTextCell *)buildMessageCell:(LinphoneEventLog *) event {
NSString *kCellId = nil;
LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event);
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);
// To use less memory and to avoid overlapping. To be improved.
UIChatBubbleTextCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCellId];
cell = [[NSClassFromString(kCellId) alloc] initWithIdentifier:kCellId];
[cell setEvent:event];
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *kCellId = nil;
LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue];
if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) {
UIChatBubbleTextCell *cell = [self buildMessageCell:event];
LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event);
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);
// To use less memory and to avoid overlapping. To be improved.
UIChatBubbleTextCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
cell = [[NSClassFromString(kCellId) alloc] initWithIdentifier:kCellId];
[cell setEvent:event];
if (chat) {
cell.isFirst = [self isFirstIndexInTableView:indexPath chat:chat];
cell.isLast = [self isLastIndexInTableView:indexPath chat:chat];
[cell update];
}
if (chat) {
cell.isFirst = [self isFirstIndexInTableView:indexPath chat:chat];
cell.isLast = [self isLastIndexInTableView:indexPath chat:chat];
[cell update];
}
[cell setChatRoomDelegate:_chatRoomDelegate];
[super accessoryForCell:cell atPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
@ -378,6 +379,13 @@ static const int BASIC_EVENT_LIST=15;
[_chatRoomDelegate tableViewIsScrolling];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
[ChatConversationView markAsRead:_chatRoom];
}
}
static const CGFloat MESSAGE_SPACING_PERCENTAGE = 1.f;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
@ -443,12 +451,11 @@ static const CGFloat MESSAGE_SPACING_PERCENTAGE = 1.f;
LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue];
UIContextualAction *imdnAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal
title:NSLocalizedString(@"Info", nil)
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
LinphoneChatMessage *msg = linphone_event_log_get_chat_message(event);
ChatConversationImdnView *view = VIEW(ChatConversationImdnView);
view.msg = msg;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
title:NSLocalizedString(@"Info", nil)
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
ChatConversationImdnView *view = VIEW(ChatConversationImdnView);
view.event = event;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}];
UISwipeActionsConfiguration *swipeActionConfig;

View file

@ -63,6 +63,7 @@
@property(nonatomic) LinphoneChatRoomCbs *chatRoomCbs;
@property(nonatomic) Boolean markAsRead;
@property(nonatomic) const char *peerAddress;
@property(nonatomic) const char *localAddress;
@property (strong, nonatomic) FileDataSource *FileDataSource;
@ -112,6 +113,7 @@
@property LinphonePlayer *sharedVoicePlayer;
@property BOOL showVoiceRecorderView;
@property BOOL preservePendingActions;
@property BOOL *sharingMedia;
// Reply
@property (weak, nonatomic) IBOutlet UIView *replyView;

View file

@ -139,7 +139,7 @@ static UICompositeViewDescription *compositeDescription = nil;
if (compositeDescription == nil) {
compositeDescription = [[UICompositeViewDescription alloc] init:self.class
statusBar:StatusBarView.class
tabBar:nil
tabBar:IPAD ? TabBarView.class :nil
sideMenu:SideMenuView.class
fullscreen:false
isLeftFragment:NO
@ -196,7 +196,8 @@ static UICompositeViewDescription *compositeDescription = nil;
_vrInnerView.layer.masksToBounds = YES;
_vrWaveMaskPlayer.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"color_L"]]; // rgba(1,88,7,0.2);
_showVoiceRecorderView = false;
_toggleMenuButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_toggleRecord.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
- (void)refreshData {
@ -212,10 +213,17 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!_chatRoom)
[self onLinphoneCoreReady:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(applicationWillEnterBackground)
selector:@selector(applicationDidEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(applicationWillEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
@ -232,10 +240,10 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(callUpdateEvent:)
name:kLinphoneCallUpdate
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(onLinphoneCoreReady:)
name:kLinphoneGlobalStateUpdate
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(onLinphoneCoreReady:)
name:kLinphoneGlobalStateUpdate
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(endVoicePlayingIfDoingSO:)
@ -246,24 +254,23 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(endVoicePlayingIfDoingSO:)
name:kLinphoneVoiceMessagePlayerEOF
object:nil];
if ([_fileContext count] > 0) {
[UIView animateWithDuration:0
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
// resizing imagesView
CGRect imagesFrame = [_imagesView frame];
imagesFrame.origin.y = [_messageView frame].origin.y - 120;
imagesFrame.size.height = 120;
[_imagesView setFrame:imagesFrame];
// resizing chatTable
CGRect tableViewFrame = [_tableController.tableView frame];
tableViewFrame.size.height -= 120;
[_tableController.tableView setFrame:tableViewFrame];
[self updateFramesInclRecordingAndReplyView];
}
completion:nil];
}
if ([_fileContext count] > 0) {
[UIView animateWithDuration:0
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
// resizing imagesView
CGRect imagesFrame = [_imagesView frame];
imagesFrame.origin.y = [_messageView frame].origin.y - 120;
imagesFrame.size.height = 120;
[_imagesView setFrame:imagesFrame];
// resizing chatTable
CGRect tableViewFrame = [_tableController.tableView frame];
tableViewFrame.size.height -= 120;
[_tableController.tableView setFrame:tableViewFrame];
[self updateFramesInclRecordingAndReplyView];
} completion:nil];
}
[self configureForRoom:self.editing];
// Resize the popup table depending on wether ephemeral messages are enabled or not.
@ -273,12 +280,13 @@ static UICompositeViewDescription *compositeDescription = nil;
// Voice recording & Replies
_vrView.hidden = true;
_toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0;
_toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0;
_replyView.hidden = true;
_preservePendingActions = false;
_toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0;
[PhoneMainView.instance hideTabBar:!IPAD];
}
- (void)viewWillDisappear:(BOOL)animated {
@ -307,6 +315,8 @@ static UICompositeViewDescription *compositeDescription = nil;
[NSNotificationCenter.defaultCenter removeObserver:self];
PhoneMainView.instance.currentRoom = NULL;
[[UIApplication sharedApplication] setIdleTimerDisabled:false];
_chatRoom = NULL;
_tableController.chatRoom = nil;
}
- (void)removeCallBacks {
@ -323,27 +333,33 @@ static UICompositeViewDescription *compositeDescription = nil;
return;
}
composingVisible = !composingVisible;
[self setComposingVisible:!composingVisible withDelay:0];
// force offset recomputing
[_messageField refreshHeight];
LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress);
if (peerAddr) {
_chatRoom = linphone_core_get_chat_room([LinphoneManager getLc], peerAddr);
isOneToOne = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesOneToOne;
isEncrypted = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesEncrypted;
LinphoneAddress *localAddr = linphone_core_create_address([LinphoneManager getLc], _localAddress);
if (peerAddr && localAddr) {
_chatRoom = linphone_core_search_chat_room([LinphoneManager getLc], NULL, localAddr, peerAddr, NULL);
if (_chatRoom) {
isOneToOne = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesOneToOne;
isEncrypted = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesEncrypted;
[self setComposingVisible:!composingVisible withDelay:0];
[self configureForRoom:true];
[_tableController scrollToBottom:true];
}
}
[self configureForRoom:true];
_backButton.hidden = _tableController.isEditing;
[_tableController scrollToBottom:true];
[self refreshImageDrawer];
[self stopAllPlays];
if (peerAddr) linphone_address_unref(peerAddr);
if (localAddr) linphone_address_unref(localAddr);
_backButton.hidden = _tableController.isEditing;
[self refreshImageDrawer];
[self stopAllPlays];
[self keyboardWillHide:nil];
}
#pragma mark -
- (void)applicationWillEnterBackground{
- (void)applicationDidEnterBackground{
if (!_preservePendingActions)
[self cancelVoiceRecording];
else if (_isVoiceRecording)
@ -351,9 +367,18 @@ static UICompositeViewDescription *compositeDescription = nil;
if (!_preservePendingActions)
[self closePendingReply];
[self stopAllPlays];
_chatRoom = nil;
[_messageField resignFirstResponder];
}
- (void)applicationWillEnterForeground{
if (_chatRoom == nil) {
if (linphone_core_get_calls_nb(LC) == 0)
[SVProgressHUD show];
else
[self onLinphoneCoreReady:nil];
}
}
- (void)configureForRoom:(BOOL)editing {
if (!_chatRoom) {
@ -395,6 +420,7 @@ static UICompositeViewDescription *compositeDescription = nil;
LinphoneParticipant *firstParticipant = participants ? (LinphoneParticipant *)participants->data : NULL;
const LinphoneAddress *addr = firstParticipant ? linphone_participant_get_address(firstParticipant) : linphone_chat_room_get_peer_address(_chatRoom);
[ContactDisplay setDisplayNameLabel:_addressLabel forAddress:addr];
bctbx_list_free(participants);
} else
_addressLabel.text = [NSString stringWithUTF8String:linphone_chat_room_get_subject(_chatRoom) ?: LINPHONE_DUMMY_SUBJECT];
@ -426,7 +452,7 @@ static UICompositeViewDescription *compositeDescription = nil;
if (!room)
return true;
LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(room);
return capabilities & LinphoneChatRoomCapabilitiesBasic;
return capabilities & LinphoneChatRoomCapabilitiesEncrypted;
}
@ -466,7 +492,7 @@ static UICompositeViewDescription *compositeDescription = nil;
//file shared from photo lib
NSString *fileName = dict[@"url"];
[_messageField setText:dict[@"message"]];
[self confirmShare:[self nsDataRead] url:nil fileName:fileName];
[self confirmShare:[self nsDataRead] url:nil fileName:fileName];
[defaults removeObjectForKey:@"photoData"];
} else if (dictFile) {
NSString *fileName = dictFile[@"url"];
@ -483,23 +509,26 @@ static UICompositeViewDescription *compositeDescription = nil;
// reload the chatroom after the core starts
- (void)onLinphoneCoreReady:(NSNotification *)notif {
if ((LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneGlobalOn) {
LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress);
if (peerAddr) {
_chatRoom = linphone_core_get_chat_room([LinphoneManager getLc], peerAddr);
isOneToOne = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesOneToOne;
isEncrypted = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesEncrypted;
}
[self configureForRoom:self.editing];
if (_chatRoom && _markAsRead) {
if (IPAD) {
[VIEW(ChatsListView).tableController loadData];
if (linphone_core_get_global_state(LC) == LinphoneGlobalOn) {
LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress);
LinphoneAddress *localAddr = linphone_core_create_address([LinphoneManager getLc], _localAddress);
if (peerAddr && localAddr) {
_chatRoom = linphone_core_search_chat_room([LinphoneManager getLc], NULL, localAddr, peerAddr, NULL);
if (_chatRoom) {
isOneToOne = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesOneToOne;
isEncrypted = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesEncrypted;
[self configureForRoom:self.editing];
if (_chatRoom && _markAsRead) {
if (IPAD) {
[VIEW(ChatsListView).tableController loadData];
}
[ChatConversationView markAsRead:_chatRoom];
}
_markAsRead = TRUE;
}
[ChatConversationView markAsRead:_chatRoom];
}
_markAsRead = TRUE;
}
}
[SVProgressHUD dismiss];
}
}
- (void)callUpdateEvent:(NSNotification *)notif {
@ -598,9 +627,9 @@ static UICompositeViewDescription *compositeDescription = nil;
}
- (void)confirmShare:(NSData *)data url:(NSString *)url fileName:(NSString *)fileName {
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:@""];
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select or create a conversation to share the file(s)", nil)];
dispatch_async(dispatch_get_main_queue(), ^{
[sheet addButtonWithTitle:NSLocalizedString(@"send to this conversation", nil)
[sheet addButtonWithTitle:NSLocalizedString(@"Send to this conversation", nil)
block:^() {
if (![[self.messageField text] isEqualToString:@""]) {
[self sendMessageInMessageField:linphone_chat_room_create_empty_message(_chatRoom)];
@ -657,6 +686,11 @@ static UICompositeViewDescription *compositeDescription = nil;
completion:^(BOOL finished) {
_composeIndicatorView.hidden = !visible;
}];
if (visible) {
if (_tableController.tableView.contentOffset.y + newComposingFrame.size.height >= (_tableController.tableView.contentSize.height - _tableController.tableView.frame.size.height - 1)) {
[_tableController scrollToBottom:TRUE];
}
}
}
- (BOOL) groupCallAvailable {
@ -771,9 +805,9 @@ static UICompositeViewDescription *compositeDescription = nil;
// if we're showing the compose message, update it position
if (![_composeLabel isHidden]) {
CGRect frame = [_composeLabel frame];
CGRect frame = [_composeIndicatorView frame];
frame.origin.y -= diff;
[_composeLabel setFrame:frame];
[_composeIndicatorView setFrame:frame];
}
}
}
@ -781,7 +815,15 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - Action Functions
- (IBAction)onBackClick:(id)event {
[PhoneMainView.instance popCurrentView];
NSString *previousViewName = [PhoneMainView.instance getPreviousViewName];
_sharingMedia = nil;
if ([previousViewName isEqualToString:@"ContactDetailsView"]) {
ContactDetailsView *view = VIEW(ContactDetailsView);
[PhoneMainView.instance popToView:view.compositeViewDescription];
} else {
ChatsListView *view = VIEW(ChatsListView);
[PhoneMainView.instance popToView:view.compositeViewDescription];
}
}
- (IBAction)onEditClick:(id)event {
@ -937,6 +979,7 @@ static UICompositeViewDescription *compositeDescription = nil;
view.oldSubject = [NSString stringWithUTF8String:linphone_chat_room_get_subject(_chatRoom) ?: LINPHONE_DUMMY_SUBJECT];
view.room = _chatRoom;
view.peerAddress = _peerAddress;
view.localAddress = _localAddress;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
@ -1049,84 +1092,86 @@ static UICompositeViewDescription *compositeDescription = nil;
}
+ (void)writeMediaToGallery:(NSString *)name fileType:(NSString *)fileType {
NSString *filePath = [LinphoneManager validFilePath:name];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
NSData* data = [NSData dataWithContentsOfFile:filePath];
if (![LinphoneManager.instance lpConfigBoolForKey:@"vfs_enabled_mode"]) {
NSString *filePath = [LinphoneManager validFilePath:name];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
NSData* data = [NSData dataWithContentsOfFile:filePath];
// define a block , not called immediately. To avoid crash when saving photo before PHAuthorizationStatusNotDetermined.
void (^block)(void)= ^ {
if ([fileType isEqualToString:@"image"] ) {
// we're finished, save the image and update the message
UIImage *image = [UIImage imageWithData:data];
if (!image) {
ChatConversationView *view = VIEW(ChatConversationView);
[view showFileDownloadError];
return;
// define a block , not called immediately. To avoid crash when saving photo before PHAuthorizationStatusNotDetermined.
void (^block)(void)= ^ {
if ([fileType isEqualToString:@"image"] ) {
// we're finished, save the image and update the message
UIImage *image = [UIImage imageWithData:data];
if (!image) {
ChatConversationView *view = VIEW(ChatConversationView);
[view showFileDownloadError];
return;
}
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library",nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"Image saved to [%@]", [placeHolder localIdentifier]);
}
});
}];
} else if([fileType isEqualToString:@"video"]) {
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:filePath]];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
LOGE(@"Cannot save video data downloaded [%@]", [error localizedDescription]);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write video to photo library", nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"video saved to [%@]", [placeHolder localIdentifier]);
}
});
}];
}
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError *error) {
};
// When you save an image or video to a photo library, make sure that it is allowed. Otherwise, there will be a backup error.
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
block();
} else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library",nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
block();
} else {
LOGI(@"Image saved to [%@]", [placeHolder localIdentifier]);
}
});
}];
} else if([fileType isEqualToString:@"video"]) {
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:filePath]];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
LOGE(@"Cannot save video data downloaded [%@]", [error localizedDescription]);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write video to photo library", nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"video saved to [%@]", [placeHolder localIdentifier]);
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
});
}];
}
};
// When you save an image or video to a photo library, make sure that it is allowed. Otherwise, there will be a backup error.
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
block();
} else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
block();
} else {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
});
}];
}
}
}
@ -1415,12 +1460,11 @@ void on_chat_room_chat_message_received(LinphoneChatRoom *cr, const LinphoneEven
bool isDisplayingBottomOfTable = [view.tableController.tableView indexPathsForVisibleRows].lastObject.row == [view.tableController totalNumberOfItems] - 1;
[view.tableController addEventEntry:(LinphoneEventLog *)event_log];
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:view];
if (isDisplayingBottomOfTable) {
[view.tableController scrollToBottom:TRUE];
} else {
[[view.tableController scrollBadge] setHidden:FALSE];
int unread_msg = linphone_chat_room_get_unread_messages_count(cr);
[[view.tableController scrollBadge] setText:[NSString stringWithFormat:@"%d", unread_msg]];
}
@ -1721,13 +1765,6 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
}
}
-(BOOL) isConversationMuted {
return FALSE; // TODO
}
-(void) toggleMuteConversation {
// TODO
}
-(void) showAddressAndIdentityPopup {
char *localAddress = linphone_address_as_string(linphone_chat_room_get_local_address(_chatRoom));
@ -1754,8 +1791,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
}
-(BOOL) canAdminEphemeral:(const LinphoneChatRoom *)cr {
if (!cr) return FALSE;
if ([ChatConversationView isBasicChatRoom:_chatRoom]) return FALSE;
if (!cr || !isEncrypted) return FALSE;
// If ephemeral mode is DeviceManaged, then we don't need to check anything else
return (linphone_chat_room_params_get_ephemeral_mode(linphone_chat_room_get_current_params(cr)) == LinphoneChatRoomEphemeralModeDeviceManaged)
@ -1773,42 +1809,57 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
_popupMenu.layer.masksToBounds = false;
_toggleMenuButton.hidden = false;
_popupMenu.tableFooterView = [UIView new];
_popupMenu.separatorStyle = UITableViewCellSeparatorStyleNone;
[_popupMenu reloadData];
}
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self onToggleMenu:nil];
BOOL isEncrypted = ![ChatConversationView isBasicChatRoom:_chatRoom];
if (indexPath.row == 0) {
int firstIndex = isOneToOne ? 0 : 1;
if (!isOneToOne && indexPath.row == 0) { //Schedule meeting
[ConferenceViewModelBridge scheduleFromGroupChatWithCChatRoom:_chatRoom];
[PhoneMainView.instance popToView:ConferenceSchedulingView.compositeViewDescription];
}
if (indexPath.row == firstIndex) {
if (isOneToOne) {
[self addOrGoToContact:linphone_chat_room_get_peer_address(_chatRoom)];
if (isEncrypted) {
LinphoneAddress* contactAddress = linphone_address_clone(linphone_participant_get_address(bctbx_list_nth_data(linphone_chat_room_get_participants(_chatRoom), 0)));
linphone_address_clean(contactAddress);
[self addOrGoToContact:contactAddress];
linphone_address_unref(contactAddress);
} else {
[self addOrGoToContact:linphone_chat_room_get_peer_address(_chatRoom)];
}
} else {
[self displayGroupInfo];
}
}
if (isEncrypted && indexPath.row == 1) {
if (isEncrypted && indexPath.row == 1+firstIndex) {
[self goToDeviceListView];
}
BOOL canEphemeral = [self canAdminEphemeral:_chatRoom];
if (canEphemeral && indexPath.row == 2) {
if (canEphemeral && indexPath.row == 2+firstIndex) {
EphemeralSettingsView *view = VIEW(EphemeralSettingsView);
view.room = _chatRoom;
[PhoneMainView.instance popToView:view.compositeViewDescription];
}
if ((!isEncrypted && indexPath.row == 1) || (isEncrypted && indexPath.row == 3)) {
[self toggleMuteConversation];
if ((!isEncrypted && indexPath.row == 1+firstIndex) || (isEncrypted && indexPath.row == 3+firstIndex)) {
[LinphoneManager setChatroomPushEnabled:_chatRoom withPushEnabled:![LinphoneManager getChatroomPushEnabled:_chatRoom]];
[_popupMenu reloadData];
}
if ((!isEncrypted && indexPath.row == 2) || (isEncrypted && indexPath.row == 4)) {
if ((!isEncrypted && indexPath.row == 2+firstIndex) || (isEncrypted && indexPath.row == 4+firstIndex)) {
[_tableController onEditClick:nil];
[self onEditionChangeClick:nil];
}
if ((isEncrypted && ((!canEphemeral && indexPath.row == 4)||(canEphemeral && indexPath.row == 5)))
|| (!isEncrypted && indexPath.row == 3)) {
if ((isEncrypted && ((!canEphemeral && indexPath.row == 4+firstIndex)||(canEphemeral && indexPath.row == 5+firstIndex)))
|| (!isEncrypted && indexPath.row == 3+firstIndex)) {
[self showAddressAndIdentityPopup];
}
}
@ -1831,61 +1882,89 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
if ([self canAdminEphemeral:_chatRoom])
++nbRows;
if (!isOneToOne) // schedule meeting
++nbRows;
return nbRows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[UITableViewCell alloc] init];
if (indexPath.row == 0) {
if (!_chatRoom) {
// Workaround to avoid crash in background for release 4.7. This shouldn't happen though, so there may be a deeper issue not found yet
return cell;
}
int firstIndex = isOneToOne ? 0 : 1;
if (!isOneToOne && indexPath.row == 0) {
cell.imageView.image = [UIImage imageNamed:@"menu_voip_meeting_schedule"];
cell.textLabel.text = NSLocalizedString(@"Schedule a meeting",nil);
}
if (indexPath.row == firstIndex) {
if (isOneToOne) {
Contact *contact = [FastAddressBook getContactWithAddress:linphone_chat_room_get_peer_address(_chatRoom)];
Contact *contact;
if (isEncrypted) {
LinphoneAddress * contactAddress = linphone_address_clone(linphone_participant_get_address(bctbx_list_nth_data(linphone_chat_room_get_participants(_chatRoom), 0)));
linphone_address_clean(contactAddress);
contact = [FastAddressBook getContactWithAddress:contactAddress];
linphone_address_unref(contactAddress);
} else {
contact = [FastAddressBook getContactWithAddress:linphone_chat_room_get_peer_address(_chatRoom)];
}
if (contact == nil) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"contact_add_default.png"] newSize:CGSizeMake(20, 25)];
cell.imageView.image = [UIImage imageNamed:@"contact_add_default.png"];
cell.textLabel.text = NSLocalizedString(@"Add to contacts",nil);
} else {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"contacts_all_default.png"] newSize:CGSizeMake(20, 25)];
cell.imageView.image = [UIImage imageNamed:@"contacts_all_default.png"];
cell.textLabel.text = NSLocalizedString(@"Go to contact",nil);
}
} else {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"chat_group_informations.png"] newSize:CGSizeMake(25, 25)];
cell.imageView.image = [UIImage imageNamed:@"chat_group_informations.png"];
cell.textLabel.text = NSLocalizedString(@"Group infos",nil);
}
}
if (isEncrypted && indexPath.row == 1) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"menu_security_default.png"] newSize:CGSizeMake(20, 25)];
if (isEncrypted && indexPath.row == 1+firstIndex) {
cell.imageView.image = [UIImage imageNamed:@"menu_security_default.png"];
cell.textLabel.text = NSLocalizedString(@"Conversation's devices",nil);
}
bool canEphemeral = [self canAdminEphemeral:_chatRoom];
if (canEphemeral && indexPath.row == 2) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"ephemeral_messages_default.png"] newSize:CGSizeMake(20, 25)];
if (canEphemeral && indexPath.row == 2+firstIndex) {
cell.imageView.image = [UIImage imageNamed:@"ephemeral_messages_default.png"];
cell.textLabel.text = NSLocalizedString(@"Ephemeral messages",nil);
}
if ((isEncrypted && indexPath.row == 3) || (!isEncrypted && indexPath.row == 1)) {
if ([self isConversationMuted]) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"menu_notifications_off.png"] newSize:CGSizeMake(20, 25)];
cell.textLabel.text = NSLocalizedString(@"NOT IMPLEMENTED",nil);
if ((isEncrypted && indexPath.row == 3+firstIndex) || (!isEncrypted && indexPath.row == 1+firstIndex)) {
if ([LinphoneManager getChatroomPushEnabled:_chatRoom]) {
cell.imageView.image = [UIImage imageNamed:@"menu_notifications_off.png"];
cell.textLabel.text = NSLocalizedString(@"Mute notifications",nil);
} else {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"menu_notifications_on.png"] newSize:CGSizeMake(20, 25)];
cell.textLabel.text = NSLocalizedString(@"NOT IMPLEMENTED",nil);
cell.imageView.image = [UIImage imageNamed:@"menu_notifications_on.png"];
cell.textLabel.text = NSLocalizedString(@"Un-mute notifications",nil);
}
}
if ((isEncrypted && indexPath.row == 4) || (!isEncrypted && indexPath.row == 2)) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"delete_default.png"] newSize:CGSizeMake(20, 25)];
if ((isEncrypted && indexPath.row == 4+firstIndex) || (!isEncrypted && indexPath.row == 2+firstIndex)) {
cell.imageView.image = [UIImage imageNamed:@"delete_default.png"];
cell.textLabel.text = NSLocalizedString(@"Delete messages",nil);
}
if ((isEncrypted && ((!canEphemeral && indexPath.row == 4)||(canEphemeral && indexPath.row == 5)))
|| (!isEncrypted && indexPath.row == 3)) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"chat_group_informations.png"] newSize:CGSizeMake(25, 25)];
cell.textLabel.text = NSLocalizedString(@"Show address and identity",nil);
if ((isEncrypted && ((!canEphemeral && indexPath.row == 4+firstIndex)||(canEphemeral && indexPath.row == 5+firstIndex)))
|| (!isEncrypted && indexPath.row == 3+firstIndex)) {
cell.imageView.image = [UIImage imageNamed:@"chat_group_informations.png"];
cell.textLabel.text = NSLocalizedString(@"Debug infos",nil);
}
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
UIImageView * icon = [[UIImageView alloc] initWithFrame:CGRectMake(tableView.frame.size.width-37, 7, 30, 30)];
icon.contentMode = UIViewContentModeScaleAspectFit;
icon.image = cell.imageView.image;
[cell.contentView addSubview:icon];
cell.imageView.image = nil;
return cell;
}
- (IBAction)onToggleMenu:(id)sender {
@ -2194,6 +2273,7 @@ void on_shared_player_eof_reached(LinphonePlayer *p) {
_showReplyView = true;
[self updateFramesInclRecordingAndReplyView];
[self.tableController scrollToMessage:message];
[self.messageField becomeFirstResponder];
}
-(void) handlePendingTransferIfAny {

View file

@ -41,6 +41,11 @@
return self;
}
- (void)dealloc {
bctbx_list_free(_data);
}
#pragma mark - ViewController Functions
- (void)viewWillAppear:(BOOL)animated {
@ -125,6 +130,7 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo
[LinphoneManager.instance lpConfigSetBool:FALSE forKey:@"create_chat"];
} else if (![self selectFirstRow]) {
ChatConversationCreateView *view = VIEW(ChatConversationCreateView);
[view fragmentCompositeDescription];
view.tableController.notFirstTime = FALSE;
view.isForVoipConference = FALSE;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
@ -190,7 +196,7 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo
void deletion_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState) {
LinphoneChatRoomCbs *cbs = linphone_chat_room_get_current_callbacks(cr);
ChatsListTableView *view = (__bridge ChatsListTableView *)linphone_chat_room_cbs_get_user_data(cbs) ?: NULL;
ChatsListTableView *view =cbs ? ((__bridge ChatsListTableView *)linphone_chat_room_cbs_get_user_data(cbs) ?: NULL) : NULL;
if (!view)
return;
@ -231,7 +237,9 @@ void deletion_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomStat
}
}
[ftdToDelete cancel];
// Re-enable push notification after deleting the chatroom, in order to get the notification if we are re-invited, or for secure 1-to-1 chatrooms.
[LinphoneManager setChatroomPushEnabled:chatRoom withPushEnabled:TRUE];
linphone_core_delete_chat_room(LC, chatRoom);
chatRooms = chatRooms->next;
}

View file

@ -43,4 +43,6 @@
- (IBAction)onEditionChangeClick:(id)sender;
- (IBAction)onDeleteClick:(id)sender;
-(void) mediaSharing;
@end

View file

@ -39,6 +39,10 @@
selector:@selector(ephemeralDeleted:)
name:kLinphoneEphemeralMessageDeletedInRoom
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(displayModeChanged)
name:kDisplayModeChanged
object:nil];
[_backToCallButton update];
self.tableController.waitView = _waitView;
[self setEditing:NO];
@ -54,20 +58,43 @@
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];*/
BOOL forwardMode = VIEW(ChatConversationView).pendingForwardMessage != nil;
[self mediaSharing];
}
- (void)mediaSharing{
BOOL forwardMode;
NSString* groupName = [NSString stringWithFormat:@"group.%@.linphoneExtension",[[NSBundle mainBundle] bundleIdentifier]];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName];
NSDictionary *dict = [defaults valueForKey:@"photoData"];
NSDictionary *dictFile = [defaults valueForKey:@"icloudData"];
NSDictionary *dictUrl = [defaults valueForKey:@"url"];
if(dict||dictFile||dictUrl) VIEW(ChatConversationView).sharingMedia = TRUE;
if(VIEW(ChatConversationView).sharingMedia == nil){
forwardMode = VIEW(ChatConversationView).pendingForwardMessage != nil;
}else{
forwardMode = VIEW(ChatConversationView).sharingMedia != nil;
}
_tableController.editButton.hidden = forwardMode;
_forwardTitle.text = NSLocalizedString(@"Select a discussion or create a new one",nil);
if(VIEW(ChatConversationView).sharingMedia == nil){
_forwardTitle.text = NSLocalizedString(@"Select a discussion or create a new one",nil);
}
else{
_forwardTitle.text = NSLocalizedString(@"Select or create a conversation to share the file(s)",nil);
}
_forwardTitle.hidden = !forwardMode;
_cancelForwardButton.hidden = !forwardMode;
_tableController.tableView.frame = CGRectMake(0, 66 + (forwardMode ? _forwardTitle.frame.size.height : 0), _tableController.tableView.frame.size.width, self.view.frame.size.height - 66 - ( forwardMode ? _forwardTitle.frame.size.height : 0 ));
_addButton.frame = CGRectMake(forwardMode ? 82 : 0 , _addButton.frame.origin.y, _addButton.frame.size.width, _addButton.frame.size.height);
_addGroupChatButton.frame = CGRectMake(forwardMode ? 164 : 82 , _addGroupChatButton.frame.origin.y, _addGroupChatButton.frame.size.width, _addGroupChatButton.frame.size.height);
}
- (void)displayModeChanged{
[self.tableController.tableView reloadData];
}
- (void)ephemeralDeleted:(NSNotification *)notif {
//LinphoneChatRoom *r =[[notif.userInfo objectForKey:@"room"] intValue];
@ -117,6 +144,7 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)newChatCreate:(BOOL)isGroup {
ChatConversationCreateView *view = VIEW(ChatConversationCreateView);
[view fragmentCompositeDescription];
view.isForEditing = false;
view.isGroupChat = isGroup;
view.tableController.notFirstTime = FALSE;
@ -172,8 +200,16 @@ static UICompositeViewDescription *compositeDescription = nil;
}
- (IBAction)onCancelForwardClicked:(id)sender {
VIEW(ChatConversationView).sharingMedia = nil;
VIEW(ChatConversationView).pendingForwardMessage = nil;
NSString* groupName = [NSString stringWithFormat:@"group.%@.linphoneExtension",[[NSBundle mainBundle] bundleIdentifier]];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName];
[defaults removeObjectForKey:@"photoData"];
[defaults removeObjectForKey:@"icloudData"];
[defaults removeObjectForKey:@"url"];
[PhoneMainView.instance popCurrentView];
}
@end

View file

@ -35,7 +35,7 @@
@property(nonatomic, strong) NSMutableArray *sipAddresses;
@property(nonatomic, strong) NSMutableArray *emails;
@property(nonatomic, strong) NSMutableArray *phones;
@property BOOL createdFromLdap;
@property BOOL createdFromLdapOrProvisioning;
@property BOOL added;
- (void)setAvatar:(UIImage *)avatar;
@ -58,4 +58,6 @@
- (BOOL)removeSipAddressAtIndex:(NSInteger)index;
- (BOOL)removePhoneNumberAtIndex:(NSInteger)index;
- (BOOL)removeEmailAtIndex:(NSInteger)index;
- (NSMutableArray*)getSipAddressesWithoutDuplicatePhoneNumbers;
@end

View file

@ -36,7 +36,7 @@
_person = acncontact;
_friend = afriend ? linphone_friend_ref(afriend) : NULL;
_added = FALSE;
_createdFromLdap = FALSE;
_createdFromLdapOrProvisioning = FALSE;
_phones = [[NSMutableArray alloc] init];
_sipAddresses = [[NSMutableArray alloc] init];
_emails = [[NSMutableArray alloc] init];
@ -64,13 +64,17 @@
}
const char *key = [NSString stringWithFormat:@"ab%@", acncontact.identifier].UTF8String;
// try to find friend associated with that person
if (_friend){
linphone_friend_unref(_friend);
_friend = nil;
}
_friend = linphone_friend_list_find_friend_by_ref_key(linphone_core_get_default_friend_list(LC), key);
if (!_friend) {
_friend = linphone_friend_ref(linphone_core_create_friend(LC));
_friend = linphone_core_create_friend(LC);
linphone_friend_set_ref_key(_friend, key);
linphone_friend_set_name(_friend, [NSString stringWithFormat:@"%@%@", _firstName ? _firstName : @"", _lastName ? [_firstName ? @" " : @"" stringByAppendingString:_lastName] : @""] .UTF8String);
for (NSString *sipAddr in _sipAddresses) {
LinphoneAddress *addr = linphone_core_interpret_url(LC, sipAddr.UTF8String);
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, sipAddr.UTF8String, true);
if (addr) {
linphone_address_set_display_name(addr, [self displayName].UTF8String);
linphone_friend_add_address(_friend, addr);
@ -85,9 +89,7 @@
linphone_friend_set_inc_subscribe_policy(_friend, LinphoneSPDeny);
linphone_core_add_friend(LC, _friend);
}
}
linphone_friend_ref(_friend);
}else linphone_friend_ref(_friend);
} else if (_friend) {
[self loadFriend];
} else {
@ -287,7 +289,7 @@
[_person setValue:tmpSipAddresses forKey:CNContactInstantMessageAddressesKey];
ret = TRUE;
} else {
LinphoneAddress *addr = linphone_core_interpret_url(LC, sip.UTF8String) ?: linphone_address_new(sip.UTF8String);
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, sip.UTF8String, true) ?: linphone_address_new(sip.UTF8String);
if (!addr)
return FALSE;
@ -370,7 +372,7 @@
}
ret = TRUE;
} else {
LinphoneAddress *addr = linphone_core_interpret_url(LC, ((NSString *)_sipAddresses[index]).UTF8String);
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, ((NSString *)_sipAddresses[index]).UTF8String, true);
if (!addr)
return FALSE;
@ -461,11 +463,11 @@
// try to find friend associated with that person
_friend = linphone_friend_list_find_friend_by_ref_key(linphone_core_get_default_friend_list(LC), key);
if (!_friend) {
_friend = linphone_friend_ref(linphone_core_create_friend(LC));
_friend = linphone_core_create_friend(LC);
linphone_friend_set_ref_key(_friend, key);
linphone_friend_set_name(_friend, [NSString stringWithFormat:@"%@%@", _firstName ? _firstName : @"", _lastName ? [_firstName ? @" " : @"" stringByAppendingString:_lastName] : @""] .UTF8String);
for (NSString *sipAddr in _sipAddresses) {
LinphoneAddress *addr = linphone_core_interpret_url(LC, sipAddr.UTF8String);
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, sipAddr.UTF8String, true);
if (addr) {
linphone_address_set_display_name(addr, [self displayName].UTF8String);
linphone_friend_add_address(_friend, addr);
@ -480,12 +482,31 @@
linphone_friend_set_inc_subscribe_policy(_friend, LinphoneSPDeny);
linphone_core_add_friend(LC, _friend);
}
}
linphone_friend_ref(_friend);
} else linphone_friend_ref(_friend);
}
- (void)clearFriend {
if (_friend) linphone_friend_unref(_friend);
_friend = NULL;
}
- (NSMutableArray*)getSipAddressesWithoutDuplicatePhoneNumbers {
NSMutableArray* resAdresses = [[NSMutableArray alloc] init];
for (NSString *address in _sipAddresses) {
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, [address UTF8String], YES);
bool isFoundInPhones = false;
if (addr && linphone_address_get_username(addr)) {
for (NSString *phoneNb in _phones) {
if ([phoneNb isEqualToString:[NSString stringWithUTF8String:linphone_address_get_username(addr)]]) {
isFoundInPhones = true;
break;
}
}
}
if (!isFoundInPhones) [resAdresses addObject:address];
}
return resAdresses;
}
@end

View file

@ -160,15 +160,15 @@
/*first and last name only when editting */
return (self.tableView.isEditing) ? 1 : 0;
} else if (section == ContactSections_Sip) {
return _contact.createdFromLdap ? 0 : _contact.sipAddresses.count;
return [_contact getSipAddressesWithoutDuplicatePhoneNumbers].count;
} else if (section == ContactSections_Number) {
return _contact.phones.count;
} else if (section == ContactSections_Email) {
BOOL showEmails = [LinphoneManager.instance
lpConfigBoolForKey:@"show_contacts_emails_preference"];
return showEmails ? _contact.emails.count : 0;
}
return 0;
return _contact.phones.count;
} else if (section == ContactSections_Email) {
BOOL showEmails = [LinphoneManager.instance
lpConfigBoolForKey:@"show_contacts_emails_preference"];
return showEmails ? _contact.emails.count : 0;
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
@ -200,7 +200,7 @@
LinphoneAddress *addr = NULL;
if ([LinphoneManager.instance
lpConfigBoolForKey:@"contact_display_username_only"] &&
(addr = linphone_core_interpret_url(LC, [value UTF8String]))) {
(addr = linphone_core_interpret_url_2(LC, [value UTF8String], YES))) {
value =
[NSString stringWithCString:linphone_address_get_username(addr)
encoding:[NSString defaultCStringEncoding]];
@ -283,7 +283,7 @@
if (section == ContactSections_Number) {
text = NSLocalizedString(@"Phone numbers", nil);
addEntryName = NSLocalizedString(@"Add new phone number", nil);
} else if (section == ContactSections_Sip && !_contact.createdFromLdap) {
} else if (section == ContactSections_Sip && !_contact.createdFromLdapOrProvisioning) {
text = NSLocalizedString(@"SIP addresses", nil);
addEntryName = NSLocalizedString(@"Add new SIP address", nil);
} else if (section == ContactSections_Email &&

View file

@ -102,7 +102,7 @@
}
for (NSString *sipAddr in _contact.sipAddresses) {
LinphoneAddress *addr = linphone_core_interpret_url(LC, sipAddr.UTF8String);
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, sipAddr.UTF8String, true);
if (addr) {
linphone_friend_add_address(_contact.friend, addr);
linphone_address_destroy(addr);
@ -119,8 +119,8 @@
_contact = acontact;
_emptyLabel.hidden = (_contact != NULL);
_avatarImage.hidden = !_emptyLabel.hidden;
_deleteButton.hidden = !_emptyLabel.hidden || [_contact createdFromLdap];
_editButton.hidden = !_emptyLabel.hidden || [_contact createdFromLdap];
_deleteButton.hidden = !_emptyLabel.hidden || [_contact createdFromLdapOrProvisioning];
_editButton.hidden = !_emptyLabel.hidden || [_contact createdFromLdapOrProvisioning];
[_avatarImage setImage:[FastAddressBook imageForContact:_contact] bordered:NO withRoundedRadius:YES];
[ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact];
@ -146,7 +146,7 @@
}
- (void)addCurrentContactContactField:(NSString *)address {
LinphoneAddress *linphoneAddress = linphone_core_interpret_url(LC, address.UTF8String);
LinphoneAddress *linphoneAddress = linphone_core_interpret_url_2(LC, address.UTF8String, true);
NSString *username =
linphoneAddress ? [NSString stringWithUTF8String:linphone_address_get_username(linphoneAddress)] : address;
@ -284,6 +284,9 @@
if (IPAD && self.contact == NULL) {
_editButton.hidden = TRUE;
_deleteButton.hidden = TRUE;
} else if (self.contact != NULL && self.contact.createdFromLdapOrProvisioning) {
_editButton.hidden = TRUE;
_deleteButton.hidden = TRUE;
}
PhoneMainView.instance.currentName = _nameLabel.text;
// Update presence for contact
@ -296,6 +299,7 @@
[_editButton setImage:[UIImage imageNamed:@"valid_default.png"] forState:UIControlStateSelected];
[self updateBackOrCancelButton];
[self recomputeTableViewSize:FALSE];
}
- (void)deviceOrientationDidChange:(NSNotification*)notif {
@ -310,9 +314,13 @@
}
}
if (self.contact != NULL && self.contact.createdFromLdapOrProvisioning) {
_editButton.hidden = TRUE;
_deleteButton.hidden = TRUE;
}
_nameLabel.hidden = self.tableController.isEditing;
[self updateBackOrCancelButton];
[self recomputeTableViewSize:_editButton.hidden];
[self recomputeTableViewSize:self.tableController.isEditing];
}
- (void)viewWillDisappear:(BOOL)animated {
@ -408,10 +416,11 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)recomputeTableViewSize:(BOOL)editing {
CGRect frame = _tableController.tableView.frame;
frame.origin.y = _avatarImage.frame.size.height + _avatarImage.frame.origin.y;
if ([self viewIsCurrentlyPortrait] && !editing) {
frame.origin.y += _nameLabel.frame.size.height;
}
frame.origin.y = _nameLabel.frame.origin.y + _nameLabel.frame.size.height;
} else {
frame.origin.y = _avatarImage.frame.size.height + _avatarImage.frame.origin.y;
}
frame.size.height = _tableController.tableView.contentSize.height;
_tableController.tableView.frame = frame;
@ -514,11 +523,11 @@ static UICompositeViewDescription *compositeDescription = nil;
}
NSString* previous = [PhoneMainView.instance getPreviousViewName];
if ([previous isEqualToString:@"ContactsListView"] || [previous isEqualToString:@"ImagePickerView"]) {
ContactsListView *view = VIEW(ContactsListView);
if ([previous isEqualToString:@"HistoryDetailsView"]) {
HistoryDetailsView *view = VIEW(HistoryDetailsView);
[PhoneMainView.instance popToView:view.compositeViewDescription];
} else {
HistoryDetailsView *view = VIEW(HistoryDetailsView);
ContactsListView *view = VIEW(ContactsListView);
[PhoneMainView.instance popToView:view.compositeViewDescription];
}
}
@ -588,7 +597,7 @@ static UICompositeViewDescription *compositeDescription = nil;
[[store unifiedContactWithIdentifier:contactToCheck.identifier keysToFetch:keysToFetch error:nil] mutableCopy];
if(mCNContact == NULL){
for(NSString *address in contactToCheck.sipAddresses){
NSString *name = [FastAddressBook normalizeSipURI:address];
NSString *name = [FastAddressBook normalizeSipURI:address use_prefix:TRUE];
if([LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:name]){
return TRUE;
}

View file

@ -58,6 +58,7 @@ typedef enum _ContactSelectionMode { ContactSelectionModeNone, ContactSelectionM
@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleSelectionButton;
@property (weak, nonatomic) IBOutlet UILabel *loadingLabel;
@property (weak, nonatomic) IBOutlet UIView *loadingView;
@property (weak, nonatomic) IBOutlet UILabel *ldapMoreResultsLabel;
- (IBAction)onAllClick:(id)event;
- (IBAction)onLinphoneClick:(id)event;

View file

@ -142,6 +142,15 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(onMagicSearchFinished:)
name:kLinphoneMagicSearchFinished
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(onMagicSearchMoreAvailable:)
name:kLinphoneMagicSearchMoreAvailable
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(displayModeChanged)
name:kDisplayModeChanged
object:nil];
}
- (void)onMagicSearchStarted:(NSNotification *)k {
@ -150,6 +159,9 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)onMagicSearchFinished:(NSNotification *)k {
_loadingView.hidden = TRUE;
}
- (void)onMagicSearchMoreAvailable:(NSNotification *)k {
_ldapMoreResultsLabel.hidden = FALSE;
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"Debuglog viewDidAppear");
@ -225,7 +237,7 @@ static UICompositeViewDescription *compositeDescription = nil;
}
- (void)refreshButtons {
[addButton setHidden:FALSE];
[addButton setHidden:![LinphoneManager.instance lpConfigBoolForKey:@"enable_native_address_book"]];
[self changeView:[ContactSelection getSipFilterEnabled] ? ContactsLinphone : ContactsAll];
}
@ -250,6 +262,10 @@ static UICompositeViewDescription *compositeDescription = nil;
}
}
- (void)displayModeChanged{
[self.tableController.tableView reloadData];
}
- (IBAction)onDeleteClick:(id)sender {
NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"Do you want to delete selected contacts?\nThey will also be deleted from your phone's address book.", nil)];
[LinphoneManager.instance setContactsUpdated:TRUE];
@ -268,6 +284,9 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onEditionChangeClick:(id)sender {
allButton.hidden = linphoneButton.hidden = _selectedButtonImage.hidden = addButton.hidden = self.tableController.isEditing;
if ([LinphoneManager.instance lpConfigBoolForKey:@"enable_native_address_book"]) {
addButton.hidden = self.tableController.isEditing;
}
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
@ -292,6 +311,7 @@ static UICompositeViewDescription *compositeDescription = nil;
if (searchText.length == 0) {
[LinphoneManager.instance setContactsUpdated:TRUE];
}
_ldapMoreResultsLabel.hidden = TRUE;
[tableController loadDataWithFilter:searchText];
}
}

View file

@ -145,9 +145,17 @@ static UICompositeViewDescription *compositeDescription = nil;
[_videoCameraSwitch setHidden:FALSE];
}
}
[_addContactButton setImage:[UIImage imageNamed:@"voip_conference_new"] forState:UIControlStateNormal];
_addContactButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_addContactButton.enabled = true;
LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
if (!(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount)))){
[_addContactButton setImage:[UIImage imageNamed:@"contact_add_default"] forState:UIControlStateNormal];
_addContactButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_addContactButton.enabled = true;
}else{
[_addContactButton setImage:[UIImage imageNamed:@"voip_conference_new"] forState:UIControlStateNormal];
_addContactButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_addContactButton.enabled = true;
}
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
@ -175,10 +183,11 @@ static UICompositeViewDescription *compositeDescription = nil;
_padView.hidden = !IPAD && UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
if (linphone_core_get_calls_nb(LC)) {
_backButton.hidden = FALSE;
_addContactButton.hidden = TRUE;
} else {
_backButton.hidden = TRUE;
_addContactButton.hidden = FALSE;
}
_addContactButton.hidden = FALSE;
}
- (void)viewDidAppear:(BOOL)animated {
@ -392,19 +401,38 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - Action Functions
- (IBAction)onAddContactClick:(id)event {
ConferenceSchedulingView *view = VIEW(ConferenceSchedulingView);
[view resetViewModel];
[PhoneMainView.instance changeCurrentView:ConferenceSchedulingView.compositeViewDescription];
LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
if (!(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount)))){
[ContactSelection setSelectionMode:ContactSelectionModeEdit];
[ContactSelection setAddAddress:[_addressField text]];
[ContactSelection enableSipFilter:FALSE];
[PhoneMainView.instance changeCurrentView:ContactsListView.compositeViewDescription];
}else{
ConferenceSchedulingView *view = VIEW(ConferenceSchedulingView);
[view resetViewModel];
[PhoneMainView.instance changeCurrentView:ConferenceSchedulingView.compositeViewDescription];
}
}
- (IBAction)onBackClick:(id)event {
[PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription];
[PhoneMainView.instance popToView:[CallsViewModelBridge callViewToDisplay]];
}
- (IBAction)onAddressChange:(id)sender {
if ([self displayDebugPopup:_addressField.text]) {
_addressField.text = @"";
}
LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
if (!(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount)))){
[_addContactButton setImage:[UIImage imageNamed:@"contact_add_default"] forState:UIControlStateNormal];
_addContactButton.enabled = ([[_addressField text] length] > 0);
if ([_addressField.text length] == 0) {
[self.view endEditing:YES];
}
}else{
[_addContactButton setImage:[UIImage imageNamed:@"voip_conference_new"] forState:UIControlStateNormal];
_addContactButton.enabled = true;
}
}
- (IBAction)onBackspaceClick:(id)sender {

View file

@ -69,14 +69,15 @@ static UICompositeViewDescription *compositeDescription = nil;
object:nil];
// Update on show
const MSList *list = linphone_core_get_account_list([LinphoneManager getLc]);
MSList *list = [LinphoneManager.instance createAccountsNotHiddenList];
if (list != NULL) {
LinphoneAccount *account = (LinphoneAccount *)list->data;
if (account) {
[self registrationUpdate:linphone_account_get_state(account)];
}
}
bctbx_list_free(list);
if (account_creator) {
linphone_account_creator_unref(account_creator);
}

View file

@ -41,6 +41,7 @@
@property (weak, nonatomic) IBOutlet UIView *waitView;
@property (weak, nonatomic) IBOutlet UIRoundedImageView *linphoneImage;
@property (weak, nonatomic) IBOutlet UIView *optionsView;
@property(weak, nonatomic) IBOutlet UIButton *chatButton;
@property (weak, nonatomic) IBOutlet UIView *encryptedChatView;
- (IBAction)onBackClick:(id)event;

View file

@ -68,6 +68,12 @@ static UICompositeViewDescription *compositeDescription = nil;
[_headerView addGestureRecognizer:headerTapGesture];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
_chatButton.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"];
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[self update];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
_waitView.hidden = YES;

View file

@ -268,9 +268,13 @@
} else {
if (linphone_call_log_was_conference(callLog)) {
LinphoneConferenceInfo *confInfo = linphone_call_log_get_conference_info(callLog);
ConferenceWaitingRoomFragment *view = VIEW(ConferenceWaitingRoomFragment);
if (linphone_conference_info_get_state(confInfo) == LinphoneConferenceInfoStateCancelled) {
[ConferenceViewModelBridge showCancelledMeetingWithCConferenceInfo:confInfo];
return;
}
ConferenceWaitingRoomView *view = VIEW(ConferenceWaitingRoomView);
[view setDetailsWithSubject:[NSString stringWithUTF8String:linphone_conference_info_get_subject(confInfo)] url:[NSString stringWithUTF8String:linphone_address_as_string(linphone_conference_info_get_uri(confInfo))]];
[PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomFragment.compositeViewDescription];
[PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomView.compositeViewDescription];
} else {
const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog);
[LinphoneManager.instance call:addr];

View file

@ -67,10 +67,15 @@ static UICompositeViewDescription *compositeDescription = nil;
// Fake event
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
[_toggleSelectionButton setImage:[UIImage imageNamed:@"select_all_default.png"] forState:UIControlStateSelected];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(displayModeChanged)
name:kDisplayModeChanged
object:nil];
}
- (void) viewWillDisappear:(BOOL)animated {
self.view = NULL;
[NSNotificationCenter.defaultCenter removeObserver:self];
}
#pragma mark -
@ -100,6 +105,10 @@ static UICompositeViewDescription *compositeDescription = nil;
_selectedButtonImage.frame = frame;
}
- (void)displayModeChanged{
[self.tableController.tableView reloadData];
}
#pragma m ~ark - Action Functions
- (IBAction)onAllClick:(id)event {

View file

@ -35,6 +35,8 @@
#import <IntentsUI/IntentsUI.h>
#import "linphoneapp-Swift.h"
#import "SVProgressHUD.h"
#ifdef USE_CRASHLYTICS
#include "FIRApp.h"
@ -85,6 +87,7 @@
[LinphoneManager.instance.fastAddressBook reloadFriends];
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:nil];
}
- (void)applicationWillResignActive:(UIApplication *)application {
@ -137,7 +140,6 @@
if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) {
linphone_call_accept(call);
[PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
} else {
[PhoneMainView.instance displayIncomingCall:call];
}
@ -156,7 +158,9 @@
[self handleShortcut:_shortcutItem];
_shortcutItem = nil;
}
#if TARGET_IPHONE_SIMULATOR
#else
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (error)
@ -182,6 +186,16 @@
}
}];
#endif
if ([UIDeviceBridge switchedDisplayMode]) {
[AvatarBridge prepareIt];
[NSNotificationCenter.defaultCenter postNotificationName:kDisplayModeChanged object:nil];
[PhoneMainView.instance.mainViewController removeEntryFromCache:ChatConversationCreateView.compositeViewDescription.name];
[PhoneMainView.instance.mainViewController changeView:PhoneMainView.instance.currentView];
[UIDeviceBridge notifyDisplayModeSwitch];
}
}
@ -331,8 +345,9 @@
return NO;
}
[PhoneMainView.instance.mainViewController getCachedController:ActiveCallOrConferenceView.compositeViewDescription.name]; // This will create the single instance of the ActiveCallOrConferenceView including listeneres
[PhoneMainView.instance.mainViewController getCachedController:SingleCallView.compositeViewDescription.name]; // This will create the single instance of the SingleCallView including listeneres
[PhoneMainView.instance.mainViewController getCachedController:ConferenceCallView.compositeViewDescription.name]; // This will create the single instance of the ConferenceCallView including listeneres
[CallsViewModelBridge setupCallsViewNavigation];
return YES;
}
@ -352,6 +367,7 @@
- (BOOL)handleShortcut:(UIApplicationShortcutItem *)shortcutItem {
BOOL success = NO;
if ([shortcutItem.type isEqualToString:@"linphone.phone.action.newMessage"]) {
[VIEW(ChatConversationCreateView) fragmentCompositeDescription];
[PhoneMainView.instance changeCurrentView:ChatConversationCreateView.compositeViewDescription];
success = YES;
}
@ -388,7 +404,15 @@
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else if([[url scheme] isEqualToString:@"message-linphone"]) {
[PhoneMainView.instance popToView:ChatsListView.compositeViewDescription];
if ([[PhoneMainView.instance currentView] equal:ChatsListView.compositeViewDescription]) {
VIEW(ChatConversationView).sharingMedia = TRUE;
ChatsListView *view = VIEW(ChatsListView);
[view mediaSharing];
}else{
[SVProgressHUD dismiss];
VIEW(ChatConversationView).sharingMedia = TRUE;
[PhoneMainView.instance popToView:ChatsListView.compositeViewDescription];
}
} else if ([scheme isEqualToString:@"sip"]||[scheme isEqualToString:@"sips"]) {
// remove "sip://" from the URI, and do it correctly by taking resourceSpecifier and removing leading and
// trailing "/"
@ -503,15 +527,15 @@
- (void) userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
// If an app extension launch a user notif while app is in fg, it is catch by the app
NSString *category = [[[notification request] content] categoryIdentifier];
if (category && [category isEqualToString:@"app_active"]) {
return;
}
NSString *category = [[[notification request] content] categoryIdentifier];
if (category && [category isEqualToString:@"app_active"]) {
return;
}
if (category && [category isEqualToString:@"msg_cat"] && [UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
if ((PhoneMainView.instance.currentView == ChatsListView.compositeViewDescription))
return;
if (PhoneMainView.instance.currentView == ChatConversationView.compositeViewDescription) {
NSDictionary *userInfo = [[[notification request] content] userInfo];
NSString *peerAddress = userInfo[@"peer_addr"];
@ -519,12 +543,12 @@
if (peerAddress && localAddress) {
LinphoneAddress *peer = linphone_core_create_address([LinphoneManager getLc], peerAddress.UTF8String);
LinphoneAddress *local = linphone_core_create_address([LinphoneManager getLc], localAddress.UTF8String);
LinphoneChatRoom *room = linphone_core_find_chat_room([LinphoneManager getLc], peer, local);
LinphoneChatRoom *room = linphone_core_search_chat_room([LinphoneManager getLc], NULL, local, peer, NULL);
if (room == PhoneMainView.instance.currentRoom) return;
}
}
}
completionHandler(UNNotificationPresentationOptionAlert);
}
@ -616,7 +640,6 @@
}
} else if ([response.notification.request.content.categoryIdentifier isEqual:@"video_request"]) {
if (!call) return;
[PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
NSTimer *videoDismissTimer = nil;
UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:response.notification.request.content.body
cancelMessage:nil

View file

@ -27,6 +27,7 @@
#include <stdio.h>
#include <stdlib.h>
@implementation LinphoneCoreSettingsStore
- (id)init {
@ -141,7 +142,9 @@
}
- (void)transformAccountToKeys:(NSString *)username {
const MSList *accountList = linphone_core_get_account_list(LC);
//const MSList *accountList = linphone_core_get_account_list(LC);
MSList *accountListToBeFreed = [LinphoneManager.instance createAccountsNotHiddenList];
MSList *accountList = accountListToBeFreed;
while (username && accountList &&
strcmp(username.UTF8String,
linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(accountList->data)))) != 0) {
@ -168,6 +171,7 @@
[self setInteger:-1 forKey:@"account_expire_preference"];
[self setInteger:-1 forKey:@"current_proxy_config_preference"];
[self setCString:"" forKey:@"account_prefix_preference"];
[self setBool:YES forKey:@"apply_international_prefix_for_calls_and_chats"];
[self setBool:NO forKey:@"account_substitute_+_by_00_preference"];
[self setBool:NO forKey:@"account_ice_preference"];
[self setCString:"" forKey:@"account_stun_preference"];
@ -183,7 +187,7 @@
const LinphoneAddress *identity_addr = linphone_account_params_get_identity_address(accountParams);
const char *server_addr = linphone_account_params_get_server_addr(accountParams);
LinphoneAddress *proxy_addr = linphone_core_interpret_url(LC, server_addr);
LinphoneAddress *proxy_addr = linphone_core_interpret_url_2(LC, server_addr, false);
if (identity_addr && proxy_addr) {
int port = linphone_address_get_port(proxy_addr);
@ -234,9 +238,11 @@
[self setCString:linphone_auth_info_get_algorithm(ai) forKey:@"ha1_algo_preference"];
}
MSList *accountsList = [LinphoneManager.instance createAccountsNotHiddenList];
int idx = (int)bctbx_list_index(linphone_core_get_account_list(LC), account);
[self setInteger:idx forKey:@"current_proxy_config_preference"];
bctbx_list_free(accountsList);
int expires = linphone_account_params_get_expires(accountParams);
[self setInteger:expires forKey:@"account_expire_preference"];
@ -251,10 +257,13 @@
{
const char *dial_prefix = linphone_account_params_get_international_prefix(accountParams);
[self setCString:dial_prefix forKey:@"account_prefix_preference"];
BOOL apply_prefix = linphone_account_params_get_use_international_prefix_for_calls_and_chats(accountParams);
[self setBool:apply_prefix forKey:@"apply_international_prefix_for_calls_and_chats"];
BOOL dial_escape_plus = linphone_account_params_get_dial_escape_plus_enabled(accountParams);
[self setBool:dial_escape_plus forKey:@"account_substitute_+_by_00_preference"];
}
}
bctbx_list_free(accountListToBeFreed);
}
@ -343,15 +352,17 @@
// root section
{
const bctbx_list_t *accounts = linphone_core_get_account_list(LC);
size_t count = bctbx_list_size(accounts);
for (size_t i = 1; i <= count; i++, accounts = accounts->next) {
MSList *accountsListToBeFreed = [lm createAccountsNotHiddenList];
MSList *accountsList = accountsListToBeFreed;
size_t count = bctbx_list_size(accountsList);
for (size_t i = 1; i <= count; i++, accountsList = accountsList->next) {
NSString *key = [NSString stringWithFormat:@"menu_account_%lu", i];
LinphoneAccount *account = (LinphoneAccount *)accounts->data;
LinphoneAccount *account = (LinphoneAccount *)accountsList->data;
[self setCString:linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account)))
forKey:key];
}
bctbx_free(accountsListToBeFreed);
[self setBool:linphone_core_video_display_enabled(LC) forKey:@"enable_video_preference"];
[self setBool:[LinphoneManager.instance lpConfigBoolForKey:@"auto_answer"]
forKey:@"enable_auto_answer_preference"];
@ -431,10 +442,10 @@
{
[self setCString:linphone_core_get_file_transfer_server(LC) forKey:@"file_transfer_server_url_preference"];
int maxSize = linphone_core_get_max_size_for_auto_download_incoming_files(LC);
[self setObject:maxSize==0 ? @"Always" : (maxSize==-1 ? @"Nerver" : @"Customize") forKey:@"auto_download_mode"];
[self setObject:maxSize==0 ? @"Always" : (maxSize==-1 ? @"Never" : @"Customize") forKey:@"auto_download_mode"];
[self setInteger:maxSize forKey:@"auto_download_incoming_files_max_size"];
[self setBool:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] forKey:@"vfs_enabled_mode"];
[self setBool:[lm lpConfigBoolForKey:@"auto_write_to_gallery_preference" withDefault:YES] forKey:@"auto_write_to_gallery_mode"];
[self setBool:[lm lpConfigBoolForKey:@"auto_write_to_gallery_preference" withDefault:NO] forKey:@"auto_write_to_gallery_mode"];
}
// network section
@ -486,7 +497,7 @@
val = "None";
break;
}
[self setCString:val forKey:@"media_encryption_preference"];
[self setCString:val forKey:linphone_core_get_post_quantum_available() ? @"media_encryption_preference_pq_enabled" : @"media_encryption_preference"];
[self setInteger:linphone_core_get_upload_bandwidth(LC) forKey:@"upload_bandwidth_preference"];
[self setInteger:linphone_core_get_download_bandwidth(LC) forKey:@"download_bandwidth_preference"];
[self setBool:linphone_core_adaptive_rate_control_enabled(LC) forKey:@"adaptive_rate_control_preference"];
@ -604,6 +615,7 @@
int expire = [self integerForKey:@"account_expire_preference"];
BOOL pushnotification = [self boolForKey:@"account_pushnotification_preference"];
NSString *prefix = [self stringForKey:@"account_prefix_preference"];
BOOL use_prefix = [self boolForKey:@"apply_international_prefix_for_calls_and_chats"];
NSString *proxyAddress = [self stringForKey:@"account_proxy_preference"];
if ((!proxyAddress || [proxyAddress length] < 1) && domain) {
@ -614,7 +626,7 @@
proxyAddress = [NSString stringWithFormat:@"sip:%@", proxyAddress];
}
LinphoneAddress *proxy_addr = linphone_core_interpret_url(LC, proxyAddress.UTF8String);
LinphoneAddress *proxy_addr = linphone_core_interpret_url_2(LC, proxyAddress.UTF8String, false);
if (proxy_addr) {
LinphoneTransportType type = LinphoneTransportUdp;
@ -625,9 +637,11 @@
linphone_address_set_transport(proxy_addr, type);
}
account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
MSList *accountList= [LinphoneManager.instance createAccountsNotHiddenList];
account = bctbx_list_nth_data(accountList,
[self integerForKey:@"current_proxy_config_preference"]);
bctbx_free(accountList);
// if account was deleted, it is not present anymore
if (account == NULL)
@ -670,10 +684,8 @@
linphone_nat_policy_set_stun_server(policy, stun_preference.UTF8String);
linphone_account_params_set_nat_policy(newAccountParams, policy);
if ([prefix length] > 0) {
linphone_account_params_set_international_prefix(newAccountParams, [prefix UTF8String]);
}
linphone_account_params_set_international_prefix(newAccountParams, [prefix UTF8String]);
linphone_account_params_set_use_international_prefix_for_calls_and_chats(newAccountParams, use_prefix);
if ([self objectForKey:@"account_substitute_+_by_00_preference"]) {
bool substitute_plus_by_00 = [self boolForKey:@"account_substitute_+_by_00_preference"];
linphone_account_params_set_dial_escape_plus_enabled(newAccountParams, substitute_plus_by_00);
@ -713,7 +725,7 @@
}
char *identity = linphone_address_as_string(linphoneAddress);
LinphoneAddress *from = linphone_core_interpret_url(LC, identity);
LinphoneAddress *from = linphone_core_interpret_url_2(LC, identity, false);
ms_free(identity);
if (from) {
const char *userid_str = (userID != nil) ? [userID UTF8String] : NULL;
@ -805,7 +817,7 @@
linphone_ldap_params_set_bind_dn(newLdapParams, [self stringForKey:@"ldap_bind_dn"].UTF8String);
linphone_ldap_params_set_password(newLdapParams, [self stringForKey:@"ldap_password"].UTF8String);
LinphoneLdapAuthMethod authMethod = [[self stringForKey:@"ldap_verification_method"] isEqualToString:@"simple"] ? LinphoneLdapAuthMethodSimple : LinphoneLdapAuthMethodAnonymous;
LinphoneLdapAuthMethod authMethod = [[self stringForKey:@"ldap_auth_method"] isEqualToString:@"simple"] ? LinphoneLdapAuthMethodSimple : LinphoneLdapAuthMethodAnonymous;
linphone_ldap_params_set_auth_method(newLdapParams, authMethod);
linphone_ldap_params_enable_tls(newLdapParams, [self boolForKey:@"ldap_tls_enabled"]);
@ -829,8 +841,8 @@
// Analysis parameters
linphone_ldap_params_set_name_attribute(newLdapParams, [self stringForKey:@"ldap_name_attributes"].UTF8String);
linphone_ldap_params_set_sip_attribute(newLdapParams, [self stringForKey:@"ldap_sip_attributes"].UTF8String);
linphone_ldap_params_set_name_attribute(newLdapParams, [self stringForKey:@"ldap_name_attribute"].UTF8String);
linphone_ldap_params_set_sip_attribute(newLdapParams, [self stringForKey:@"ldap_sip_attribute"].UTF8String);
linphone_ldap_params_set_sip_domain(newLdapParams, [self stringForKey:@"ldap_sip_domain"].UTF8String);
// Miscellaneous parameters
@ -1020,7 +1032,7 @@
[LinphoneCoreSettingsStore parsePortRange:video_port_preference minPort:&videoMinPort maxPort:&videoMaxPort];
linphone_core_set_video_port_range(LC, videoMinPort, videoMaxPort);
NSString *menc = [self stringForKey:@"media_encryption_preference"];
NSString *menc = [self stringForKey:linphone_core_get_post_quantum_available() ? @"media_encryption_preference_pq_enabled" : @"media_encryption_preference"];
if (menc && [menc compare:@"SRTP"] == NSOrderedSame)
linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionSRTP);
else if (menc && [menc compare:@"ZRTP"] == NSOrderedSame)
@ -1096,7 +1108,9 @@
NSString *rls_uri = [lm lpConfigStringForKey:@"rls_uri" inSection:@"sip" withDefault:@"sips:rls@sip.linphone.org"];
LinphoneAddress *rls_addr = linphone_address_new(rls_uri.UTF8String);
const char *rls_domain = linphone_address_get_domain(rls_addr);
const MSList *accounts = linphone_core_get_account_list(LC);
MSList *accountListToBeFreed = [LinphoneManager.instance createAccountsNotHiddenList];
const MSList *accounts = accountListToBeFreed;
if (!accounts) // Enable it if no proxy config for first launch of app
[self setInteger:1 forKey:@"use_rls_presence"];
else {
@ -1110,6 +1124,7 @@
}
}
linphone_address_unref(rls_addr);
bctbx_free(accountListToBeFreed);
}
[lm lpConfigSetInt:[self integerForKey:@"use_rls_presence"] forKey:@"use_rls_presence"];
@ -1151,9 +1166,12 @@
}
- (void)removeAccount {
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
MSList *accountList = [LinphoneManager.instance createAccountsNotHiddenList];
LinphoneAccount *account = bctbx_list_nth_data(accountList,
[self integerForKey:@"current_proxy_config_preference"]);
const MSList *lists = linphone_core_get_friends_lists(LC);
while (lists) {
linphone_friend_list_enable_subscriptions(lists->data, FALSE);
@ -1174,11 +1192,12 @@
if (isDefault) {
// if we removed the default proxy config, set another one instead
if (linphone_core_get_account_list(LC) != NULL) {
linphone_core_set_default_account(LC, (LinphoneAccount *)(linphone_core_get_account_list(LC)->data));
if (accountList != NULL) {
linphone_core_set_default_account(LC, (LinphoneAccount *)(accountList->data));
}
}
[self transformLinphoneCoreToKeys];
bctbx_free(accountList);
}
- (void)removeLdap {

View file

@ -65,6 +65,8 @@ extern NSString *const kLinphoneConfStateParticipantListChanged;
extern NSString *const kLinphoneConfStateChanged;
extern NSString *const kLinphoneMagicSearchStarted;
extern NSString *const kLinphoneMagicSearchFinished;
extern NSString *const kLinphoneMagicSearchMoreAvailable;
extern NSString *const kDisplayModeChanged;
extern NSString *const kLinphoneMsgNotificationAppGroupId;
@ -174,6 +176,7 @@ typedef struct _LinphoneManagerSounds {
- (void)silentPushFailed:(NSTimer*)timer;
- (MSList *) createAccountsNotHiddenList; // needs to be unref
- (void)removeAllAccounts;
+ (BOOL)isMyself:(const LinphoneAddress *)addr;
@ -192,6 +195,8 @@ typedef struct _LinphoneManagerSounds {
- (BOOL)isCTCallCenterExist;
- (void) checkLocalNetworkPermission;
+ (BOOL) getChatroomPushEnabled:(LinphoneChatRoom *)chatroom;
+ (void) setChatroomPushEnabled:(LinphoneChatRoom *)chatroom withPushEnabled:(BOOL)enabled;
@property (readonly) BOOL isTesting;
@property(readonly, strong) FastAddressBook *fastAddressBook;

View file

@ -81,13 +81,14 @@ NSString *const kLinphoneConfStateChanged = @"kLinphoneConfStateChanged";
NSString *const kLinphoneConfStateParticipantListChanged = @"kLinphoneConfStateParticipantListChanged";
NSString *const kLinphoneMagicSearchStarted = @"LinphoneMagicSearchStarted";
NSString *const kLinphoneMagicSearchFinished = @"LinphoneMagicSearchFinished";
NSString *const kLinphoneMagicSearchMoreAvailable = @"LinphoneMagicSearchMoreAvailable";
NSString *const kDisplayModeChanged = @"DisplayModeChanged";
NSString *const kLinphoneMsgNotificationAppGroupId = @"group.org.linphone.phone.msgNotification";
const int kLinphoneAudioVbrCodecDefaultBitrate = 36; /*you can override this from linphonerc or linphonerc-factory*/
extern void libmsamr_init(MSFactory *factory);
extern void libmsx264_init(MSFactory *factory);
extern void libmsopenh264_init(MSFactory *factory);
extern void libmssilk_init(MSFactory *factory);
extern void libmswebrtc_init(MSFactory *factory);
@ -276,6 +277,14 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
[self lpConfigSetString:@"conflate" forKey:@"handle_content_encoding" inSection:@"misc"];
#endif
}
if ([self lpConfigStringForKey:@"display_link_account_popup"] == nil) {
[self lpConfigSetBool:true forKey:@"display_link_account_popup"];
}
if ([self lpConfigStringForKey:@"hide_link_phone_number"] == nil) {
[self lpConfigSetInt:1 forKey:@"hide_link_phone_number"];
}
[self migrateFromUserPrefs];
[self loadAvatar];
@ -637,6 +646,11 @@ static void linphone_iphone_global_state_changed(LinphoneCore *lc, LinphoneGloba
if (theLinphoneCore && linphone_core_get_global_state(theLinphoneCore) != LinphoneGlobalOff)
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneGlobalStateUpdate object:self userInfo:dict];
});
if (state == LinphoneGlobalOn) {
// reload friends
[self.fastAddressBook fetchContactsInBackGroundThread];
}
}
- (void)globalStateChangedNotificationHandler:(NSNotification *)notif {
@ -1254,7 +1268,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
[LinphoneManager.instance lpConfigSetInt:0 forKey:@"must_link_account_time"];
} else {
LinphoneAccount *account = linphone_core_get_default_account(LC);
LinphoneAccountParams const *accountParams = linphone_account_get_params(account);
LinphoneAccountParams const *accountParams = account ? linphone_account_get_params(account) : NULL;
if (account &&
strcmp(linphone_account_params_get_domain(accountParams),
[LinphoneManager.instance lpConfigStringForKey:@"domain_name"
@ -1275,7 +1289,14 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
handler:^(UIAlertAction * action) {
[PhoneMainView.instance changeCurrentView:AssistantLinkView.compositeViewDescription];
}];
UIAlertAction* otherAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Never ask again", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[LinphoneManager.instance lpConfigSetBool:false forKey:@"display_link_account_popup"];
}];
defaultAction.accessibilityLabel = @"Later";
[errView addAction:otherAction];
[errView addAction:defaultAction];
[errView addAction:continueAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
@ -1294,7 +1315,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
NSDate *nextTime =
[NSDate dateWithTimeIntervalSince1970:[self lpConfigIntForKey:@"must_link_account_time" withDefault:1]];
NSDate *now = [NSDate date];
if (nextTime.timeIntervalSince1970 > 0 && [now earlierDate:nextTime] == nextTime) {
if (nextTime.timeIntervalSince1970 > 0 && [now earlierDate:nextTime] == nextTime && [LinphoneManager.instance lpConfigBoolForKey:@"display_link_account_popup"] && ![LinphoneManager.instance lpConfigIntForKey:@"hide_link_phone_number"]) {
LinphoneAccount *account = linphone_core_get_default_account(LC);
if (account) {
const char *username = linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account)));
@ -1334,22 +1355,37 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
}
}
- (void)activateBasicChatroomCPIMForLinphoneAccounts {
- (void)enableLinphoneAccountSpecificSettings {
const MSList *accountsList = linphone_core_get_account_list(theLinphoneCore);
while (accountsList) {
LinphoneAccount * account = accountsList->data;
LinphoneAccountParams const * currentParams = linphone_account_get_params(account);
LinphoneAddress const * currentAddress = linphone_account_params_get_identity_address(currentParams);
char * addressIdentity = linphone_address_as_string(currentAddress);
if (strcmp(linphone_address_get_domain(currentAddress), "sip.linphone.org") == 0 && !linphone_account_params_cpim_in_basic_chat_room_enabled(currentParams) ) {
LOGI(@"Enabling CPIM in basic chatroom for user %s", linphone_address_get_username(currentAddress));
if (strcmp(linphone_address_get_domain(currentAddress), "sip.linphone.org") == 0) {
LinphoneAccountParams * newParams = linphone_account_params_clone(linphone_account_get_params(account));
linphone_account_params_enable_cpim_in_basic_chat_room(newParams, true);
if (!linphone_account_params_cpim_in_basic_chat_room_enabled(currentParams) ) {
LOGI(@"Enabling CPIM in basic chatroom for account [%s]", addressIdentity);
linphone_account_params_enable_cpim_in_basic_chat_room(newParams, true);
}
const char* current_lime_url = linphone_account_params_get_lime_server_url(currentParams);
if (!current_lime_url){
const char* core_lime_url = linphone_core_get_lime_x3dh_server_url(LC);
if (core_lime_url) {
LOGI(@"Copying core's LIME X3DH server URL [%s] to account [%s]", core_lime_url, addressIdentity);
linphone_account_params_set_lime_server_url(newParams, core_lime_url);
} else {
LOGI(@"Account [%s] didn't have a LIME X3DH server URL, setting one: [%s]", addressIdentity, core_lime_url);
linphone_account_params_set_lime_server_url(newParams, "https://lime.linphone.org/lime-server/lime-server.php");
}
}
linphone_account_set_params(account, newParams);
linphone_account_params_unref(newParams);
}
ms_free(addressIdentity);
accountsList = accountsList->next;
}
}
@ -1360,7 +1396,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
linphone_core_start([LinphoneManager getLc]);
[self configurePushProviderForAccounts];
[self activateBasicChatroomCPIMForLinphoneAccounts];
[self enableLinphoneAccountSpecificSettings];
}
- (void)createLinphoneCore {
@ -1429,7 +1465,6 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
MSFactory *f = linphone_core_get_ms_factory(theLinphoneCore);
libmssilk_init(f);
libmsamr_init(f);
libmsx264_init(f);
libmsopenh264_init(f);
libmswebrtc_init(f);
libmscodec2_init(f);
@ -1459,6 +1494,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
- (void)destroyLinphoneCore {
// just in case
[self removeCTCallCenterCb];
[MagicSearchSingleton destroyInstance];
if (theLinphoneCore != nil) { // just in case application terminate before linphone core initialization
@ -1488,8 +1524,6 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
- (void)resetLinphoneCore {
[self destroyLinphoneCore];
[self createLinphoneCore];
// reload friends
[self.fastAddressBook fetchContactsInBackGroundThread];
}
static int comp_call_id(const LinphoneCall *call, const char *callid) {
@ -1884,7 +1918,10 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
}
[self checkLocalNetworkPermission];
// For OutgoingCall, show CallOutgoingView
[CallManager.instance startCallWithAddr:iaddr isSas:FALSE isVideo:false isConference:false];
LinphoneVideoActivationPolicy *policy = linphone_core_get_video_activation_policy(LC);
BOOL initiateVideoCall = linphone_video_activation_policy_get_automatically_initiate(policy);
[CallManager.instance startCallWithAddr:iaddr isSas:FALSE isVideo:initiateVideoCall isConference:false];
linphone_video_activation_policy_unref(policy);
}
#pragma mark - Misc Functions
@ -2210,6 +2247,23 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
#pragma mark -
- (MSList *) createAccountsNotHiddenList {
MSList *list = NULL;
const MSList *accounts = linphone_core_get_account_list(LC);
while (accounts) {
const char *isHidden = linphone_account_get_custom_param(accounts->data, "hidden");
if (isHidden == NULL || strcmp(linphone_account_get_custom_param(accounts->data, "hidden"), "1") != 0) {
if (!list) {
list = bctbx_list_new(accounts->data);
} else {
bctbx_list_append(list, accounts->data);
}
}
accounts = accounts->next;
}
return list;
}
- (void)removeAllAccounts {
linphone_core_clear_accounts(LC);
linphone_core_clear_all_auth_info(LC);
@ -2303,6 +2357,31 @@ void linphone_iphone_conference_state_changed(LinphoneCore *lc, LinphoneConferen
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneConfStateChanged object:nil userInfo:dict];
}
+ (BOOL) getChatroomPushEnabled:(LinphoneChatRoom *)chatroom {
bool currently_enabled = true;
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kLinphoneMsgNotificationAppGroupId];
NSDictionary *chatroomsPushStatus = [defaults dictionaryForKey:@"chatroomsPushStatus"];
if (chatroomsPushStatus != nil && chatroom) {
char *uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(chatroom));
NSString* pushStatus = [chatroomsPushStatus objectForKey:[NSString stringWithUTF8String:uri]];
currently_enabled = (pushStatus == nil) || [pushStatus isEqualToString:@"enabled"];
ms_free(uri);
}
return currently_enabled;
}
+ (void) setChatroomPushEnabled:(LinphoneChatRoom *)chatroom withPushEnabled:(BOOL)enabled {
if (!chatroom) return;
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kLinphoneMsgNotificationAppGroupId];
NSMutableDictionary *chatroomsPushStatus = [[NSMutableDictionary alloc] initWithDictionary:[defaults dictionaryForKey:@"chatroomsPushStatus"]];
if (chatroomsPushStatus == nil) chatroomsPushStatus = [[NSMutableDictionary dictionary] init];
char *uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(chatroom));
[chatroomsPushStatus setValue:(enabled ? @"enabled" : @"disabled") forKey:[NSString stringWithUTF8String:uri]];
ms_free(uri);
[defaults setObject:chatroomsPushStatus forKey:@"chatroomsPushStatus"];
}
@end

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -57,7 +57,7 @@
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onSideMenuClick:" destination="-1" eventType="touchUpInside" id="iOC-wy-MPP"/>
<action selector="onQualityClick:" destination="-1" eventType="touchUpInside" id="Pir-Ib-ywJ"/>
</connections>
</button>
</subviews>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -81,6 +81,22 @@
<rect key="frame" x="0.0" y="0.0" width="365" height="357"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_read.png" translatesAutoresizingMaskIntoConstraints="NO" id="LPj-VT-0fH" userLabel="imdmIcon">
<rect key="frame" x="372" y="342" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed"/>
</imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ephemeral_messages_color_A.png" id="7JB-ZL-0lZ" userLabel="ephemeralIcon">
<rect key="frame" x="352" y="346" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="IRV-qN-sRj" userLabel="ephemeralTime">
<rect key="frame" x="285" y="346" width="65" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="9"/>
<color key="textColor" red="1" green="0.36862745099999999" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VAJ-tE-fsa">
<rect key="frame" x="0.0" y="10" width="382" height="347"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -185,22 +201,6 @@
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_read.png" translatesAutoresizingMaskIntoConstraints="NO" id="LPj-VT-0fH" userLabel="imdmIcon">
<rect key="frame" x="372" y="337" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed"/>
</imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ephemeral_messages_color_A.png" id="7JB-ZL-0lZ" userLabel="ephemeralIcon">
<rect key="frame" x="351" y="336" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="IRV-qN-sRj" userLabel="ephemeralTime">
<rect key="frame" x="282" y="336" width="65" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="9"/>
<color key="textColor" red="1" green="0.36862745099999999" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view tag="28021" contentMode="scaleToFill" id="bhq-9n-zYF" userLabel="voiceRecording">
<rect key="frame" x="7" y="252" width="351" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
@ -261,7 +261,7 @@
<image name="color_M.png" width="2" height="2"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="linphone_logo.png" width="41.599998474121094" height="42.400001525878906"/>
<image name="menu_reply_default.png" width="25" height="25"/>
<image name="menu_reply_default.png" width="60" height="60"/>
<image name="vr_play.png" width="200" height="200"/>
<image name="vr_wave.png" width="1078" height="90"/>
</resources>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -59,14 +59,14 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView>
<textView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Lore ipsum..." translatesAutoresizingMaskIntoConstraints="NO" id="CYa-If-oB4" userLabel="messageText" customClass="UITextViewNoDefine">
<rect key="frame" x="0.0" y="0.0" width="126" height="40"/>
<rect key="frame" x="4" y="0.0" width="118" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_read.png" translatesAutoresizingMaskIntoConstraints="NO" id="Nod-GX-0kg" userLabel="imdmIcon">
<rect key="frame" x="133" y="30" width="10" height="10"/>
<rect key="frame" x="133" y="28" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="00:00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GDJ-O8-m6J" userLabel="ephemeralTime">
@ -93,6 +93,6 @@
<image name="chat_read.png" width="20" height="20"/>
<image name="color_A.png" width="2" height="2"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="menu_reply_default.png" width="25" height="25"/>
<image name="menu_reply_default.png" width="60" height="60"/>
</resources>
</document>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -66,15 +66,11 @@
<nil key="highlightedColor"/>
</label>
<view hidden="YES" autoresizesSubviews="NO" userInteractionEnabled="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7DE-KJ-9Q3" userLabel="unreadCountView" customClass="UIBouncingView">
<rect key="frame" x="338" y="12" width="21" height="21"/>
<rect key="frame" x="338" y="12" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="8" contentMode="scaleAspectFit" fixedFrame="YES" image="chat_list_indicator.png" translatesAutoresizingMaskIntoConstraints="NO" id="NXj-A8-YLh" userLabel="unreadCountImage">
<rect key="frame" x="0.0" y="0.0" width="21" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="99" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="7" translatesAutoresizingMaskIntoConstraints="NO" id="ZXq-Do-7Ua" userLabel="unreadCountLabel">
<rect key="frame" x="0.0" y="0.0" width="21" height="21"/>
<rect key="frame" x="0.0" y="0.0" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" none="YES"/>
@ -88,11 +84,11 @@
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="forward_message_default.png" id="rbY-QS-6QH" userLabel="transferIcon">
<rect key="frame" x="338" y="33" width="21" height="21"/>
<rect key="frame" x="338" y="33" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ephemeral_messages_color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="q18-yi-ol3">
<rect key="frame" x="338" y="54" width="21" height="21"/>
<rect key="frame" x="338" y="54" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
</subviews>
@ -103,7 +99,6 @@
</objects>
<resources>
<image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/>
<image name="chat_list_indicator.png" width="28" height="28"/>
<image name="chat_read.png" width="20" height="20"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="forward_message_default.png" width="187" height="148"/>

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -23,18 +24,18 @@
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="Z2U-vm-azg" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="0.0" y="5" width="44" height="28"/>
<rect key="frame" x="0.0" y="8" width="44" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f3q-pd-EF5" userLabel="displayNameLabel">
<rect key="frame" x="43" y="11" width="185" height="16"/>
<rect key="frame" x="43" y="0.0" width="185" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" text="Today - 18h42" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hEV-7I-B0v" userLabel="contactDateLabel">
<rect key="frame" x="200" y="13" width="159" height="12"/>
<rect key="frame" x="229" y="16" width="130" height="12"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
@ -42,12 +43,15 @@
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</tableViewCellContentView>
<point key="canvasLocation" x="61.5" y="52"/>
<point key="canvasLocation" x="60" y="51.27436281859071"/>
</tableViewCell>
</objects>
<resources>
<image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -61,7 +61,7 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="890.39999999999998" y="192.50374812593705"/>

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="UIChatCreateCollectionViewCell">
<rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fHV-en-AZD" userLabel="displayNameLabel">
<rect key="frame" x="14" y="18" width="92" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" fixedFrame="YES" image="conference_delete.png" translatesAutoresizingMaskIntoConstraints="NO" id="yfP-hQ-SXb" userLabel="selectedImage">
<rect key="frame" x="1" y="23" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
</subviews>
</view>
<size key="customSize" width="170" height="45"/>
<connections>
<outlet property="nameLabel" destination="fHV-en-AZD" id="gOU-sp-v0V"/>
</connections>
<point key="canvasLocation" x="2" y="86"/>
</collectionViewCell>
</objects>
<resources>
<image name="conference_delete.png" width="17.600000381469727" height="17.600000381469727"/>
</resources>
</document>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -13,6 +13,7 @@
<connections>
<outlet property="contentCollection" destination="ZgY-Ye-8DD" id="hlD-Fw-g1z"/>
<outlet property="dismissButton" destination="KrT-j6-YOy" id="K5m-Mw-Cjx"/>
<outlet property="icsIcon" destination="gkH-jk-DYU" id="Xkl-Ip-skO"/>
<outlet property="leftBar" destination="Iov-AL-8Xz" id="eKb-4B-unZ"/>
<outlet property="originalMessageGone" destination="B26-sw-o4w" id="Gwh-dh-GRN"/>
<outlet property="senderName" destination="uuW-tW-1Sj" id="Yao-6A-SBh"/>
@ -68,6 +69,10 @@
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
<state key="normal" image="reply_cancel.png"/>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="voip_meeting_schedule.png" translatesAutoresizingMaskIntoConstraints="NO" id="gkH-jk-DYU">
<rect key="frame" x="16" y="23" width="35" height="35"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
@ -77,6 +82,7 @@
</objects>
<resources>
<image name="reply_cancel.png" width="60" height="60"/>
<image name="voip_meeting_schedule.png" width="20" height="20"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -11,11 +11,14 @@
<connections>
<outlet property="authButton" destination="lWW-wB-FMR" id="C7W-JM-WFQ"/>
<outlet property="authView" destination="CCn-Oz-I0M" id="fSM-6k-paN"/>
<outlet property="backgroundColor" destination="cqN-1f-6SE" id="gjg-LB-xLT"/>
<outlet property="cancelButton" destination="B1K-CB-3of" id="KKi-Xc-ldA"/>
<outlet property="confirmationButton" destination="SbQ-re-fGQ" id="yiv-a9-o8E"/>
<outlet property="firstView" destination="ef9-Iu-Bcb" id="hKx-op-r7Z"/>
<outlet property="forwardImage" destination="1Wh-Yi-cUe" id="YQq-bt-pk1"/>
<outlet property="groupCallImage" destination="SVn-4k-9yc" id="sAP-8V-ttn"/>
<outlet property="securityImage" destination="bbo-g3-bGy" id="qZa-li-yrl"/>
<outlet property="subscribeLabel" destination="Xbl-Qs-GaE" id="Qnf-pA-nL0"/>
<outlet property="titleLabel" destination="jLz-g1-cTe" id="qaj-OB-2r1"/>
<outlet property="view" destination="2Vb-Xy-rci" id="nNw-EJ-AY3"/>
</connections>
@ -25,27 +28,32 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" alpha="0.89999999999999991" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_C.png" translatesAutoresizingMaskIntoConstraints="NO" id="cqN-1f-6SE" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="377" height="667"/>
<view contentMode="scaleToFill" id="ef9-Iu-Bcb" userLabel="firstView">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2BQ-o9-xv2">
<rect key="frame" x="28" y="139" width="320" height="365"/>
</view>
<view contentMode="scaleToFill" id="2BQ-o9-xv2">
<rect key="frame" x="25" y="114" width="325" height="440"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_C.png" translatesAutoresizingMaskIntoConstraints="NO" id="cqN-1f-6SE" userLabel="backgroundColor">
<rect key="frame" x="-17" y="-17" width="360" height="474"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.26666668059999998" green="0.26666668059999998" blue="0.26666668059999998" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
</imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" image="security_2_indicator.png" translatesAutoresizingMaskIntoConstraints="NO" id="bbo-g3-bGy" userLabel="securityImage">
<rect key="frame" x="130" y="0.0" width="56" height="68"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<rect key="frame" x="130" y="15" width="64" height="68"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Are you sure you want to delete all your selection?" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="jLz-g1-cTe" userLabel="titleLabel">
<rect key="frame" x="-10" y="15" width="336" height="279"/>
<rect key="frame" x="-8" y="15" width="347" height="350"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="21"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="B1K-CB-3of" userLabel="cancelButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="8" y="308" width="139" height="42"/>
<rect key="frame" x="16" y="383" width="139" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" title="CANCEL" backgroundImage="color_H.png">
<color key="titleColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -57,11 +65,11 @@
</connections>
</button>
<view hidden="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="CCn-Oz-I0M" userLabel="authView">
<rect key="frame" x="61" y="273" width="240" height="27"/>
<rect key="frame" x="65" y="345" width="243" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lWW-wB-FMR" userLabel="authButton">
<rect key="frame" x="26" y="2" width="17" height="22"/>
<rect key="frame" x="24" y="0.0" width="22" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" image="checkbox_unchecked.png"/>
<state key="selected" image="checkbox_checked.png"/>
@ -70,7 +78,7 @@
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Do not show again" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dQL-Sf-slc">
<rect key="frame" x="55" y="2" width="176" height="21"/>
<rect key="frame" x="57" y="10" width="173" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -79,7 +87,7 @@
</subviews>
</view>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SbQ-re-fGQ" userLabel="confirmationButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="169" y="308" width="143" height="42"/>
<rect key="frame" x="177" y="383" width="136" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" title="DELETE" backgroundImage="color_I.png">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -91,20 +99,34 @@
</connections>
</button>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="forward_message_default.png" translatesAutoresizingMaskIntoConstraints="NO" id="1Wh-Yi-cUe">
<rect key="frame" x="89" y="50" width="138" height="54"/>
<rect key="frame" x="100" y="50" width="136" height="54"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="voip_conference_new.png" translatesAutoresizingMaskIntoConstraints="NO" id="SVn-4k-9yc">
<rect key="frame" x="89" y="50" width="138" height="54"/>
<rect key="frame" x="100" y="50" width="136" height="54"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<label hidden="YES" opaque="NO" tag="13" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="https://subscribe.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Xbl-Qs-GaE" userLabel="subscribeLabel">
<rect key="frame" x="-42" y="291" width="414" height="29"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.98766469960000003" green="0.27512490750000002" blue="0.029739789660000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<connections>
<outletCollection property="gestureRecognizers" destination="tfG-O1-yfD" appends="YES" id="Jcg-KH-U5B"/>
</connections>
</label>
</subviews>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="simulatedStatusBarMetrics"/>
<point key="canvasLocation" x="871.20000000000005" y="261.31934032983509"/>
<point key="canvasLocation" x="548" y="34"/>
</view>
<tapGestureRecognizer id="tfG-O1-yfD" userLabel="onSubscribeTap">
<connections>
<action selector="onSubscribeTap:" destination="-1" id="Zq2-mP-ccM"/>
</connections>
</tapGestureRecognizer>
</objects>
<resources>
<image name="checkbox_checked.png" width="27.200000762939453" height="27.200000762939453"/>

View file

@ -35,6 +35,7 @@
@property(weak, nonatomic) IBOutlet UIView *outcallView;
- (IBAction)onSecurityClick:(id)sender;
- (IBAction)onQualityClick:(id)sender;
- (IBAction)onSideMenuClick:(id)sender;
- (IBAction)onRegistrationStateClick:(id)sender;
+ (UIImage *)imageForState:(LinphoneRegistrationState)state;

View file

@ -21,6 +21,7 @@
#import "LinphoneManager.h"
#import "PhoneMainView.h"
#import <UserNotifications/UserNotifications.h>
#import "linphoneapp-Swift.h"
@implementation StatusBarView {
@ -193,11 +194,13 @@
message = NSLocalizedString(@"Fetching remote configuration", nil);
} else if (account == NULL) {
state = LinphoneRegistrationNone;
if (linphone_core_get_account_list(LC) != NULL) {
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
if (accounts != NULL) {
message = NSLocalizedString(@"No default account", nil);
} else {
message = NSLocalizedString(@"No account configured", nil);
}
bctbx_free(accounts);
} else {
state = linphone_account_get_state(account);
@ -329,14 +332,9 @@
correspondantCode = [code substringToIndex:2];
myCode = [code substringFromIndex:2];
}
NSString *message =
[NSString stringWithFormat:NSLocalizedString(@"\nConfirmation security\n\n"
@"Say: %@\n"
@"Confirm that your interlocutor\n"
@"says: %@",
nil),
myCode.uppercaseString, correspondantCode.uppercaseString];
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"\nCommunication security:\n\nTo raise the security level, you can check the following codes with your correspondent.\n\nSay: %1$@\n\nYour correspondent must say: %2$@", nil),
myCode.uppercaseString, correspondantCode.uppercaseString];
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive &&
floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
@ -368,14 +366,14 @@
UIFont *baseFont = [UIFont systemFontOfSize:21.0];
[attrString addAttribute:NSFontAttributeName value:baseFont range:NSMakeRange(0, length)];
UIFont *boldFont = [UIFont boldSystemFontOfSize:23.0];
[attrString addAttribute:NSFontAttributeName value:boldFont range:[message rangeOfString:@"Confirmation security"]];
[attrString addAttribute:NSFontAttributeName value:boldFont range:[message rangeOfString:@"Communication security"]];
UIColor *color = [UIColor colorWithRed:(150 / 255.0) green:(193 / 255.0) blue:(31 / 255.0) alpha:1.0];
[attrString addAttribute:NSForegroundColorAttributeName value:color range:[message rangeOfString:myCode.uppercaseString]];
[attrString addAttribute:NSForegroundColorAttributeName value:color range:[message rangeOfString:correspondantCode.uppercaseString]];
securityDialog = [UIConfirmationDialog ShowWithAttributedMessage:attrString
cancelMessage:NSLocalizedString(@"DENY", nil)
confirmMessage:NSLocalizedString(@"ACCEPT", nil)
cancelMessage:NSLocalizedString(@"Later", nil)
confirmMessage:NSLocalizedString(@"Correct", nil)
onCancelClick:^() {
if (linphone_core_get_current_call(LC) == call) {
linphone_call_set_authentication_token_verified(call, NO);
@ -393,6 +391,7 @@
securityDialog.securityImage.hidden = FALSE;
[securityDialog setSpecialColor];
[securityDialog setWhiteCancel];
}
}
}
@ -400,6 +399,10 @@
}
}
- (IBAction)onQualityClick:(id)sender {
[ControlsViewModelBridge toggleStatsVisibility];
}
- (IBAction)onSideMenuClick:(id)sender {
UICompositeView *cvc = PhoneMainView.instance.mainViewController;
[cvc hideSideMenu:(cvc.sideMenuView.frame.origin.x == 0)];
@ -408,10 +411,15 @@
- (IBAction)onRegistrationStateClick:(id)sender {
if (linphone_core_get_default_account(LC)) {
linphone_core_refresh_registers(LC);
} else if (linphone_core_get_account_list(LC)) {
[PhoneMainView.instance changeCurrentView:SettingsView.compositeViewDescription];
} else {
[PhoneMainView.instance changeCurrentView:AssistantView.compositeViewDescription];
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
if (accounts) {
[PhoneMainView.instance changeCurrentView:SettingsView.compositeViewDescription];
} else {
[PhoneMainView.instance changeCurrentView:AssistantView.compositeViewDescription];
}
bctbx_free(accounts);
}
}

View file

@ -48,7 +48,7 @@
}
- (IBAction)onBackToCallClick:(id)sender {
[PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription];
[PhoneMainView.instance popToView:[CallsViewModelBridge callViewToDisplay]];
}
@end

View file

@ -192,14 +192,20 @@
_voiceRecordingFile = nil;
LinphoneContent *voiceContent = [UIChatBubbleTextCell voiceContent:self.message];
if (voiceContent) {
_voiceRecordingFile = [NSString stringWithUTF8String:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] ? linphone_content_get_plain_file_path(voiceContent) : linphone_content_get_file_path(voiceContent)];
if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId])
const char *fileName = ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] ? linphone_content_get_plain_file_path(voiceContent) : linphone_content_get_file_path(voiceContent));
if (fileName == nil) {
linphone_content_set_file_path(voiceContent, [[LinphoneManager imagesDirectory] stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]].UTF8String);
linphone_chat_message_download_content(self.message, voiceContent);
}
_voiceRecordingFile = fileName ? [NSString stringWithUTF8String:fileName] : nil;
if (fileName && [VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) {
[encrptedFilePaths setValue:_voiceRecordingFile forKey:[NSString stringWithUTF8String:linphone_content_get_name(voiceContent)]];
}
_vrTimerLabel.text = [self formattedDuration:linphone_content_get_file_duration(voiceContent)/1000];
_vrWaveMaskPlayback.frame = CGRectZero;
_vrWaveMaskPlayback.backgroundColor = linphone_chat_message_is_outgoing(self.message) ? UIColor.orangeColor : UIColor.grayColor;
}
const bctbx_list_t *contents = linphone_chat_message_get_contents(self.message);
size_t contentCount = bctbx_list_size(contents);
@ -234,7 +240,8 @@
if (strcmp(cPath, "") != 0) {
NSString *tempPath = [NSString stringWithUTF8String:cPath];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
filePath = [paths objectAtIndex:0];
filePath = [NSString stringWithFormat:@"%@/%s", [paths objectAtIndex:0],linphone_chat_message_get_message_id(super.message)];
[FileUtil ensureDirectoryExistsWithPath:filePath];
filePath = [filePath stringByAppendingPathComponent:name];
[[NSFileManager defaultManager] moveItemAtPath:tempPath toPath:filePath error:nil];
}
@ -300,7 +307,8 @@
if (strcmp(cPath, "") != 0) {
NSString *tempPath = [NSString stringWithUTF8String:cPath];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
filePath = [paths objectAtIndex:0];
filePath = [NSString stringWithFormat:@"%@/%s", [paths objectAtIndex:0],linphone_chat_message_get_message_id(super.message)];
[FileUtil ensureDirectoryExistsWithPath:filePath];
filePath = [filePath stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] moveItemAtPath:tempPath toPath:filePath error:nil];
}

View file

@ -46,10 +46,8 @@
[self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)];
[self addSubview:sub];
self.icsBubbleView = [[ICSBubbleView alloc] init];
self.icsBubbleView.frame = CGRectMake(_messageText.frame.origin.x, _messageText.frame.origin.y+25, CONFERENCE_INVITATION_WIDTH-80, CONFERENCE_INVITATION_HEIGHT-20);
[self.innerView addSubview:self.icsBubbleView];
[(ICSBubbleView*)self.icsBubbleView setLayoutConstraintsWithView:self.backgroundColorImage];
}
}
@ -121,16 +119,21 @@
}
- (void)setChatMessageForCbs:(LinphoneChatMessage *)amessage {
if (!amessage || amessage == _message) {
if (amessage == _message) {
return;
}
if (_message){
linphone_chat_message_unref(_message);
}
_message = amessage;
linphone_chat_message_set_user_data(_message, (void *)CFBridgingRetain(self));
LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(_message);
linphone_chat_message_cbs_set_msg_state_changed(cbs, message_status);
linphone_chat_message_cbs_set_participant_imdn_state_changed(cbs, participant_imdn_status);
linphone_chat_message_cbs_set_user_data(cbs, (void *)_event);
if (amessage){
linphone_chat_message_ref(amessage);
linphone_chat_message_set_user_data(_message, (void *)CFBridgingRetain(self));
LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(_message);
linphone_chat_message_cbs_set_msg_state_changed(cbs, message_status);
linphone_chat_message_cbs_set_participant_imdn_state_changed(cbs, participant_imdn_status);
linphone_chat_message_cbs_set_user_data(cbs, (void *)_event);
}
}
+ (NSString *)TextMessageForChat:(LinphoneChatMessage *)message {
@ -168,7 +171,7 @@
return;
}
if (_messageText && ![LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message]) {
if (_messageText && ![LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message] && ![ICSBubbleView isConferenceInvitationMessageWithCmessage:self.message]) {
[_messageText setHidden:FALSE];
/* We need to use an attributed string here so that data detector don't mess
* with the text style. See http://stackoverflow.com/a/20669356 */
@ -346,8 +349,10 @@ static void message_status(LinphoneChatMessage *msg, LinphoneChatMessageState st
}
static void participant_imdn_status(LinphoneChatMessage* msg, const LinphoneParticipantImdnState *state) {
ChatConversationImdnView *imdnView = VIEW(ChatConversationImdnView);
[imdnView updateImdnList];
dispatch_async(dispatch_get_main_queue(), ^{
ChatConversationImdnView *imdnView = VIEW(ChatConversationImdnView);
[imdnView updateImdnList];
});
}
- (void)displayImdmStatus:(LinphoneChatMessageState)state {
@ -399,7 +404,7 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
return cached;
}
CGSize size = [self ViewHeightForMessageText:chat withWidth:width textForImdn:nil];
CGSize size = [self ViewHeightForMessageText:chat withWidth:width textForImdn:nil];
size.height += linphone_chat_message_is_forward(chat) || linphone_chat_message_is_reply(chat) ? REPLY_OR_FORWARD_TAG_HEIGHT : 0;
size.height += linphone_chat_message_is_reply(chat) ? REPLY_CHAT_BUBBLE_HEIGHT+5 : 0;
@ -491,7 +496,7 @@ 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);
return CGSizeMake(CONFERENCE_INVITATION_WIDTH, CONFERENCE_INVITATION_HEIGHT+[ICSBubbleView getDescriptionHeightFromContentWithCmessage:chat]);
}
NSString *messageText = [UIChatBubbleTextCell TextMessageForChat:chat];
@ -791,6 +796,8 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
_replyTransferIcon.hidden = ! linphone_chat_message_is_reply(_message) && !linphone_chat_message_is_forward(_message);
_replyTransferLabel.hidden = ! linphone_chat_message_is_reply(_message) && !linphone_chat_message_is_forward(_message);
[(ICSBubbleView*)self.icsBubbleView updateTopLayoutConstraintsWithView:self.backgroundColorImage replyOrForward:linphone_chat_message_is_reply(_message)||linphone_chat_message_is_forward(_message)];
if (linphone_chat_message_is_reply(_message)) {
CGRect replyFrame = CGRectMake(10, _replyTransferLabel.frame.origin.y+_replyTransferLabel.frame.size.height+5,MAX(self.contactDateLabel.frame.size.width-20,180), REPLY_CHAT_BUBBLE_HEIGHT);
@ -848,10 +855,13 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
-(void) buildActions {
LinphoneChatMessage *message = self.message;
LinphoneEventLog *event = self.event;
_messageActionsTitles = [[NSMutableArray alloc] init];
_messageActionsBlocks = [[NSMutableArray alloc] init];
_messageActionsIcons = [[NSMutableArray alloc] init];
[VIEW(ChatConversationView).messageField resignFirstResponder];
UIChatBubbleTextCell *thiz = self;
LinphoneChatMessageState state = linphone_chat_message_get_state(self.message);
@ -869,7 +879,7 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
}
if (linphone_chat_message_get_utf8_text(message)) {
if (linphone_chat_message_get_utf8_text(message) && ![ICSBubbleView isConferenceInvitationMessageWithCmessage:message]) {
[_messageActionsTitles addObject:NSLocalizedString(@"Copy text", nil)];
[_messageActionsIcons addObject:@"menu_copy_text_default"];
[_messageActionsBlocks addObject:^{
@ -896,16 +906,43 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
[VIEW(ChatConversationView) initiateReplyViewForMessage:message];
}];
if (linphone_chat_message_is_outgoing(self.message) || linphone_chat_room_get_nb_participants(linphone_chat_message_get_chat_room(self.message)) > 1) {
LinphoneChatRoom *chatroom = linphone_chat_message_get_chat_room(self.message);
if (linphone_chat_room_get_nb_participants(chatroom) > 1) {
[_messageActionsTitles addObject:NSLocalizedString(@"Infos", nil)];
[_messageActionsIcons addObject:@"menu_info"];
[_messageActionsBlocks addObject:^{
[thiz dismissPopup];
ChatConversationImdnView *view = VIEW(ChatConversationImdnView);
view.msg = message;
view.event = event;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}];
}
if (!linphone_chat_message_is_outgoing(self.message)
&& [FastAddressBook getContactWithAddress:linphone_chat_message_get_from_address(self.message)] == nil
&& !(linphone_chat_room_get_capabilities(chatroom) & LinphoneChatRoomCapabilitiesOneToOne) ) {
LinphoneAddress *fromAddress = linphone_address_clone(linphone_chat_message_get_from_address(self.message));
[_messageActionsTitles addObject:NSLocalizedString(@"Add to contact", nil)];
[_messageActionsIcons addObject:@"contact_add_default"];
[_messageActionsBlocks addObject:^{
[thiz dismissPopup];
linphone_address_clean(fromAddress);
char *lAddress = linphone_address_as_string_uri_only(fromAddress);
if (lAddress != NULL) {
NSString *normSip = [NSString stringWithUTF8String:lAddress];
normSip = [normSip hasPrefix:@"sip:"] ? [normSip substringFromIndex:4] : normSip;
normSip = [normSip hasPrefix:@"sips:"] ? [normSip substringFromIndex:5] : normSip;
[ContactSelection setAddAddress:normSip];
[ContactSelection setSelectionMode:ContactSelectionModeEdit];
[ContactSelection enableSipFilter:FALSE];
[PhoneMainView.instance changeCurrentView:ContactsListView.compositeViewDescription];
ms_free(lAddress);
}
linphone_address_unref(fromAddress);
}];
}
[_messageActionsTitles addObject:NSLocalizedString(@"Delete", nil)];
[_messageActionsIcons addObject:@"menu_delete"];
@ -930,7 +967,7 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
int cellHeight = 45;
int numberOfItems = (int) _messageActionsTitles.count;
CGRect screenRect = UIScreen.mainScreen.bounds;
int menuHeight = numberOfItems * cellHeight + 15;
int menuHeight = numberOfItems * cellHeight;
CGRect frame = CGRectMake(
linphone_chat_message_is_outgoing(self.message) ? screenRect.size.width - width - 10 : 10,
@ -942,7 +979,8 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
_popupMenu.scrollEnabled = false;
_popupMenu.dataSource = self;
_popupMenu.delegate = self;
_popupMenu.separatorStyle = UITableViewCellSeparatorStyleNone;
_popupMenu.layer.masksToBounds = false;
_popupMenu.layer.shadowColor = [UIColor darkGrayColor].CGColor;

View file

@ -21,6 +21,7 @@
#import "PhoneMainView.h"
#import "LinphoneManager.h"
#import "Utils.h"
#import "linphoneapp-Swift.h"
@implementation UIChatCell
@ -38,6 +39,10 @@
[self addSubview:sub];
}
[_imdmIcon setHidden:TRUE];
_unreadCountView.backgroundColor = VoipTheme.primary_color;
_unreadCountView.layer.cornerRadius = 10;
_unreadCountView.clipsToBounds = true;
_unreadCountLabel.textAlignment = NSTextAlignmentCenter;
return self;
}
@ -77,6 +82,7 @@
} else {
_addressLabel.text = [NSString stringWithUTF8String:LINPHONE_DUMMY_SUBJECT];
}
bctbx_list_free(participants);
} else {
const char *subject = linphone_chat_room_get_subject(chatRoom);
_addressLabel.text = [NSString stringWithUTF8String:subject ?: LINPHONE_DUMMY_SUBJECT];
@ -132,7 +138,8 @@
_chatContentLabel.frame = newFrame;
}
} else {
NSString *text = [[FastAddressBook displayNameForAddress:linphone_chat_message_get_from_address(last_msg)]
NSString *conferenceInfo = [ICSBubbleView getConferenceSummaryWithCmessage:last_msg];
NSString *text = conferenceInfo != nil ? conferenceInfo : [[FastAddressBook displayNameForAddress:linphone_chat_message_get_from_address(last_msg)]
stringByAppendingFormat:@" : %@", [UIChatBubbleTextCell TextMessageForChat:last_msg]];
// shorten long messages
/*if ([text length] > 50)

View file

@ -21,9 +21,8 @@
#import "ChatConversationCreateView.h"
@interface UIChatCreateCollectionViewCell : UICollectionViewCell
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property UILabel *nameLabel;
@property (strong, nonatomic) ChatConversationCreateView *controller;
@property (strong, nonatomic) NSString *uri;
- (id)initWithName:(NSString *)identifier;
- (void)onDelete;
@end

View file

@ -18,27 +18,31 @@
*/
#import "UIChatCreateCollectionViewCell.h"
#import "linphoneapp-Swift.h"
@implementation UIChatCreateCollectionViewCell
- (void)awakeFromNib {
[super awakeFromNib];
}
- (id)initWithName:(NSString *)identifier {
if (self != nil) {
NSArray *arrayOfViews =
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
if ([arrayOfViews count] >= 1) {
UIChatCreateCollectionViewCell *sub = ((UIChatCreateCollectionViewCell *)[arrayOfViews objectAtIndex:0]);
[self addSubview:sub];
_nameLabel = sub.nameLabel;
}
}
[_nameLabel setText:identifier];
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
self.contentView.translatesAutoresizingMaskIntoConstraints = false;
[SnapkitBridge matchParentDimensionsWithView:self.contentView topInset:10];
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.nameLabel.numberOfLines = 1;
[self.contentView addSubview:self.nameLabel];
[SnapkitBridge matchParentDimensionsWithView:self.nameLabel leftInset:20];
[SnapkitBridge heightWithView:self heiht:50];
UIImageView *image = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"conference_delete"]];
image.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:image];
[SnapkitBridge squareWithView:image size:15];
[SnapkitBridge alignParentLeftWithView:image];
[SnapkitBridge centerYWithView:image];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onDelete)];
tap.numberOfTouchesRequired = 1;
[self addGestureRecognizer:tap];
[image addGestureRecognizer:tap];
image.userInteractionEnabled = true;
return self;
}
@ -60,4 +64,6 @@
[_controller.tableController.tableView reloadData];
_controller.nextButton.enabled = (_controller.tableController.contactsGroup.count > 0) || _controller.isForEditing;
}
@end

View file

@ -33,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (weak, nonatomic) IBOutlet UICollectionView *contentCollection;
@property NSArray *dataContent;
@property (weak, nonatomic) IBOutlet UILabel *originalMessageGone;
@property (weak, nonatomic) IBOutlet UIImageView *icsIcon;
-(void) configureForMessage:(LinphoneChatMessage *)message withDimissBlock:(void (^)(void))dismissBlock hideDismiss:(BOOL)hideDismiss withClickBlock:(void (^)(void))clickBlock;
@end

View file

@ -40,6 +40,7 @@
-(void) viewDidLoad {
_contentCollection.dataSource = self;
[_icsIcon setImageNamed:@"voip_meeting_schedule" tintColor:VoipTheme.voip_dark_gray];
[_contentCollection registerClass:UICollectionViewCell.class forCellWithReuseIdentifier:@"dataContent"];
}
@ -51,6 +52,7 @@
_contentCollection.hidden = true;
_senderName.hidden = true;
_originalMessageGone.hidden = false;
_icsIcon.hidden = true;
return;
}
if (hideDismiss) {
@ -60,9 +62,12 @@
_originalMessageGone.hidden = true;
self.message = message;
self.dataContent = [self loadDataContent];
BOOL isIcal = [ICSBubbleView isConferenceInvitationMessageWithCmessage:message];
_icsIcon.hidden = !isIcal;
NSString *sender = [FastAddressBook displayNameForAddress:linphone_chat_message_get_from_address(message)];
_senderName.text = sender;
const char * text = linphone_chat_message_get_text_content(message);
const char * text = isIcal ? [ICSBubbleView getSubjectFromContentWithCmessage:message].UTF8String : linphone_chat_message_get_text_content(message);
if (text && strlen(text) == 0)
text = nil;
_textContent.text = text ? [NSString stringWithUTF8String:text] : @"";

View file

@ -91,8 +91,8 @@
#pragma mark -
- (void)accessoryForCell:(UITableViewCell *)cell atPath:(NSIndexPath *)indexPath {
cell.selectionStyle = UITableViewCellSelectionStyleGray;
if ([self isEditing]) {
cell.selectionStyle = UITableViewCellSelectionStyleGray;
UIButton *checkBoxButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *image = nil;
if ([_selectedItems containsObject:indexPath]) {

View file

@ -84,6 +84,7 @@
- (UIViewController *)getCurrentViewController;
- (UIInterfaceOrientation)currentOrientation;
- (void)clearCache:(NSArray *)exclude;
- (void)removeEntryFromCache:(NSString *)key;
- (IBAction)onRightSwipe:(id)sender;

View file

@ -305,6 +305,9 @@
return nil;
}
- (void)removeEntryFromCache:(NSString *)key {
[viewControllerCache removeObjectForKey:key];
}
- (void)clearCache:(NSArray *)exclude {
@ -313,7 +316,7 @@
bool remove = true;
/*ImagePickerView can be used as popover and we do NOT want to free it*/;
if ([key isEqualToString:ImagePickerView.compositeViewDescription.name] || [key isEqualToString:ActiveCallOrConferenceView.compositeViewDescription.name]) {
if ([key isEqualToString:ImagePickerView.compositeViewDescription.name] || [key isEqualToString:SingleCallView.compositeViewDescription.name] || [key isEqualToString:ConferenceCallView.compositeViewDescription.name]) {
remove = false;
} else if (exclude != nil) {
for (UICompositeViewDescription *description in exclude) {
@ -336,6 +339,9 @@
}
- (IBAction)onRightSwipe:(id)sender {
if (linphone_core_get_calls_nb(LC) > 0) {
return;
}
[self hideSideMenu:NO];
}

View file

@ -17,11 +17,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "UIRoundBorderedButton.h"
#import <UIKit/UIKit.h>
#import "UICompositeView.h"
#import "UIRoundBorderedButton.h"
typedef void (^UIConfirmationBlock)(void);
@interface UIConfirmationDialog : UIViewController {
@interface UIConfirmationDialog : UIViewController <UICompositeViewDelegate, UIGestureRecognizerDelegate, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource>{
UIConfirmationBlock onCancelCb;
UIConfirmationBlock onConfirmCb;
}
@ -49,13 +52,18 @@ typedef void (^UIConfirmationBlock)(void);
@property (weak, nonatomic) IBOutlet UIImageView *groupCallImage;
@property(weak, nonatomic) IBOutlet UIRoundBorderedButton *confirmationButton;
@property (weak, nonatomic) IBOutlet UIView *authView;
@property (weak, nonatomic) IBOutlet UIImageView *backgroundColor;
@property(weak, nonatomic) IBOutlet UILabel *titleLabel;
@property(weak, nonatomic) IBOutlet UIView *firstView;
@property (weak, nonatomic) IBOutlet UIButton *authButton;
@property(weak, nonatomic) IBOutlet UILabel *subscribeLabel;
- (void)setSpecialColor;
-(void) setWhiteCancel;
- (IBAction)onCancelClick:(id)sender;
- (IBAction)onConfirmationClick:(id)sender;
- (IBAction)onAuthClick:(id)sender;
- (IBAction)onSubscribeTap:(id)sender;
- (void)dismiss;
@end

View file

@ -19,7 +19,7 @@
#import "UIConfirmationDialog.h"
#import "PhoneMainView.h"
#import "linphoneapp-Swift.h""
#import "linphoneapp-Swift.h"
@implementation UIConfirmationDialog
+ (UIConfirmationDialog *)initDialog:(NSString *)cancel
@ -33,6 +33,8 @@
dialog.view.frame = PhoneMainView.instance.mainViewController.view.frame;
[controller.view addSubview:dialog.view];
[controller addChildViewController:dialog];
dialog.backgroundColor.layer.cornerRadius = 10;
dialog.backgroundColor.layer.masksToBounds = true;
dialog->onCancelCb = onCancel;
dialog->onConfirmCb = onConfirm;
@ -48,9 +50,21 @@
[[UIColor colorWithPatternImage:[UIImage imageNamed:@"color_A.png"]] CGColor];
dialog.cancelButton.layer.borderColor =
[[UIColor colorWithPatternImage:[UIImage imageNamed:@"color_F.png"]] CGColor];
if (linphone_core_get_post_quantum_available()) {
[dialog.securityImage setImage:[UIImage imageNamed:@"post_quantum_secure.png"]];
}
return dialog;
}
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(onCancelClick:)];
tapGestureRecognizer.delegate = self;
[self.firstView addGestureRecognizer:tapGestureRecognizer];
}
+ (UIConfirmationDialog *)ShowWithMessage:(NSString *)message
cancelMessage:(NSString *)cancel
confirmMessage:(NSString *)confirm
@ -131,4 +145,12 @@
- (void)dismiss {
[self onCancelClick:nil];
}
- (IBAction)onSubscribeTap:(id)sender {
UIGestureRecognizer *gest = sender;
NSString *url = ((UILabel *)gest.view).text;
if (![UIApplication.sharedApplication openURL:[NSURL URLWithString:url]]) {
LOGE(@"Failed to open %@, invalid URL", url);
}
}
@end

View file

@ -50,28 +50,34 @@
normAddr = linphone_account_normalize_phone_number(account,
_addressLabel.text.UTF8String);
}
LinphoneAddress *addr = linphone_core_interpret_url(LC, normAddr);
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, normAddr, true);
_chatButton.enabled = _callButton.enabled = _encryptedChatButton.enabled = (addr != NULL);
_chatButton.accessibilityLabel =
[NSString stringWithFormat:NSLocalizedString(@"Chat with %@", nil), _addressLabel.text];
_callButton.accessibilityLabel = [NSString stringWithFormat:NSLocalizedString(@"Call %@", nil), _addressLabel.text];
// Test presence
Contact *contact;
contact = addr ? [FastAddressBook getContactWithAddress:(addr)] : NULL;
Contact *contact = addr ? [FastAddressBook getContactWithAddress:(addr)] : NULL;
LinphoneFriend *contactFriend = NULL;
if (contact && contact.friend) {
contactFriend = contact.friend;
} else if (addr) {
contactFriend = linphone_core_find_friend(LC, addr);
}
ContactDetailsView *contactDetailsView = VIEW(ContactDetailsView);
_linphoneImage.hidden = TRUE;
if (contact) {
const LinphonePresenceModel *model = contact.friend ? linphone_friend_get_presence_model_for_uri_or_tel(contact.friend, _addressLabel.text.UTF8String) : NULL;
if (contactFriend) {
const LinphonePresenceModel *model = contactFriend ? linphone_friend_get_presence_model_for_uri_or_tel(contactFriend, _addressLabel.text.UTF8String) : NULL;
self.linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] ||
!((model && linphone_presence_model_get_basic_status(model) == LinphonePresenceBasicStatusOpen) ||
(account && !linphone_account_is_phone_number(account,
_addressLabel.text.UTF8String) &&
[FastAddressBook isSipURIValid:_addressLabel.text]));
ContactDetailsView *contactDetailsView = VIEW(ContactDetailsView);
self.inviteButton.hidden = !ENABLE_SMS_INVITE || [[contactDetailsView.contact sipAddresses] count] > 0 || !self.linphoneImage.hidden;
self.inviteButton.hidden = !ENABLE_SMS_INVITE || [[contactDetailsView.contact sipAddresses] count] > 0 || !self.linphoneImage.hidden;
[self shouldHideEncryptedChatView:account && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(account)) && model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh)];
_chatButton.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"];
}
if (addr) {
@ -103,7 +109,7 @@
normAddr = linphone_account_normalize_phone_number(account,
_addressLabel.text.UTF8String);
}
LinphoneAddress *addr = linphone_core_interpret_url(LC, normAddr);
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, normAddr, true);
// Test presence
Contact *contact = [FastAddressBook getContactWithAddress:(addr)];

View file

@ -67,7 +67,6 @@
if (addressField && (!dtmf || !linphone_core_in_call(LC))) {
NSString *newAddress = [NSString stringWithFormat:@"%@%c", addressField.text, digit];
[addressField setText:newAddress];
linphone_core_play_dtmf(LC, digit, -1);
} else {
linphone_call_send_dtmf(linphone_core_get_current_call(LC), digit);
linphone_core_play_dtmf(LC, digit, 100);

View file

@ -91,7 +91,7 @@
// Set up the cell...
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];
displayNameLabel.text = [NSString stringWithUTF8String:subject];
[_avatarImage setImage:[UIImage imageNamed:@"voip_multiple_contacts_avatar"]];
_stateImage.hidden = true;
} else {

View file

@ -92,12 +92,6 @@
[_playButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemPlay:[UIColor blackColor]] forState:UIControlStateNormal];
[_stopButton setTitle:@"" forState:UIControlStateNormal];
[_stopButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemRefresh:[UIColor blackColor]] forState:UIControlStateNormal];
if (linphone_player_get_is_video_available(player)) {
linphone_player_set_window_id(player, (__bridge void *)VIEW(RecordingsListView).videoView);
VIEW(RecordingsListView).videoView.hidden = NO;
} else {
VIEW(RecordingsListView).videoView.hidden = YES;
}
}
- (BOOL)isOpened {
@ -195,6 +189,12 @@ void on_eof_reached(LinphonePlayer *pl) {
[_playButton setTitle:@"" forState:UIControlStateNormal];
[_playButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemPause:[UIColor blackColor]] forState:UIControlStateNormal];
linphone_player_start(player);
if (linphone_player_get_is_video_available(player)) {
linphone_player_set_window_id(player, (__bridge void *)VIEW(RecordingsListView).videoView);
VIEW(RecordingsListView).videoView.hidden = NO;
} else {
VIEW(RecordingsListView).videoView.hidden = YES;
}
break;
case LinphonePlayerPlaying:
NSLog(@"Pause");

View file

@ -25,8 +25,6 @@
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (strong, nonatomic) IBOutlet UIToolbar *toolbar;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *shareButton;
@property(nonatomic, assign) __block NSString *recording;
- (id)initWithIdentifier:(NSString*)identifier;

View file

@ -64,7 +64,8 @@ static UILinphoneAudioPlayer *player;
NSArray *parsedRecording = [LinphoneUtils parseRecordingName:_recording];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"HH:mm:ss"];
_nameLabel.text = [[[parsedRecording objectAtIndex:0] stringByAppendingString:@" @ "] stringByAppendingString:[dateFormat stringFromDate:[parsedRecording objectAtIndex:1]]];
NSString *b = [[parsedRecording objectAtIndex:0] containsString:@"conf-id"] ? NSLocalizedString(@"Meeting", nil) : [parsedRecording objectAtIndex:0];
_nameLabel.text = [[b stringByAppendingString:@" @ "] stringByAppendingString:[dateFormat stringFromDate:[parsedRecording objectAtIndex:1]]];
}
}
@ -89,7 +90,8 @@ static UILinphoneAudioPlayer *player;
}
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
self.selectionStyle = UITableViewCellSelectionStyleNone;
if (!VIEW(RecordingsListView).tableController.isEditing)
self.selectionStyle = UITableViewCellSelectionStyleNone;
}
- (void)updateFrame {
@ -104,10 +106,11 @@ static UILinphoneAudioPlayer *player;
-(void)setSelected:(BOOL)selected animated:(BOOL)animated{
[super setSelected:selected animated:animated];
_toolbar.hidden = !selected;
if (!selected) {
return;
}
if (!selected || (selected && VIEW(RecordingsListView).tableController.isEditing)) {
_toolbar.hidden = true;
return;
}
if (player && [player isCreated]) {
[player close];
}
@ -121,6 +124,7 @@ static UILinphoneAudioPlayer *player;
player.view.frame = _playerView.frame;
player.view.bounds = _playerView.bounds;
[player open];
_toolbar.hidden = false;
}
- (void)onShareButtonPressed {

View file

@ -20,6 +20,7 @@ import linphonesw
var previousFilter : String?
var magicSearch : MagicSearch
var magicSearchDelegate : MagicSearchDelegate?
var lastSearch : [SearchResult]?
override init() {
@ -30,10 +31,12 @@ import linphonesw
magicSearchDelegate = MagicSearchDelegateStub(onSearchResultsReceived: { (magicSearch: MagicSearch) in
self.needUpdateLastSearchContacts = true
self.ongoingSearch = false
self.lastSearch = magicSearch.lastSearch
Log.directLog(BCTBX_LOG_MESSAGE, text: "Contact magic search -- filter = \(String(describing: self.previousFilter)) -- \(magicSearch.lastSearch.count) contact founds")
NotificationCenter.default.post(name: Notification.Name(kLinphoneMagicSearchFinished), object: self)
}, onLdapHaveMoreResults: { (magicSearch: MagicSearch, ldap: Ldap) in
Log.directLog(BCTBX_LOG_MESSAGE, text: "Ldap have more result")
NotificationCenter.default.post(name: Notification.Name(kLinphoneMagicSearchMoreAvailable), object: self)
})
magicSearch.addDelegate(delegate: magicSearchDelegate!)
@ -47,17 +50,26 @@ import linphonesw
return theMagicSearchSingleton!
}
@objc static func destroyInstance() {
theMagicSearchSingleton = nil
}
func getContactFromAddr(addr: Address) -> Contact? {
return LinphoneManager.instance().fastAddressBook.addressBookMap.object(forKey: addr.asStringUriOnly() as Any) as? Contact
}
func getContactFromPhoneNb(phoneNb: String) -> Contact? {
let contactKey = FastAddressBook.localizedLabel(FastAddressBook.normalizeSipURI( lc?.defaultAccount?.normalizePhoneNumber(username: phoneNb) ?? phoneNb))
let contactKey = FastAddressBook.localizedLabel(FastAddressBook.normalizeSipURI(lc?.defaultAccount?.normalizePhoneNumber(username: phoneNb) ?? phoneNb, use_prefix: true))
return LinphoneManager.instance().fastAddressBook.addressBookMap.object(forKey: contactKey as Any) as? Contact
}
func searchAndAddMatchingContact(searchResult: SearchResult) -> Contact? {
if let friend = searchResult.friend {
if (searchResult.sourceFlags == MagicSearchSource.LdapServers.rawValue), let newContact = Contact(friend: friend.getCobject) {
// Contact comes from LDAP, creating a new one
newContact.createdFromLdapOrProvisioning = true
return newContact
}
if let addr = friend.address, let foundContact = getContactFromAddr(addr: addr) {
return foundContact
}
@ -66,11 +78,6 @@ import linphonesw
return foundContact
}
}
// No contacts found (searchResult likely comes from LDAP), creating a new one
if let newContact = Contact(friend: friend.getCobject) {
newContact.createdFromLdap = true
return newContact
}
}
if let addr = searchResult.address, let foundContact = getContactFromAddr(addr: addr) {
@ -81,6 +88,11 @@ import linphonesw
return foundContact
}
// Friend comes from provisioning
if let addr = searchResult.address, let friend = searchResult.friend, let newContact = Contact(friend: friend.getCobject) {
newContact.createdFromLdapOrProvisioning = true
return newContact
}
return nil
}
@ -91,8 +103,10 @@ import linphonesw
@objc func getLastSearchResults() -> UnsafeMutablePointer<bctbx_list_t>? {
var cList: UnsafeMutablePointer<bctbx_list_t>? = nil
for data in magicSearch.lastSearch {
cList = bctbx_list_append(cList, UnsafeMutableRawPointer(data.getCobject))
if let search = lastSearch {
for data in search {
cList = bctbx_list_append(cList, UnsafeMutableRawPointer(data.getCobject))
}
}
return cList
}
@ -100,13 +114,9 @@ import linphonesw
@objc func getLastSearchContacts() -> [Contact] {
if (needUpdateLastSearchContacts) {
lastSearchContacts = []
var addedContactNames : [String] = []
for res in magicSearch.lastSearch {
if let contact = searchAndAddMatchingContact(searchResult: res) {
if (!addedContactNames.contains(contact.displayName)) {
addedContactNames.append(contact.displayName)
lastSearchContacts.append(contact)
}
lastSearchContacts.append(contact)
}
}
needUpdateLastSearchContacts = false

View file

@ -123,3 +123,4 @@
@end
void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState);
void main_view_chat_room_conference_joined(LinphoneChatRoom *cr, const LinphoneEventLog *event_log);

View file

@ -374,7 +374,6 @@ static RootViewManager *rootViewManagerInstance = nil;
}
break;
}
case LinphoneCallOutgoingInit:
case LinphoneCallOutgoingEarlyMedia:
case LinphoneCallOutgoingProgress:
case LinphoneCallOutgoingRinging: {
@ -386,7 +385,7 @@ static RootViewManager *rootViewManagerInstance = nil;
}
break;
}
case LinphoneCallPausedByRemote:
case LinphoneCallPausedByRemote:break;
case LinphoneCallConnected: {
if (![LinphoneManager.instance isCTCallCenterExist]) {
/*only register CT call center CB for connected call*/
@ -418,6 +417,7 @@ static RootViewManager *rootViewManagerInstance = nil;
}
case LinphoneCallUpdating:
break;
}
if (state == LinphoneCallEnd || state == LinphoneCallError || floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)
[self updateApplicationBadgeNumber];
@ -453,9 +453,19 @@ static RootViewManager *rootViewManagerInstance = nil;
LinphoneManager *lm = LinphoneManager.instance;
LOGI(@"%s", linphone_global_state_to_string(linphone_core_get_global_state(LC)));
NSString* groupName = [NSString stringWithFormat:@"group.%@.linphoneExtension",[[NSBundle mainBundle] bundleIdentifier]];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName];
NSDictionary *dict = [defaults valueForKey:@"photoData"];
NSDictionary *dictFile = [defaults valueForKey:@"icloudData"];
NSDictionary *dictUrl = [defaults valueForKey:@"url"];
// If we've been started by a remote push notification,
// we'll already be on the corresponding chat conversation view, no need to go anywhere else
if (![[self currentView].name isEqualToString:@"ChatConversationView"]) {
if (dict||dictFile||dictUrl){
[self changeCurrentView:ChatsListView.compositeViewDescription];
}else if (![[self currentView].name isEqualToString:@"ChatConversationView"]) {
if (linphone_core_get_global_state(LC) != LinphoneGlobalOn) {
[self changeCurrentView:DialerView.compositeViewDescription];
@ -607,7 +617,8 @@ static RootViewManager *rootViewManagerInstance = nil;
}
[self _changeCurrentView:viewStack.lastObject ?: DialerView.compositeViewDescription
transition:[PhoneMainView getBackwardTransition]
animated:ANIMATED];
animated:ANIMATED
addViewToStack:FALSE];
return [mainViewController getCurrentViewController];
}
@ -621,18 +632,19 @@ static RootViewManager *rootViewManagerInstance = nil;
- (void)changeCurrentView:(UICompositeViewDescription *)view {
[self _changeCurrentView:view transition:nil animated:ANIMATED];
[self _changeCurrentView:view transition:nil animated:ANIMATED addViewToStack:TRUE];
}
- (UIViewController *)_changeCurrentView:(UICompositeViewDescription *)view
transition:(CATransition *)transition
animated:(BOOL)animated {
animated:(BOOL)animated
addViewToStack:(BOOL)addViewToStack {
PhoneMainView *vc = [[RootViewManager instance] setViewControllerForDescription:view];
if (![view equal:vc.currentView] || vc != self) {
LOGI(@"Change current view to %@", view.name);
[self setPreviousViewName:vc.currentView.name];
NSMutableArray *viewStack = [RootViewManager instance].viewDescriptionStack;
[viewStack addObject:view];
if (addViewToStack) [viewStack addObject:view];
if (animated && transition == nil)
transition = [PhoneMainView getTransition:vc.currentView new:view];
[vc.mainViewController setViewTransition:(animated ? transition : nil)];
@ -653,7 +665,8 @@ static RootViewManager *rootViewManagerInstance = nil;
while (viewStack.count > 0 && ![[viewStack lastObject] equal:view]) {
[viewStack removeLastObject];
}
return [self _changeCurrentView:view transition:[PhoneMainView getBackwardTransition] animated:ANIMATED];
BOOL addView = (viewStack.count == 0); // if we couldn't find the view in the stack, we need to add it
return [self _changeCurrentView:view transition:[PhoneMainView getBackwardTransition] animated:ANIMATED addViewToStack:addView];
}
- (void) setPreviousViewName:(NSString*)previous{
@ -884,6 +897,7 @@ static RootViewManager *rootViewManagerInstance = nil;
LinphoneChatRoomCbs *cbs = linphone_factory_create_chat_room_cbs(linphone_factory_get());
linphone_chat_room_cbs_set_state_changed(cbs, main_view_chat_room_state_changed);
linphone_chat_room_cbs_set_conference_joined(cbs, main_view_chat_room_conference_joined);
linphone_chat_room_add_callbacks(room, cbs);
return room;
@ -901,31 +915,34 @@ static RootViewManager *rootViewManagerInstance = nil;
[view clearMessageView];
view.chatRoom = cr;
view.peerAddress = linphone_address_as_string(linphone_chat_room_get_peer_address(cr));
view.localAddress = linphone_address_as_string(linphone_chat_room_get_local_address(cr));
self.currentRoom = view.chatRoom;
if (PhoneMainView.instance.currentView == view.compositeViewDescription)
[view configureForRoom:FALSE];
else
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
void main_view_chat_room_conference_joined(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) {
PhoneMainView *view = PhoneMainView.instance;
LOGI(@"Chat room [%p] conference joined.", cr);
linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr));
[view goToChatRoom:cr];
if (!IPAD)
return;
if (PhoneMainView.instance.currentView != ChatsListView.compositeViewDescription && PhoneMainView.instance.currentView != ChatConversationView.compositeViewDescription)
return;
ChatsListView *mainView = VIEW(ChatsListView);
[mainView.tableController loadData];
[mainView.tableController selectFirstRow];
}
void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState) {
PhoneMainView *view = PhoneMainView.instance;
switch (newState) {
case LinphoneChatRoomStateCreated: {
LOGI(@"Chat room [%p] created on server.", cr);
linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr));
[view goToChatRoom:cr];
if (!IPAD)
break;
if (PhoneMainView.instance.currentView != ChatsListView.compositeViewDescription && PhoneMainView.instance.currentView != ChatConversationView.compositeViewDescription)
break;
ChatsListView *mainView = VIEW(ChatsListView);
[mainView.tableController loadData];
[mainView.tableController selectFirstRow];
break;
}
case LinphoneChatRoomStateCreationFailed:
LOGE(@"Chat room [%p] could not be created on server.", cr);
linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr));
@ -958,4 +975,8 @@ void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomSta
}
}
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[UIDeviceBridge notifyDisplayModeSwitch];
}
@end

View file

@ -147,8 +147,6 @@
NSString *recordingPath = subAr[indexPath.row];
[cell setRecording:recordingPath];
[super accessoryForCell:cell atPath:indexPath];
//accessoryForCell set it to gray but we don't want it
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell updateFrame];
cell.contentView.userInteractionEnabled = false;
return cell;

View file

@ -22,12 +22,12 @@
#import "LinphoneAppDelegate.h"
#import "PhoneMainView.h"
#import "Utils.h"
#import "linphoneapp-Swift.h"
#import "DCRoundSwitch.h"
#import "IASKSpecifierValuesViewController.h"
#import "IASKPSTextFieldSpecifierViewCell.h"
#import "IASKPSTitleValueSpecifierViewCell.h"
#import "IASKSpecifier.h"
#import "IASKTextField.h"
#include "linphone/lpconfig.h"
@ -247,7 +247,7 @@
[field setTextColor:LINPHONE_MAIN_COLOR];
}
if ([cell isKindOfClass:[IASKPSTitleValueSpecifierViewCell class]]) {
if ([cell isKindOfClass:[UITableViewCell class]]) {
cell.detailTextLabel.textColor = [UIColor grayColor];
} else {
cell.detailTextLabel.textColor = LINPHONE_MAIN_COLOR;
@ -445,8 +445,11 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
linphone_account_creator_set_password(creator, _tmpPwd.UTF8String);
[settingsStore setObject:_tmpPwd forKey:@"account_mandatory_password_preference"];
MSList *accountList = [LinphoneManager.instance createAccountsNotHiddenList];
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
[settingsStore integerForKey:@"current_proxy_config_preference"]);
bctbx_free(accountList);
if (account != NULL) {
const LinphoneAuthInfo *auth = linphone_account_find_auth_info(account);
if (auth) {
@ -521,11 +524,26 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
[keys addObject:@"download_bandwidth_preference"];
} else if ([@"auto_download_mode" compare:notif.object] == NSOrderedSame) {
NSString *download_mode = [notif.userInfo objectForKey:@"auto_download_mode"];
removeFromHiddenKeys = [download_mode isEqualToString:@"Customize"];
if (removeFromHiddenKeys)
[LinphoneManager.instance lpConfigSetInt:10000000 forKey:@"auto_download_incoming_files_max_size"];
[keys addObject:@"auto_download_incoming_files_max_size"];
}
if([download_mode isEqualToString:@"Customize"]){
[LinphoneManager.instance lpConfigSetBool:FALSE forKey:@"auto_download_mode_is_never"];
removeFromHiddenKeys = [download_mode isEqualToString:@"Customize"];
[LinphoneManager.instance lpConfigSetInt:10000000 forKey:@"auto_download_incoming_files_max_size"];
[keys addObject:@"auto_download_incoming_files_max_size"];
[hiddenKeys addObject:@"auto_write_to_gallery_mode"];
} else {
[LinphoneManager.instance lpConfigSetBool:FALSE forKey:@"auto_download_mode_is_never"];
[hiddenKeys addObject:@"auto_download_incoming_files_max_size"];
}
}else if ([@"vfs_enabled_mode" compare:notif.object] == NSOrderedSame) {
removeFromHiddenKeys = [[notif.userInfo objectForKey:@"vfs_enabled_mode"] boolValue];
if(removeFromHiddenKeys){
[LinphoneManager.instance lpConfigSetBool:TRUE forKey:@"vfs_enabled_mode"];
[LinphoneManager.instance lpConfigSetBool:FALSE forKey:@"auto_write_to_gallery_preference"];
[hiddenKeys addObject:@"auto_write_to_gallery_mode"];
[hiddenKeys addObject:@"vfs_enabled_mode"];
[keys addObject:@"vfs_enabled"];
}
}
for (NSString *key in keys) {
if (removeFromHiddenKeys)
@ -552,11 +570,12 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
return [[IASKSpecifier alloc] initWithSpecifier:dict];
}
} else {
if ([[specifier key] isEqualToString:@"media_encryption_preference"]) {
BOOL pq_available = linphone_core_get_post_quantum_available();
if ([[specifier key] isEqualToString:pq_available ? @"media_encryption_preference_pq_enabled" : @"media_encryption_preference"]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[specifier specifierDict]];
if (!linphone_core_media_encryption_supported(LC, LinphoneMediaEncryptionZRTP)) {
NSMutableArray *titles = [NSMutableArray arrayWithArray:[dict objectForKey:@"Titles"]];
[titles removeObject:@"ZRTP"];
[titles removeObject:pq_available ? @"ZRTP" : @"ZRTP Post Quantum"];
[dict setObject:titles forKey:@"Titles"];
NSMutableArray *values = [NSMutableArray arrayWithArray:[dict objectForKey:@"Values"]];
[values removeObject:@"ZRTP"];
@ -583,7 +602,7 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
}
if ([specifier.key hasPrefix:@"menu_account_"]) {
const bctbx_list_t *accounts = linphone_core_get_account_list(LC);
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
int index = [specifier.key substringFromIndex:@"menu_account_".length].intValue - 1;
if (index < bctbx_list_size(accounts)) {
LinphoneAccount *account = (LinphoneAccount *)bctbx_list_nth_data(accounts, index);
@ -591,6 +610,7 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
stringWithUTF8String:linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account)))];
[specifier.specifierDict setValue:name forKey:kIASKTitle];
}
bctbx_free(accounts);
}
if ([specifier.key hasPrefix:@"ldap_"]) {
@ -625,18 +645,25 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
- (NSSet *)findHiddenKeys {
LinphoneManager *lm = LinphoneManager.instance;
NSMutableSet *hiddenKeys = [NSMutableSet set];
const MSList *accounts = linphone_core_get_account_list(LC);
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
for (size_t i = bctbx_list_size(accounts) + 1; i <= 5; i++) {
[hiddenKeys addObject:[NSString stringWithFormat:@"menu_account_%lu", i]];
}
bctbx_free(accounts);
const MSList *ldaps = linphone_core_get_ldap_list(LC);
for (size_t i = bctbx_list_size(ldaps) + 1; i <= 5; i++) {
[hiddenKeys addObject:[NSString stringWithFormat:@"ldap_%lu", i]];
}
if (!linphone_core_sip_transport_supported(LC, LinphoneTransportTls)) {
[hiddenKeys addObject:@"media_encryption_preference"];
[hiddenKeys addObject:@"media_encryption_preference_pq_enabled"];
} else {
if (linphone_core_get_post_quantum_available()) {
[hiddenKeys addObject:@"media_encryption_preference"];
} else {
[hiddenKeys addObject:@"media_encryption_preference_pq_enabled"];
}
}
if (!linphone_core_ldap_available(LC)) {
@ -761,6 +788,13 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
if (![[lm lpConfigStringForKey:@"auto_download_mode"] isEqualToString:@"Customize"]) {
[hiddenKeys addObject:@"auto_download_incoming_files_max_size"];
}
if ([lm lpConfigBoolForKey:@"vfs_enabled_mode"]) {
[hiddenKeys addObject:@"auto_write_to_gallery_mode"];
[hiddenKeys addObject:@"vfs_enabled_mode"];
}else{
[hiddenKeys addObject:@"vfs_enabled"];
}
return hiddenKeys;
}
@ -851,25 +885,34 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
[PhoneMainView.instance changeCurrentView:AssistantView.compositeViewDescription];
return;
} else if ([key isEqual:@"account_mandatory_remove_button"]) {
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Warning", nil)
message:NSLocalizedString(@"Are you sure to want to remove your proxy setup?", nil)
preferredStyle:UIAlertControllerStyleAlert];
NSString *popUpText;
NSString *appDomain = [LinphoneManager.instance lpConfigStringForKey:@"domain_name"
inSection:@"app"
withDefault:@"sip.linphone.org"];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
MSList *accountList = [LinphoneManager.instance createAccountsNotHiddenList];
LinphoneAccount *account = bctbx_list_nth_data(accountList,
[settingsStore integerForKey:@"current_proxy_config_preference"]);
UIAlertAction* continueAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[settingsStore removeAccount];
[self recomputeAccountLabelsAndSync];
[_settingsController.navigationController popViewControllerAnimated:NO];
}];
bool isLinphoneAccount = strcmp(appDomain.UTF8String, linphone_account_params_get_domain(linphone_account_get_params(account))) == 0;
if (isLinphoneAccount) {
popUpText = NSLocalizedString(@"Your account will only be deleted locally.\nTo delete it permanently, go to our account management platform:", nil);
} else {
popUpText = NSLocalizedString(@"Your account will only be deleted locally.\nTo delete it permanently, go on your SIP provider website.", nil);
}
bctbx_free(accountList);
[errView addAction:defaultAction];
[errView addAction:continueAction];
[self presentViewController:errView animated:YES completion:nil];
UIConfirmationDialog *dialog = [UIConfirmationDialog ShowWithMessage:popUpText
cancelMessage:nil
confirmMessage:nil
onCancelClick:nil
onConfirmationClick:^() {
[settingsStore removeAccount];
[self recomputeAccountLabelsAndSync];
[_settingsController.navigationController popViewControllerAnimated:NO];
}];
dialog.subscribeLabel.hidden = !isLinphoneAccount; // Only display link to https://subscribe.linphone.org for linphone accounts
} else if ([key isEqual:@"account_mandatory_change_password"]) {
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Change your password", nil)
message:NSLocalizedString(@"Please enter and confirm your new password", nil)
@ -901,8 +944,11 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
if (pwd && ![pwd isEqualToString:@""]) {
if ([pwd isEqualToString:conf_pwd]) {
_tmpPwd = pwd;
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
[settingsStore integerForKey:@"current_proxy_config_preference"]);
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
LinphoneAccount *account = bctbx_list_nth_data(accounts,
[settingsStore integerForKey:@"current_proxy_config_preference"]);
bctbx_free(accounts);
const LinphoneAuthInfo *ai = linphone_account_find_auth_info(account);
LinphoneAccountCreator *account_creator = linphone_account_creator_new(

View file

@ -66,8 +66,11 @@
changeCurrentView:AssistantView.compositeViewDescription];
}]];
BOOL mustLink = ([LinphoneManager.instance lpConfigIntForKey:@"must_link_account_time"] > 0);
BOOL hasAccount = linphone_core_get_account_list(LC) != NULL;
if (mustLink && hasAccount) {
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
BOOL hasAccount = accounts != NULL;
bctbx_free(accounts);
if (mustLink && hasAccount && ![LinphoneManager.instance lpConfigIntForKey:@"hide_link_phone_number"]) {
[_sideMenuEntries
addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"Link my account", nil)
image:[UIImage imageNamed:@"menu_link_account.png"]
@ -104,7 +107,7 @@
}
[_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:VoipTexts.conference_scheduled
image:[UIImage imageNamed:@"voip_conference_new.png"]
image:[UIImage imageNamed:@"side_menu_voip_meeting_schedule"]
tapBlock:^() {
[PhoneMainView.instance
changeCurrentView:ScheduledConferencesView.compositeViewDescription];
@ -127,9 +130,12 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
BOOL hasDefault = (linphone_core_get_default_account(LC) != NULL);
// default account is shown in the header already
size_t count = bctbx_list_size(linphone_core_get_account_list(LC));
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
size_t count = bctbx_list_size(accounts);
bctbx_free(accounts);
return MAX(0, (int)count - (hasDefault ? 1 : 0));
} else {
return [_sideMenuEntries count];
@ -142,12 +148,14 @@
// isLcInitialized called here because this is called when going in bg after LC destroy
if (indexPath.section == 0 && [LinphoneManager isLcInitialized]) {
// do not display default account here, it is already in header view
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
int idx =
linphone_core_get_default_account(LC)
? bctbx_list_index(linphone_core_get_account_list(LC), linphone_core_get_default_account(LC))
? bctbx_list_index(accounts, linphone_core_get_default_account(LC))
: HUGE_VAL;
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
LinphoneAccount *account = bctbx_list_nth_data(accounts,
(int)indexPath.row + (idx <= indexPath.row ? 1 : 0));
bctbx_free(accounts);
if (account) {
cell.textLabel.text = [NSString stringWithUTF8String:linphone_account_params_get_identity(linphone_account_get_params(account))];
cell.imageView.image = [StatusBarView imageForState:linphone_account_get_state(account)];

View file

@ -70,7 +70,9 @@
_addressLabel.text = str ? [NSString stringWithUTF8String:str] : NSLocalizedString(@"No address", nil);
if (str) ms_free(str);
} else {
_nameLabel.text = linphone_core_get_account_list(LC) ? NSLocalizedString(@"No default account", nil) : NSLocalizedString(@"No account", nil);
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
_nameLabel.text = accounts ? NSLocalizedString(@"No default account", nil) : NSLocalizedString(@"No account", nil);
bctbx_free(accounts);
// display direct IP:port address so that we can be reached
LinphoneAddress *addr = linphone_core_get_primary_contact_parsed(LC);
if (addr) {

View file

@ -27,7 +27,7 @@ import AVFoundation
@objc class CallAppData: NSObject {
@objc var batteryWarningShown = false
@objc var videoRequested = false /*set when user has requested for video*/
@objc var isConference = true
@objc var isConference = false
}
@ -49,6 +49,7 @@ import AVFoundation
var actionsToPerformOnceWhenCoreIsOn : [(()->Void)] = []
var conference: Conference?
var callkitAudioSessionActivated : Bool? = nil // if "nil", ignore.
var actionToFulFill : CXCallAction? = nil;
var backgroundContextCall : Call?
@objc var backgroundContextCameraIsEnabled : Bool = false
@ -556,7 +557,7 @@ import AVFoundation
switch cstate {
case .IncomingReceived:
let addr = call.remoteAddress
var displayName = incomingDisplayName(call: call)
var displayName = incomingDisplayName(call: call)
if (CallManager.callKitEnabled()) {
let isConference = isConferenceCall(call: call)
@ -608,7 +609,13 @@ import AVFoundation
CallManager.instance().speakerBeforePause = false
CallManager.instance().changeRouteToSpeaker()
}
actionToFulFill?.fulfill()
actionToFulFill = nil
break
case .Paused:
actionToFulFill?.fulfill()
actionToFulFill = nil
break
case .OutgoingInit,
.OutgoingProgress,
.OutgoingRinging,
@ -625,7 +632,16 @@ import AVFoundation
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: outgoing call started connecting with uuid \(uuid!) and callId \(callId!)")
CallManager.instance().providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!)
} else {
CallManager.instance().referedToCall = callId
if CallManager.instance().isConferenceCall(call: call) {
let uuid = UUID()
let callInfo = CallInfo.newOutgoingCallInfo(addr: call.remoteAddress!, isSas: call.params?.mediaEncryption == .ZRTP, displayName: VoipTexts.conference_default_title, isVideo: call.params?.videoEnabled == true, isConference:true)
CallManager.instance().providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
CallManager.instance().providerDelegate.uuids.updateValue(uuid, forKey: "")
CallManager.instance().providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid)
Core.get().activateAudioSession(actived: true)
} else {
CallManager.instance().referedToCall = callId
}
}
}
break
@ -695,7 +711,7 @@ import AVFoundation
}
break
case .Released:
call.userData = nil
CallManager.setAppData(sCall : call, appData : nil);
break
case .Referred:
CallManager.instance().referedFromCall = call.callLog?.callId
@ -706,7 +722,7 @@ import AVFoundation
let readyForRoutechange = CallManager.instance().callkitAudioSessionActivated == nil || (CallManager.instance().callkitAudioSessionActivated == true)
if (readyForRoutechange && (cstate == .IncomingReceived || cstate == .OutgoingInit || cstate == .Connected || cstate == .StreamsRunning)) {
if ((call.currentParams?.videoEnabled ?? false) && CallManager.instance().isReceiverEnabled()) {
if ((call.currentParams?.videoEnabled ?? false) && CallManager.instance().isReceiverEnabled() && call.conference == nil) {
CallManager.instance().changeRouteToSpeaker()
} else if (isBluetoothAvailable()) {
// Use bluetooth device by default if one is available
@ -775,6 +791,7 @@ import AVFoundation
do {
if let core = lc, let params = try? core.createConferenceParams(conference: nil) {
params.videoEnabled = false // We disable video for local conferencing (cf Android)
params.subject = VoipTexts.conference_local_title
let conference = core.conference != nil ? core.conference : try core.createConferenceWithParams(params: params)
try conference?.addParticipants(calls: core.calls)
}
@ -783,8 +800,12 @@ import AVFoundation
}
}
@objc func applyInternationalPrefix() -> Bool {
if let account = lc?.defaultAccount, let params = account.params {
return params.useInternationalPrefixForCallsAndChats
}
return true; // Legacy behavior
}
}

View file

@ -36,10 +36,16 @@ class ScheduledConferenceData {
let participantsShort = MutableLiveData<String>()
let participantsExpanded = MutableLiveData<String>()
let rawDate : Date
let isConferenceCancelled = MutableLiveData(false)
let canEdit = MutableLiveData(false)
let isFinished : Bool
let selectedForDeletion = MutableLiveData(false)
private var conferenceSchedulerDelegate : ConferenceSchedulerDelegateStub? = nil
private var conferenceScheduler : ConferenceScheduler? = nil
init (conferenceInfo: ConferenceInfo) {
init (conferenceInfo: ConferenceInfo, isFinished: Bool = false) {
self.conferenceInfo = conferenceInfo
self.isFinished = isFinished
expanded.value = false
address.value = conferenceInfo.uri?.asStringUriOnly()
@ -51,14 +57,24 @@ class ScheduledConferenceData {
rawDate = Date(timeIntervalSince1970:TimeInterval(conferenceInfo.dateTime))
let durationFormatter = DateComponentsFormatter()
durationFormatter.unitsStyle = .positional
durationFormatter.allowedUnits = [.minute, .second ]
durationFormatter.zeroFormattingBehavior = [ .pad ]
duration.value = conferenceInfo.duration > 0 ? durationFormatter.string(from: TimeInterval(conferenceInfo.duration)) : nil
durationFormatter.unitsStyle = .abbreviated
duration.value = conferenceInfo.duration > 0 ? durationFormatter.string(from: TimeInterval(conferenceInfo.duration*60)) : nil
organizer.value = conferenceInfo.organizer?.addressBookEnhancedDisplayName()
computeParticipantsLists()
isConferenceCancelled.value = conferenceInfo.state == .Cancelled
if let organizerAddress = conferenceInfo.organizer {
let localAccount = Core.get().accountList.filter { account in
account.params?.identityAddress != nil && organizerAddress.weakEqual(address2: account.params!.identityAddress!)
}.first
canEdit.value = localAccount != nil
} else {
canEdit.value = false
Log.e("[Scheduled Conference] No organizer SIP URI found for: \(conferenceInfo.uri?.asStringUriOnly())")
}
}
func destroy() {
@ -73,6 +89,10 @@ class ScheduledConferenceData {
String(describing: participant.addressBookEnhancedDisplayName())
}.joined(separator: ", ")
if (participantsShort.value?.count == 0) {
participantsShort.value = " "
}
participantsExpanded.value = conferenceInfo.participants.map {(participant) in
String(describing: participant.addressBookEnhancedDisplayName())+" ("+String(describing: participant.asStringUriOnly())+")"
}.joined(separator: "\n")
@ -81,4 +101,30 @@ class ScheduledConferenceData {
func gotoAssociatedChat() {
}
func deleteConference() {
conferenceSchedulerDelegate = ConferenceSchedulerDelegateStub(
onStateChanged: { scheduler, state in
Log.i("[Conference Deletion] Conference scheduler state is \(state)")
if (state == .Ready) {
if let chatRoomParams = ConferenceSchedulingViewModel.shared.getConferenceInvitationsChatRoomParams() {
scheduler.sendInvitations(chatRoomParams: chatRoomParams) // Send cancel ICS
Log.e("[Conference Deletion] sent cancel ICS.")
}
}
})
if (conferenceInfo.state != .Cancelled && canEdit.value == true) {
Log.i("[Scheduled Conferences] Cancelling conference \(conferenceInfo.subject)")
self.conferenceScheduler = try? Core.get().createConferenceScheduler()
if (self.conferenceScheduler != nil) {
self.conferenceScheduler?.addDelegate(delegate: conferenceSchedulerDelegate!)
self.conferenceScheduler?.cancelConference(conferenceInfo: conferenceInfo)
}
} else {
Core.get().deleteConferenceInformation(conferenceInfo: conferenceInfo)
}
}
}

View file

@ -31,8 +31,9 @@ class ConferenceSchedulingViewModel {
let description = MutableLiveData<String>()
let scheduleForLater = MutableLiveData<Bool>()
let scheduledDateTime = MutableLiveData<Date>()
let scheduledDate = MutableLiveData<Date>()
let scheduledTime = MutableLiveData<Date>()
var scheduledTimeZone = MutableLiveData<Int>()
static let timeZones: [TimeZoneData] = computeTimeZonesList()
@ -64,7 +65,7 @@ class ConferenceSchedulingViewModel {
private var chatRooomDelegate : ChatRoomDelegate? = nil
private var conferenceSchedulerDelegate : ConferenceSchedulerDelegateStub? = nil
var existingConfInfo:ConferenceInfo? = nil
var existingConfInfo:MutableLiveData<ConferenceInfo?> = MutableLiveData()
init () {
@ -79,14 +80,10 @@ class ConferenceSchedulingViewModel {
}
self.address.value = conferenceAddress
if (self.sendInviteViaChat.value == true) {
if (self.scheduleForLater.value == true && 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!
if let chatRoomParams = self.getConferenceInvitationsChatRoomParams() {
scheduler.sendInvitations(chatRoomParams: chatRoomParams)
}
} else {
@ -133,13 +130,23 @@ class ConferenceSchedulingViewModel {
scheduleForLater.observe { _ in
self.continueEnabled.value = self.allMandatoryFieldsFilled()
}
scheduledDateTime.observe { _ in
scheduledDate.observe { _ in
self.continueEnabled.value = self.allMandatoryFieldsFilled()
}
scheduledTime.observe { _ in
self.continueEnabled.value = self.allMandatoryFieldsFilled()
}
}
func isEndToEndEncryptedChatAvailable() -> Bool {
let core = Core.get()
return core.limeX3DhEnabled &&
((core.limeX3DhServerUrl != nil && core.limeX3DhServerUrl.count > 0) || core.defaultAccount?.params?.limeServerUrl != nil) &&
core.defaultAccount?.params?.conferenceFactoryUri != nil
}
func reset() {
subject.value = ""
@ -147,8 +154,9 @@ class ConferenceSchedulingViewModel {
isEncrypted.value = false
sendInviteViaChat.value = true
sendInviteViaEmail.value = false
scheduledDateTime.value = Date()
scheduledDate.value = nil
scheduledTime.value = nil
scheduledTimeZone.value = ConferenceSchedulingViewModel.timeZones.indices.filter {
ConferenceSchedulingViewModel.timeZones[$0].timeZone.identifier == NSTimeZone.default.identifier
}.first
@ -158,7 +166,7 @@ class ConferenceSchedulingViewModel {
}.first
continueEnabled.value = false
selectedAddresses.value = []
existingConfInfo = nil
existingConfInfo.value = nil
description.value = ""
}
@ -191,7 +199,7 @@ class ConferenceSchedulingViewModel {
conferenceScheduler = try? Core.get().createConferenceScheduler()
conferenceScheduler?.addDelegate(delegate: conferenceSchedulerDelegate!)
guard let conferenceInfo = existingConfInfo != nil ? existingConfInfo : try Factory.Instance.createConferenceInfo() else {
guard let conferenceInfo = existingConfInfo.value != nil ? existingConfInfo.value??.clone() : try Factory.Instance.createConferenceInfo() else {
Log.e("[Conference Creation/Update] Failed, unable to get conf info.")
return
}
@ -206,7 +214,7 @@ class ConferenceSchedulingViewModel {
}
conferenceScheduler?.account = localAccount
conferenceScheduler?.info = conferenceInfo // Will trigger the conference creation automatically
existingConfInfo = conferenceInfo
existingConfInfo.value = conferenceInfo
} catch {
Log.e("[Conference Creation] Failed \(error)")
@ -216,14 +224,15 @@ class ConferenceSchedulingViewModel {
private func allMandatoryFieldsFilled() -> Bool {
return subject.value != nil && subject.value!.count > 0 && (scheduleForLater.value != true || scheduledDateTime.value != nil);
return subject.value != nil && subject.value!.count > 0 && (scheduleForLater.value != true || (scheduledDate.value != nil && scheduledTime.value != nil) );
}
private func getConferenceStartTimestamp() -> Double {
return scheduleForLater.value == true ?
scheduledDateTime.value!.timeIntervalSince1970 +
Double(ConferenceSchedulingViewModel.timeZones[scheduledTimeZone.value!].timeZone.secondsFromGMT()-TimeZone.current.secondsFromGMT())
let days = Int32(scheduledDate.value!.timeIntervalSince1970)/86400
let time = Int32(scheduledTime.value!.timeIntervalSince1970)%86400
return scheduleForLater.value == true ? TimeInterval(days * 86400 + time) + Double(ConferenceSchedulingViewModel.timeZones[scheduledTimeZone.value!].timeZone.secondsFromGMT()-TimeZone.current.secondsFromGMT())
: Date().timeIntervalSince1970
}
@ -238,5 +247,16 @@ class ConferenceSchedulingViewModel {
return [Duration(value: 30, display: "30min"), Duration(value: 60, display: "1h"), Duration(value: 120, display: "2h")]
}
func getConferenceInvitationsChatRoomParams() -> ChatRoomParams? {
if let chatRoomParams = try?self.core.createDefaultChatRoomParams() {
chatRoomParams.encryptionEnabled = self.isEndToEndEncryptedChatAvailable()
chatRoomParams.groupEnabled = false
chatRoomParams.backend = chatRoomParams.encryptionEnabled ? .FlexisipChat : .Basic
chatRoomParams.subject = self.subject.value!
return chatRoomParams
}
return nil
}
}

View file

@ -31,13 +31,17 @@ class ScheduledConferencesViewModel {
var conferences : MutableLiveData<[ScheduledConferenceData]> = MutableLiveData([])
var daySplitted : [Date : [ScheduledConferenceData]] = [:]
var coreDelegate: CoreDelegateStub?
var showTerminated = MutableLiveData(false)
let editionEnabled = MutableLiveData(false)
let conferenceScheduler = try? Core.get().createConferenceScheduler()
init () {
coreDelegate = CoreDelegateStub(
onConferenceInfoReceived: { (core, conferenceInfo) in
Log.i("[Scheduled Conferences] New conference info received")
self.conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo))
self.conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo,isFinished: false))
self.conferences.notifyValue()
}
@ -48,11 +52,20 @@ class ScheduledConferencesViewModel {
func computeConferenceInfoList() {
conferences.value!.removeAll()
let now = Date().timeIntervalSince1970 // Linphone uses time_t in seconds
let oneHourAgo = now - 3600 // Show all conferences from 1 hour ago and forward
core.getConferenceInformationListAfterTime(time: time_t(oneHourAgo)).filter{$0.duration != 0}.forEach { conferenceInfo in
conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo))
if (showTerminated.value == true) {
core.conferenceInformationList.filter{$0.duration != 0 && (TimeInterval($0.dateTime) + TimeInterval($0.duration) < now)}.forEach { conferenceInfo in
conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo,isFinished: true))
}
} else {
let twoHoursAgo = now - 7200 // Show all conferences from 2 hour ago and forward
core.getConferenceInformationListAfterTime(time: time_t(twoHoursAgo)).filter{$0.duration != 0}.forEach { conferenceInfo in
conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo,isFinished: false))
}
}
daySplitted = [:]
conferences.value!.forEach { (conferenceInfo) in
let startDateDay = dateAtBeginningOfDay(for: conferenceInfo.rawDate)

View file

@ -50,7 +50,7 @@ import linphonesw
}
static let compositeDescription = UICompositeViewDescription(ConferenceHistoryDetailsView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, fullscreen: false, isLeftFragment: false,fragmentWith: nil)
static let compositeDescription = UICompositeViewDescription(ConferenceHistoryDetailsView.self, statusBar: StatusBarView.self, tabBar: TabBarView.classForCoder(), sideMenu: SideMenuView.self, fullscreen: false, isLeftFragment: false,fragmentWith: HistoryListView.classForCoder())
static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription }
@ -65,6 +65,7 @@ import linphonesw
title:"")
super.nextButton.isHidden = true
super.backButton.isHidden = UIDevice.ipad()
let schedulingStack = UIStackView()
schedulingStack.axis = .vertical
@ -106,7 +107,6 @@ import linphonesw
// Organiser
organiserLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
contentView.addSubview(organiserLabel)
organiserLabel.matchParentSideBorders().height(form_input_height).alignUnder(view: schedulingStack,withMargin: form_margin*2).done()
organiserLabel.textAlignment = .left
@ -120,12 +120,10 @@ import linphonesw
organizerTableView.allowsFocus = false
}
organizerTableView.separatorStyle = .singleLine
organizerTableView.separatorColor = VoipTheme.light_grey_color
organizerTableView.tag = 1;
organizerTableView.matchParentSideBorders().height(VoipParticipantCell.cell_height).alignUnder(view: organiserLabel,withMargin: form_margin).done()
// Participants
participantsLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
contentView.addSubview(participantsLabel)
participantsLabel.matchParentSideBorders().height(form_input_height).alignUnder(view: organizerTableView,withMargin: form_margin).done()
participantsLabel.textAlignment = .left
@ -139,7 +137,6 @@ import linphonesw
participantsListTableView.allowsFocus = false
}
participantsListTableView.separatorStyle = .singleLine
participantsListTableView.separatorColor = VoipTheme.light_grey_color
// Goto chat - v2
/*
@ -152,6 +149,14 @@ import linphonesw
chatButton.centerX().alignParentBottom(withMargin: 3*self.form_margin).alignUnder(view: participantsListTableView,withMargin: 3*self.form_margin).done()
*/
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.organiserLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
self.organizerTableView.separatorColor = VoipTheme.separatorColor.get()
self.participantsLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
self.participantsListTableView.separatorColor = VoipTheme.separatorColor.get()
self.view.backgroundColor = VoipTheme.voipBackgroundBWColor.get()
}
}

View file

@ -29,13 +29,16 @@ import SVProgressHUD
let participantsListTableView = UITableView()
let datePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledDateTime,pickerMode: .date, readOnly:true)
let datePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledDate,pickerMode: .date, readOnly:true)
let timeZoneValue = StyledValuePicker(liveIndex: ConferenceSchedulingViewModel.shared.scheduledTimeZone,options: ConferenceSchedulingViewModel.timeZones.map({ (tzd: TimeZoneData) -> String in tzd.descWithOffset()}), readOnly:true)
let durationValue = StyledValuePicker(liveIndex: ConferenceSchedulingViewModel.shared.scheduledDuration,options: ConferenceSchedulingViewModel.durationList.map({ (duration: Duration) -> String in duration.display}), readOnly:true)
let timePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledDateTime,pickerMode: .time, readOnly:true)
let timePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledTime,pickerMode: .time, readOnly:true)
let descriptionLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_description_hint)
let descriptionInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_description_hint,liveValue: ConferenceSchedulingViewModel.shared.description, readOnly:true)
let createButton = FormButton(backgroundStateColors: VoipTheme.primary_colors_background)
let leftColumn = UIView()
let rightColumn = UIView()
let scheduleForm = UIView()
static let compositeDescription = UICompositeViewDescription(ConferenceSchedulingSummaryView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, fullscreen: false, isLeftFragment: false,fragmentWith: nil)
static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
@ -76,15 +79,12 @@ import SVProgressHUD
schedulingStack.alignUnder(view: subjectInput,withMargin: 3*form_margin).matchParentSideBorders(insetedByDx: form_margin).done()
let scheduleForm = UIView()
schedulingStack.addArrangedSubview(scheduleForm)
scheduleForm.matchParentSideBorders().done()
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { (forLater) in scheduleForm.isHidden = forLater != true }
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { (forLater) in self.scheduleForm.isHidden = forLater != true }
// Left column (Date & Time)
let leftColumn = UIView()
scheduleForm.addSubview(leftColumn)
leftColumn.matchParentWidthDividedBy(2.2).alignParentLeft(withMargin: form_margin).alignParentTop(withMargin: form_margin).done()
leftColumn.matchParentWidthDividedBy(2.2).alignParentLeft().alignParentTop(withMargin: form_margin).done()
let dateLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_date)
leftColumn.addSubview(dateLabel)
@ -104,9 +104,8 @@ import SVProgressHUD
// Right column (Duration & Timezone)
let rightColumn = UIView()
scheduleForm.addSubview(rightColumn)
rightColumn.matchParentWidthDividedBy(2.2).alignParentRight(withMargin: form_margin).alignParentTop().done()
rightColumn.matchParentWidthDividedBy(2.2).alignParentRight().alignParentTop().done()
let durationLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_duration)
rightColumn.addSubview(durationLabel)
@ -125,31 +124,26 @@ import SVProgressHUD
rightColumn.wrapContentY().done()
// Description
let descriptionLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_description_title)
scheduleForm.addSubview(descriptionLabel)
descriptionLabel.alignUnder(view: leftColumn,withMargin: form_margin).alignUnder(view: rightColumn,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).done()
descriptionInput.textContainer.maximumNumberOfLines = 5
descriptionInput.textContainer.lineBreakMode = .byWordWrapping
scheduleForm.addSubview(descriptionInput)
descriptionInput.alignUnder(view: descriptionLabel,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).height(description_height).alignParentBottom(withMargin: form_margin*2).done()
scheduleForm.wrapContentY().done()
// Sending method
let viaChatLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_send_invite_chat_summary)
contentView.addSubview(viaChatLabel)
viaChatLabel.matchParentSideBorders(insetedByDx: form_margin).alignUnder(view: schedulingStack,withMargin: 2*form_margin).done()
viaChatLabel.numberOfLines = 2
ConferenceSchedulingViewModel.shared.sendInviteViaChat.readCurrentAndObserve { (sendChat) in
viaChatLabel.isHidden = sendChat != true || ConferenceSchedulingViewModel.shared.scheduleForLater.value != true
}
// Participants
let participantsLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_participants_list)
participantsLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
let participantsLabel = StyledLabel(VoipTheme.conference_scheduling_font, " "+VoipTexts.conference_schedule_participants_list)
contentView.addSubview(participantsLabel)
participantsLabel.matchParentSideBorders().height(form_input_height).alignUnder(view: viaChatLabel,withMargin: form_margin*2).done()
participantsLabel.textAlignment = .center
participantsLabel.matchParentSideBorders().height(form_input_height).alignUnder(view: viaChatLabel,withMargin: form_margin).done()
participantsLabel.textAlignment = .left
contentView.addSubview(participantsListTableView)
@ -161,7 +155,7 @@ import SVProgressHUD
participantsListTableView.allowsFocus = false
}
participantsListTableView.separatorStyle = .singleLine
participantsListTableView.separatorColor = VoipTheme.light_grey_color
participantsListTableView.backgroundColor = .clear
ConferenceSchedulingViewModel.shared.selectedAddresses.readCurrentAndObserve { (addresses) in
self.participantsListTableView.reloadData()
@ -172,8 +166,13 @@ import SVProgressHUD
// Create / Schedule
contentView.addSubview(createButton)
createButton.centerX().alignParentBottom(withMargin: 3*self.form_margin).alignUnder(view: participantsListTableView,withMargin: 3*self.form_margin).width(0).done()
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { _ in
self.createButton.title = ConferenceSchedulingViewModel.shared.scheduleForLater.value == true ? VoipTexts.conference_schedule_start.uppercased() : VoipTexts.conference_group_call_create.uppercased()
self.createButton.title = ConferenceSchedulingViewModel.shared.scheduleForLater.value == true ? ConferenceSchedulingViewModel.shared.existingConfInfo.value != nil ? VoipTexts.conference_schedule_edit.uppercased() : VoipTexts.conference_schedule_start.uppercased() : VoipTexts.conference_group_call_create.uppercased()
self.createButton.addSidePadding()
}
ConferenceSchedulingViewModel.shared.existingConfInfo.readCurrentAndObserve { _ in
self.createButton.title = ConferenceSchedulingViewModel.shared.scheduleForLater.value == true ? ConferenceSchedulingViewModel.shared.existingConfInfo.value != nil ? VoipTexts.conference_schedule_edit.uppercased() : VoipTexts.conference_schedule_start.uppercased() : VoipTexts.conference_group_call_create.uppercased()
self.createButton.addSidePadding()
}
@ -210,21 +209,40 @@ import SVProgressHUD
}
}
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { (later) in
self.createButton.title = ConferenceSchedulingViewModel.shared.scheduleForLater.value == true ? VoipTexts.conference_schedule_start.uppercased() : VoipTexts.conference_group_call_create.uppercased()
self.createButton.title = ConferenceSchedulingViewModel.shared.scheduleForLater.value == true ? ConferenceSchedulingViewModel.shared.existingConfInfo.value != nil ? VoipTexts.conference_schedule_edit.uppercased() : VoipTexts.conference_schedule_start.uppercased() : VoipTexts.conference_group_call_create.uppercased()
viaChatLabel.isHidden = later != true || ConferenceSchedulingViewModel.shared.sendInviteViaChat.value != true
viaChatLabel.removeConstraints().matchParentSideBorders(insetedByDx: self.form_margin).alignUnder(view: schedulingStack,withMargin: (viaChatLabel.isHidden ? 0 : 1)*self.form_margin).done()
if (viaChatLabel.isHidden) {
viaChatLabel.height(0).done()
}
self.createButton.addSidePadding()
}
createButton.centerX().alignParentBottom(withMargin: 3*self.form_margin).alignUnder(view: participantsListTableView,withMargin: 3*self.form_margin).done()
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.view.backgroundColor = VoipTheme.voipBackgroundBWColor.get()
participantsLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
self.participantsListTableView.separatorColor = VoipTheme.separatorColor.get()
}
}
override func viewWillAppear(_ animated: Bool) {
datePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledDateTime
datePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledDate
timeZoneValue.setIndex(index: ConferenceSchedulingViewModel.shared.scheduledTimeZone.value!)
durationValue.setIndex(index: ConferenceSchedulingViewModel.shared.scheduledDuration.value!)
timePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledDateTime
timePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledTime
descriptionInput.text = ConferenceSchedulingViewModel.shared.description.value
descriptionLabel.removeConstraints().alignUnder(view: leftColumn,withMargin: form_margin).alignUnder(view: rightColumn,withMargin: form_margin).matchParentSideBorders().done()
descriptionInput.removeConstraints().alignUnder(view: descriptionLabel,withMargin: form_margin).matchParentSideBorders().height(description_height).alignParentBottom(withMargin: form_margin*2).done()
if (ConferenceSchedulingViewModel.shared.description.value == nil || ConferenceSchedulingViewModel.shared.description.value!.count == 0) {
descriptionLabel.height(0).done()
descriptionInput.height(0).done()
}
// Wrap form
scheduleForm.removeConstraints().matchParentSideBorders().wrapContentY().done()
createButton.addSidePadding()
super.viewWillAppear(animated)
}
@ -233,6 +251,7 @@ import SVProgressHUD
func goBackParticipantsListSelection() {
let view: ChatConversationCreateView = VIEW(ChatConversationCreateView.compositeViewDescription())
view.unfragmentCompositeDescription()
let addresses = ConferenceSchedulingViewModel.shared.selectedAddresses.value!.map { (address) in String(address.asStringUriOnly()) }
view.tableController.contactsGroup = (addresses as NSArray).mutableCopy() as? NSMutableArray
view.tableController.notFirstTime = true

View file

@ -21,6 +21,7 @@
import UIKit
import Foundation
import linphonesw
import IQKeyboardManager
@objc class ConferenceSchedulingView: BackNextNavigationView, UICompositeViewDelegate {
@ -29,17 +30,19 @@ import linphonesw
static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription }
let datePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledDateTime,pickerMode: .date)
let datePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledDate,pickerMode: .date)
let timeZoneValue = StyledValuePicker(liveIndex: ConferenceSchedulingViewModel.shared.scheduledTimeZone,options: ConferenceSchedulingViewModel.timeZones.map({ (tzd: TimeZoneData) -> String in tzd.descWithOffset()}))
let durationValue = StyledValuePicker(liveIndex: ConferenceSchedulingViewModel.shared.scheduledDuration,options: ConferenceSchedulingViewModel.durationList.map({ (duration: Duration) -> String in duration.display}))
let timePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledDateTime,pickerMode: .time)
let timePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledTime,pickerMode: .time)
let descriptionInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_description_hint,liveValue: ConferenceSchedulingViewModel.shared.description)
let subjectInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_subject_hint, liveValue: ConferenceSchedulingViewModel.shared.subject,maxLines:1)
override func viewDidLoad() {
super.viewDidLoad(
backAction: {
ConferenceSchedulingViewModel.shared.reset()
PhoneMainView.instance().popView(self.compositeViewDescription())
},nextAction: {
self.gotoParticipantsListSelection()
@ -52,13 +55,11 @@ import linphonesw
contentView.addSubview(subjectLabel)
subjectLabel.alignParentLeft(withMargin: form_margin).alignParentTop().done()
let subjectInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_subject_hint, liveValue: ConferenceSchedulingViewModel.shared.subject,maxLines:1)
contentView.addSubview(subjectInput)
subjectInput.alignUnder(view: subjectLabel,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).height(form_input_height).done()
let schedulingStack = UIStackView()
schedulingStack.axis = .vertical
schedulingStack.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
contentView.addSubview(schedulingStack)
schedulingStack.alignUnder(view: subjectInput,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).done()
@ -132,19 +133,25 @@ import linphonesw
descriptionInput.textContainer.maximumNumberOfLines = 5
descriptionInput.textContainer.lineBreakMode = .byWordWrapping
scheduleForm.addSubview(descriptionInput)
descriptionInput.alignUnder(view: descriptionLabel,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).height(description_height).alignParentBottom(withMargin: form_margin*2).done()
scheduleForm.wrapContentY().done()
descriptionInput.alignUnder(view: descriptionLabel,withMargin: 2*form_margin).matchParentSideBorders(insetedByDx: form_margin).height(description_height).done()
// Sending methods
let viaChatSwitch = StyledCheckBox(liveValue: ConferenceSchedulingViewModel.shared.sendInviteViaChat)
contentView.addSubview(viaChatSwitch)
viaChatSwitch.alignParentLeft(withMargin: form_margin).alignUnder(view: schedulingStack,withMargin: 2*form_margin).done()
let viaChatView = UIView()
scheduleForm.addSubview(viaChatView)
viaChatView.alignUnder(view: descriptionInput,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).alignParentBottom(withMargin: form_margin*4).done()
let viaChatSwitch = StyledCheckBox()
viaChatSwitch.liveValue = ConferenceSchedulingViewModel.shared.sendInviteViaChat
viaChatView.addSubview(viaChatSwitch)
viaChatSwitch.alignParentLeft().done()
let viaChatLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_send_invite_chat)
contentView.addSubview(viaChatLabel)
viaChatLabel.toRightOf(viaChatSwitch,withLeftMargin: form_margin).alignUnder(view: schedulingStack,withMargin: 2*form_margin).alignHorizontalCenterWith(viaChatSwitch).done()
viaChatView.addSubview(viaChatLabel)
viaChatLabel.toRightOf(viaChatSwitch,withLeftMargin: form_margin).alignHorizontalCenterWith(viaChatSwitch).done()
viaChatView.wrapContentY().done()
/* Hidden as in Android 9.6.2022
let viaMailSwitch = StyledCheckBox(liveValue: ConferenceSchedulingViewModel.shared.sendInviteViaEmail)
@ -183,34 +190,51 @@ import linphonesw
let mandatoryLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_mandatory_field)
mandatoryLabel.addIndicatorIcon(iconName: "voip_mandatory", trailing: false)
contentView.addSubview(mandatoryLabel)
mandatoryLabel.alignUnder(view: viaChatSwitch,withMargin: 2*form_margin).centerX().matchParentSideBorders().done()
mandatoryLabel.textAlignment = .center
mandatoryLabel.alignParentBottom().done()
let lastView = UIView()
contentView.addSubview(lastView)
lastView.alignUnder(view: mandatoryLabel).alignParentBottom().done()
// Schedule for later observer
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { (forLater) in
self.subjectInput.setPlaceHolder(phText: forLater == true ? VoipTexts.conference_schedule_subject_hint : VoipTexts.conference_group_call_subject_hint)
scheduleForm.isHidden = forLater != true
super.titleLabel.text = forLater == true ? VoipTexts.conference_schedule_title : VoipTexts.conference_group_call_title
viaChatSwitch.isHidden = forLater != true
viaChatLabel.isHidden = forLater != true
super.titleLabel.text = forLater == true ? ConferenceSchedulingViewModel.shared.existingConfInfo.value != nil ? VoipTexts.conference_schedule_edit : VoipTexts.conference_schedule_title : VoipTexts.conference_group_call_title
mandatoryLabel.removeConstraints().done()
mandatoryLabel.alignUnder(view: forLater == true ? scheduleForm : scheduleForLater,withMargin: 2*self.form_margin).centerX().matchParentSideBorders().done()
}
ConferenceSchedulingViewModel.shared.existingConfInfo.readCurrentAndObserve { (confInfo) in
super.titleLabel.text = ConferenceSchedulingViewModel.shared.scheduleForLater.value == true ? ConferenceSchedulingViewModel.shared.existingConfInfo.value != nil ? VoipTexts.conference_schedule_edit : VoipTexts.conference_schedule_title : VoipTexts.conference_group_call_title
}
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.view.backgroundColor = VoipTheme.voipBackgroundBWColor.get()
schedulingStack.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
datePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledDateTime
datePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledDate
timeZoneValue.setIndex(index: ConferenceSchedulingViewModel.shared.scheduledTimeZone.value!)
durationValue.setIndex(index: ConferenceSchedulingViewModel.shared.scheduledDuration.value!)
timePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledDateTime
timePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledTime
descriptionInput.text = ConferenceSchedulingViewModel.shared.description.value
IQKeyboardManager.shared().isEnabled = true
}
override func viewWillDisappear(_ animated: Bool) {
IQKeyboardManager.shared().isEnabled = false
super.viewWillDisappear(animated)
}
func gotoParticipantsListSelection() {
let view: ChatConversationCreateView = self.VIEW(ChatConversationCreateView.compositeViewDescription());
let view: ChatConversationCreateView = self.VIEW(ChatConversationCreateView.compositeViewDescription())
view.unfragmentCompositeDescription()
let addresses = ConferenceSchedulingViewModel.shared.selectedAddresses.value!.map { (address) in String(address.asStringUriOnly()) }
view.tableController.contactsGroup = (addresses as NSArray).mutableCopy() as? NSMutableArray
view.isForEditing = false

View file

@ -22,16 +22,17 @@ import UIKit
import linphonesw
@objc class ConferenceWaitingRoomFragment: UIViewController, UICompositeViewDelegate { // Replaces CallView
@objc class ConferenceWaitingRoomView: UIViewController, UICompositeViewDelegate { // Replaces CallView
// Layout constants
let common_margin = 17.0
let switch_camera_button_size = 50
let switch_camera_button_size = 35
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
let layout_picker_inset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
var audioRoutesView : AudioRoutesView? = nil
@ -47,15 +48,19 @@ import linphonesw
var conferenceUrl : String? = nil
let conferenceSubject = MutableLiveData<String>()
let controlsView = ControlsView(showVideo: true, controlsViewModel: ConferenceWaitingRoomViewModel.sharedModel)
var layoutPicker : CallControlButton? = nil
let layoutPickerView = ConferenceLayoutPickerView(orientation: UIDevice.current.orientation)
static let compositeDescription = UICompositeViewDescription(ConferenceWaitingRoomFragment.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil)
static let compositeDescription = UICompositeViewDescription(ConferenceWaitingRoomView.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()
view.backgroundColor = VoipTheme.voipBackgroundColor.get()
view.addSubview(subject)
subject.centerX().alignParentTop(withMargin: common_margin).done()
@ -64,16 +69,15 @@ import linphonesw
}
// Controls
let controlsView = ControlsView(showVideo: true, controlsViewModel: ConferenceWaitingRoomViewModel.sharedModel)
view.addSubview(controlsView)
controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
// Layoout picker
let layoutPicker = CallControlButton(imageInset : UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8),buttonTheme: VoipTheme.conf_waiting_room_layout_picker, onClickAction: {
layoutPicker = CallControlButton(imageInset : layout_picker_inset,buttonTheme: VoipTheme.conf_waiting_room_layout_picker, onClickAction: {
ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.value = ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.value != true
})
view.addSubview(layoutPicker)
layoutPicker.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).alignParentRight(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
view.addSubview(layoutPicker!)
layoutPicker!.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).alignParentRight(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
ConferenceWaitingRoomViewModel.sharedModel.joinLayout.readCurrentAndObserve { layout in
var icon = ""
@ -85,18 +89,28 @@ import linphonesw
ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value = false
break
}
layoutPicker.applyTintedIcons(tintedIcons: [UIButton.State.normal.rawValue : TintableIcon(name: icon ,tintColor: LightDarkColor(.white,.white))])
self.layoutPicker?.applyTintedIcons(tintedIcons: [UIButton.State.normal.rawValue : TintableIcon(name: icon ,tintColor: LightDarkColor(.white,.white))])
}
ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.observe { video in
if (video == true && ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value == .AudioOnly) {
ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value = .ActiveSpeaker
}
}
let layoutPickerView = ConferenceLayoutPickerView()
view.addSubview(layoutPickerView)
layoutPickerView.alignAbove(view:layoutPicker,withMargin:button_spacing).alignVerticalCenterWith(layoutPicker).done()
ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.readCurrentAndObserve { show in
layoutPicker.isSelected = show == true
layoutPickerView.isHidden = show != true
self.layoutPicker?.isSelected = show == true
self.layoutPickerView.isHidden = show != true
if (show == true) {
self.view.bringSubviewToFront(layoutPickerView)
self.view.bringSubviewToFront(self.layoutPickerView)
}
}
self.view.onClick{
if (ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.value == true) {
ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.value = false
}
}
@ -131,16 +145,16 @@ import linphonesw
self.start.isEnabled = joining != true
//self.localVideo.isHidden = joining == true (UX question as video window goes black by the core, better black or hidden ?)
self.noVideoLabel.isHidden = joining == true
layoutPicker.isHidden = joining == true
self.layoutPicker?.isHidden = joining == true
if (joining == true) {
self.view.addSubview(self.conferenceJoinSpinner)
self.conferenceJoinSpinner.square(IncomingOutgoingCommonView.spinner_size).center().done()
self.conferenceJoinSpinner.square(AbstractIncomingOutgoingCallView.spinner_size).center().done()
self.conferenceJoinSpinner.startRotation()
controlsView.isHidden = true
self.controlsView.isHidden = true
} else {
self.conferenceJoinSpinner.stopRotation()
self.conferenceJoinSpinner.removeFromSuperview()
controlsView.isHidden = false
self.controlsView.isHidden = false
}
}
@ -151,9 +165,7 @@ import linphonesw
localVideo.contentMode = .scaleAspectFill
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
@ -182,7 +194,41 @@ import linphonesw
}
audioRoutesView!.alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
layoutRotatableElements()
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.view.backgroundColor = VoipTheme.voipBackgroundColor.get()
}
}
func layoutRotatableElements() {
layoutPickerView.removeConstraints().done()
localVideo.removeConstraints().done()
switchCamera.removeConstraints().done()
if ([.landscapeLeft, .landscapeRight].contains( UIDevice.current.orientation)) {
layoutPickerView.alignAbove(view:layoutPicker!,withMargin:button_spacing).alignVerticalCenterWith(layoutPicker!).done()
localVideo.matchParentSideBorders().alignParentBottom().alignParentTop().done()
localVideo.layer.cornerRadius = 0
switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins + (UIDevice.hasNotch() && UIDevice.current.orientation == .landscapeRight ? 30.0 : 0.0)).square(switch_camera_button_size).done()
} else {
layoutPickerView.alignAbove(view:layoutPicker!,withMargin:button_spacing).alignVerticalCenterWith(layoutPicker!).done()
localVideo.matchParentSideBorders(insetedByDx: content_inset).alignAbove(view:buttonsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).alignUnder(view: subject,withMargin: common_margin).done()
localVideo.layer.cornerRadius = center_view_corner_radius
switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins).square(switch_camera_button_size).done()
}
view.sendSubviewToBack(localVideo)
}
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
super.didRotate(from: fromInterfaceOrientation)
self.layoutRotatableElements()
Core.get().videoPreviewEnabled = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true
}
override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
Core.get().videoPreviewEnabled = false
}
override func viewWillAppear(_ animated: Bool) {

View file

@ -20,19 +20,25 @@
import UIKit
import Foundation
import linphonesw
import EventKit
import EventKitUI
@objc class ICSBubbleView: UIView {
@objc class ICSBubbleView: UIView, EKEventEditViewDelegate {
let corner_radius = 7.0
let border_width = 2.0
let rows_spacing = 6.0
let inner_padding = 8.0
let forward_reply_title_height = 10.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 inviteCancelled = StyledLabel(VoipTheme.conference_cancelled_title_font, VoipTexts.conference_cancel_title)
let inviteUpdated = StyledLabel(VoipTheme.conference_updated_title_font, VoipTexts.conference_update_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)
@ -42,40 +48,46 @@ import linphonesw
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.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.text = "\(data.time.value)" + (data.duration.value != nil ? " ( \(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
inviteTitle.isHidden = [.Cancelled,.Updated].contains(data.conferenceInfo.state)
inviteCancelled.isHidden = data.conferenceInfo.state != .Cancelled
inviteUpdated.isHidden = data.conferenceInfo.state != .Updated
join.isEnabled = data.isConferenceCancelled.value != true
}
}
}
init() {
super.init(frame:.zero)
layer.cornerRadius = corner_radius
clipsToBounds = true
backgroundColor = VoipTheme.voip_light_gray
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.backgroundColor = VoipTheme.chatBubbleBGColor.get()
}
let rows = UIStackView()
rows.axis = .vertical
rows.spacing = rows_spacing
addSubview(rows)
rows.addArrangedSubview(inviteTitle)
rows.addArrangedSubview(inviteCancelled)
rows.addArrangedSubview(inviteUpdated)
rows.addArrangedSubview(subject)
rows.addArrangedSubview(participants)
rows.addArrangedSubview(date)
@ -83,6 +95,8 @@ import linphonesw
rows.addArrangedSubview(descriptionTitle)
rows.addArrangedSubview(descriptionValue)
descriptionValue.numberOfLines = 5
addSubview(joinShare)
joinShare.axis = .horizontal
@ -92,19 +106,48 @@ import linphonesw
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())
let view : ConferenceWaitingRoomView = self.VIEW(ConferenceWaitingRoomView.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!)
let eventStore = EKEventStore()
eventStore.requestAccess( to: EKEntityType.event, completion:{(granted, error) in
DispatchQueue.main.async {
if (granted) && (error == nil) {
let event = EKEvent(eventStore: eventStore)
event.title = self.conferenceData?.subject.value
event.startDate = self.conferenceData?.rawDate
if let duration = self.conferenceData?.conferenceInfo.duration, duration > 0 {
event.endDate = event.startDate.addingTimeInterval(TimeInterval(duration*60))
} else {
event.endDate = event.startDate.addingTimeInterval(TimeInterval(3600))
}
event.calendar = eventStore.defaultCalendarForNewEvents
if let description = self.conferenceData?.description.value, description.count > 0 {
event.notes = description + "\n\n"
}
event.notes = (event.notes != nil ? event.notes! : "") + "\(VoipTexts.call_action_participants_list):\n\(self.conferenceData?.participantsExpanded.value)"
if let urlString = self.conferenceData?.conferenceInfo.uri?.asStringUriOnly() {
event.url = URL(string:urlString)
}
let addController = EKEventEditViewController()
addController.event = event
addController.eventStore = eventStore
PhoneMainView.instance().present(addController, animated: false)
addController.editViewDelegate = self;
} else {
VoipDialog.toast(message: VoipTexts.conference_unable_to_share_via_calendar)
}
}
})
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -115,7 +158,6 @@ import linphonesw
if (content.isIcalendar) {
if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: content) {
self.conferenceData = ScheduledConferenceData(conferenceInfo: conferenceInfo)
self.icsFile = content.filePath
}
}
}
@ -132,7 +174,76 @@ import linphonesw
}
@objc func setLayoutConstraints(view:UIView) {
matchDimensionsWith(view: view, insetedByDx: inner_padding).done()
matchBordersWith(view: view, insetedByDx: inner_padding).done()
}
@objc func updateTopLayoutConstraints(view:UIView, replyOrForward: Bool) {
updateTopBorderWith(view: view, inset: inner_padding + (replyOrForward ? forward_reply_title_height : 0.0)).done()
}
func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
controller.dismiss(animated: true, completion: nil)
}
@objc static func getSubjectFromContent(cmessage: OpaquePointer) -> String {
let message = ChatMessage.getSwiftObject(cObject: cmessage)
var subject = ""
message.contents.forEach { content in
if (content.isIcalendar) {
if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: content) {
subject = conferenceInfo.subject
}
}
}
return subject
}
@objc static func getConferenceInfo(cmessage: OpaquePointer) -> OpaquePointer? {
let message = ChatMessage.getSwiftObject(cObject: cmessage)
var result : OpaquePointer? = nil
message.contents.forEach { content in
if (content.isIcalendar) {
if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: content) {
result = conferenceInfo.getCobject
}
}
}
return result
}
@objc static func getConferenceSummary(cmessage: OpaquePointer) -> String? {
let message = ChatMessage.getSwiftObject(cObject: cmessage)
var subject:String? = nil
message.contents.forEach { content in
if (content.isIcalendar) {
if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: content) {
subject = conferenceInfo.state == .New ? VoipTexts.conference_invite_title + conferenceInfo.subject :
conferenceInfo.state == .Updated ? VoipTexts.conference_update_title + conferenceInfo.subject :
VoipTexts.conference_cancel_title + conferenceInfo.subject
}
}
}
return subject
}
@objc static func getDescriptionHeightFromContent(cmessage: OpaquePointer) -> CGFloat {
let message = ChatMessage.getSwiftObject(cObject: cmessage)
var height = 0.0
message.contents.forEach { content in
if (content.isIcalendar) {
if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: content) {
let description = NSString(string: conferenceInfo.description)
if (description.length > 0) {
let dummyTitle = StyledLabel(VoipTheme.conference_invite_desc_title_font, VoipTexts.conference_description_title)
let dummyLabel = StyledLabel(VoipTheme.conference_invite_desc_font)
let rect = CGSize(width: CGFloat(CONFERENCE_INVITATION_WIDTH-80), height: CGFloat.greatestFiniteMagnitude)
height = dummyTitle.intrinsicContentSize.height + description.boundingRect(with: rect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font: dummyLabel.font!], context: nil).height
}
}
}
}
return height
}
}

View file

@ -27,18 +27,20 @@ class ScheduledConferencesCell: UITableViewCell {
let corner_radius = 7.0
let border_width = 2.0
static let button_size = 40
let delete_checkbox_margin = 5
let clockIcon = UIImageView(image: UIImage(named: "conference_schedule_time_default"))
let clockIcon = UIImageView()
let timeDuration = StyledLabel(VoipTheme.conference_invite_desc_font)
let organiser = StyledLabel(VoipTheme.conference_invite_desc_font)
let subject = StyledLabel(VoipTheme.conference_invite_subject_font)
let participantsIcon = UIImageView(image: UIImage(named: "conference_schedule_participants_default"))
let subject = StyledLabel(VoipTheme.conference_list_subject_font)
let cancelledLabel = StyledLabel(VoipTheme.conference_cancelled_title_font)
let participantsIcon = UIImageView()
let participants = StyledLabel(VoipTheme.conference_invite_desc_font)
let infoConf = UIButton()
let descriptionTitle = StyledLabel(VoipTheme.conference_invite_desc_font, VoipTexts.conference_description_title)
let descriptionValue = StyledLabel(VoipTheme.conference_invite_desc_font)
let urlTitle = StyledLabel(VoipTheme.conference_invite_desc_font, VoipTexts.conference_schedule_address_title)
let descriptionTitle = StyledLabel(VoipTheme.conference_list_address_desc_font, VoipTexts.conference_description_title)
let descriptionValue = StyledLabel(VoipTheme.conference_list_address_desc_font)
let urlTitle = StyledLabel(VoipTheme.conference_list_address_desc_font, VoipTexts.conference_schedule_address_title)
let urlValue = StyledLabel(VoipTheme.conference_scheduling_font)
let copyLink = CallControlButton(width:button_size,height:button_size,buttonTheme: VoipTheme.scheduled_conference_action("voip_copy"))
let joinConf = FormButton(title:VoipTexts.conference_invite_join.uppercased(), backgroundStateColors: VoipTheme.button_green_background)
@ -47,17 +49,29 @@ class ScheduledConferencesCell: UITableViewCell {
var owningTableView : UITableView? = nil
let joinEditDelete = UIStackView()
let expandedRows = UIStackView()
let selectionCheckBox = StyledCheckBox()
let myContentView = UIView()
var conferenceData: ScheduledConferenceData? = nil {
didSet {
if let data = conferenceData {
timeDuration.text = "\(data.time.value)"+(data.duration.value != nil ? " ( \(data.duration.value) )" : "")
timeDuration.text = "\(data.time.value)"+(data.duration.value != nil ? " (\(data.duration.value))" : "")
organiser.text = VoipTexts.conference_schedule_organizer+data.organizer.value!
subject.text = data.subject.value!
cancelledLabel.text = data.isConferenceCancelled.value == true ? ( data.canEdit.value == true ? VoipTexts.conference_scheduled_cancelled_by_me: VoipTexts.conference_scheduled_cancelled_by_organizer) : nil
cancelledLabel.isHidden = data.isConferenceCancelled.value != true
descriptionValue.text = data.description.value!
urlValue.text = data.address.value!
self.joinConf.isHidden = data.isConferenceCancelled.value == true
self.editConf.isHidden = data.canEdit.value != true || data.isConferenceCancelled.value == true
self.urlTitle.isHidden = data.isConferenceCancelled.value == true
self.urlValue.isHidden = data.isConferenceCancelled.value == true
self.copyLink.isHidden = data.isConferenceCancelled.value == true
data.expanded.readCurrentAndObserve { expanded in
self.contentView.layer.borderWidth = expanded == true ? 2.0 : 0.0
self.myContentView.backgroundColor =
data.conferenceInfo.state == .Cancelled ? VoipTheme.voip_conference_cancelled_bg_color :
data.isFinished ? VoipTheme.backgroundColor3.get() : VoipTheme.backgroundColor4.get()
self.myContentView.layer.borderWidth = expanded == true ? 2.0 : 0.0
self.descriptionTitle.isHidden = expanded != true || self.descriptionValue.text?.count == 0
self.descriptionValue.isHidden = expanded != true || self.descriptionValue.text?.count == 0
self.infoConf.isSelected = expanded == true
@ -66,18 +80,18 @@ class ScheduledConferencesCell: UITableViewCell {
self.expandedRows.isHidden = expanded != true
self.joinEditDelete.isHidden = expanded != true
if let myAddress = Core.get().defaultAccount?.params?.identityAddress {
self.editConf.isHidden = expanded != true || data.conferenceInfo.organizer?.weakEqual(address2: myAddress) != true
self.editConf.isHidden = expanded != true || data.conferenceInfo.organizer?.weakEqual(address2: myAddress) != true || data.conferenceInfo.state == .Cancelled
} else {
self.editConf.isHidden = true
}
self.participants.removeConstraints().alignUnder(view: self.subject,withMargin: 15).toRightOf(self.participantsIcon,withLeftMargin:10).toRightOf(self.participantsIcon,withLeftMargin:10).toLeftOf(self.infoConf,withRightMargin: 15).done()
self.joinEditDelete.removeConstraints().alignUnder(view: self.expandedRows,withMargin: 15).alignParentRight(withMargin: 10).done()
self.participants.removeConstraints().alignUnder(view: self.subject,withMargin: 10).toRightOf(self.participantsIcon,withLeftMargin:10).toRightOf(self.participantsIcon,withLeftMargin:10).toLeftOf(self.infoConf,withRightMargin: 15).done()
self.joinEditDelete.removeConstraints().alignUnder(view: self.expandedRows,withMargin: 10).alignParentRight(withMargin: 10).done()
if (expanded == true) {
self.joinEditDelete.alignParentBottom(withMargin: 10).done()
} else {
self.participants.alignParentBottom(withMargin: 10).done()
}
self.selectionCheckBox.liveValue = data.selectedForDeletion
}
}
}
@ -86,46 +100,55 @@ class ScheduledConferencesCell: UITableViewCell {
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
contentView.addSubview(myContentView)
contentView.backgroundColor = .clear
backgroundColor = .clear
myContentView.layer.cornerRadius = corner_radius
myContentView.clipsToBounds = true
myContentView.backgroundColor = VoipTheme.header_background_color
myContentView.layer.borderColor = VoipTheme.primary_color.cgColor
myContentView.matchParentDimmensions(insetedBy: UIEdgeInsets(top: 5,left: 0,bottom: 5,right: 0)).done()
myContentView.addSubview(clockIcon)
clockIcon.alignParentTop(withMargin: 10).square(20).alignParentLeft(withMargin: 10).done()
myContentView.addSubview(timeDuration)
timeDuration.alignParentTop(withMargin: 10).toRightOf(clockIcon,withLeftMargin:10).alignHorizontalCenterWith(clockIcon).done()
myContentView.addSubview(organiser)
organiser.alignParentTop(withMargin: 10).toRightOf(timeDuration, withLeftMargin:10).alignParentRight(withMargin:10).alignHorizontalCenterWith(clockIcon).done()
contentView.addSubview(clockIcon)
clockIcon.alignParentTop(withMargin: 15).square(15).alignParentLeft(withMargin: 10).done()
contentView.addSubview(timeDuration)
timeDuration.alignParentTop(withMargin: 15).toRightOf(clockIcon,withLeftMargin:10).alignHorizontalCenterWith(clockIcon).done()
let subjectCancel = UIStackView()
subjectCancel.axis = .vertical
myContentView.addSubview(subjectCancel)
subjectCancel.alignUnder(view: timeDuration,withMargin: 10).matchParentSideBorders(insetedByDx: 10.0).done()
contentView.addSubview(organiser)
organiser.alignParentTop(withMargin: 15).toRightOf(timeDuration, withLeftMargin:10).alignParentRight(withMargin:10).alignHorizontalCenterWith(clockIcon).done()
subjectCancel.addArrangedSubview(cancelledLabel)
subjectCancel.addArrangedSubview(subject)
contentView.addSubview(subject)
subject.alignUnder(view: timeDuration,withMargin: 15).alignParentLeft(withMargin: 10).done()
contentView.addSubview(participantsIcon)
participantsIcon.alignUnder(view: subject,withMargin: 15).square(15).alignParentLeft(withMargin: 10).done()
myContentView.addSubview(participantsIcon)
participantsIcon.alignUnder(view: subject,withMargin: 5).square(25).alignParentLeft(withMargin: 10).done()
//infoConf.onClick {
contentView.onClick {
self.conferenceData?.toggleExpand()
self.owningTableView?.reloadData()
}
contentView.addSubview(infoConf)
myContentView.addSubview(infoConf)
infoConf.imageView?.contentMode = .scaleAspectFit
infoConf.alignUnder(view: subject,withMargin: 15).square(30).alignParentRight(withMargin: 10).alignHorizontalCenterWith(participantsIcon).done()
infoConf.alignUnder(view: subject,withMargin: 5).square(25).alignParentRight(withMargin: 10).done()
infoConf.applyTintedIcons(tintedIcons: VoipTheme.conference_info_button)
contentView.addSubview(participants)
participants.alignUnder(view: subject,withMargin: 15).toRightOf(participantsIcon,withLeftMargin:10).toRightOf(participantsIcon,withLeftMargin:10).toLeftOf(infoConf,withRightMargin: 15).done()
myContentView.addSubview(participants)
participants.alignUnder(view: subject,withMargin: 10).toRightOf(participantsIcon,withLeftMargin:10).toRightOf(participantsIcon,withLeftMargin:10).toLeftOf(infoConf,withRightMargin: 15).done()
expandedRows.axis = .vertical
expandedRows.spacing = 10
contentView.addSubview(expandedRows)
myContentView.addSubview(expandedRows)
expandedRows.alignUnder(view: participants,withMargin: 15).matchParentSideBorders(insetedByDx:10).done()
expandedRows.addArrangedSubview(descriptionTitle)
expandedRows.addArrangedSubview(descriptionValue)
@ -146,15 +169,15 @@ class ScheduledConferencesCell: UITableViewCell {
joinEditDelete.axis = .horizontal
joinEditDelete.spacing = 10
joinEditDelete.distribution = .equalSpacing
contentView.addSubview(joinEditDelete)
joinEditDelete.alignUnder(view: expandedRows,withMargin: 15).alignParentRight(withMargin: 10).done()
myContentView.addSubview(joinEditDelete)
joinEditDelete.alignUnder(view: expandedRows,withMargin: 10).alignParentRight(withMargin: 10).done()
joinEditDelete.addArrangedSubview(joinConf)
joinConf.width(150).done()
joinConf.onClick {
let view : ConferenceWaitingRoomFragment = self.VIEW(ConferenceWaitingRoomFragment.compositeViewDescription())
let view : ConferenceWaitingRoomView = self.VIEW(ConferenceWaitingRoomView.compositeViewDescription())
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
view.setDetails(subject: (self.conferenceData?.subject.value)!, url: (self.conferenceData?.address.value)!)
}
@ -166,9 +189,10 @@ class ScheduledConferencesCell: UITableViewCell {
VoipDialog.toast(message: VoipTexts.conference_edit_error)
return
}
let infoDate = Date(timeIntervalSince1970: Double(confData.conferenceInfo.dateTime))
ConferenceSchedulingViewModel.shared.reset()
ConferenceSchedulingViewModel.shared.scheduledDateTime.value = infoDate
let seconds = confData.conferenceInfo.dateTime % 86400
ConferenceSchedulingViewModel.shared.scheduledDate.value = Date(timeIntervalSince1970:TimeInterval(confData.conferenceInfo.dateTime - seconds))
ConferenceSchedulingViewModel.shared.scheduledTime.value = Date(timeIntervalSince1970:TimeInterval(seconds))
ConferenceSchedulingViewModel.shared.description.value = confData.description.value
ConferenceSchedulingViewModel.shared.subject.value = confData.subject.value
ConferenceSchedulingViewModel.shared.scheduledDuration.value = ConferenceSchedulingViewModel.durationList.firstIndex(where: {$0.value == confData.conferenceInfo.duration})
@ -177,7 +201,7 @@ class ScheduledConferencesCell: UITableViewCell {
confData.conferenceInfo.participants.forEach {
ConferenceSchedulingViewModel.shared.selectedAddresses.value?.append($0)
}
ConferenceSchedulingViewModel.shared.existingConfInfo = confData.conferenceInfo
ConferenceSchedulingViewModel.shared.existingConfInfo.value = confData.conferenceInfo
// TOODO TimeZone (as Android 14.6.2022) ConferenceSchedulingViewModel.shared.scheduledTimeZone.value = self.conferenceData?.timezone
let view : ConferenceSchedulingView = self.VIEW(ConferenceSchedulingView.compositeViewDescription())
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
@ -185,16 +209,35 @@ class ScheduledConferencesCell: UITableViewCell {
joinEditDelete.addArrangedSubview(deleteConf)
deleteConf.onClick {
let delete = ButtonAttributes(text:VoipTexts.conference_info_confirm_removal_delete, action: {
Core.get().deleteConferenceInformation(conferenceInfo: self.conferenceData!.conferenceInfo)
ScheduledConferencesViewModel.shared.computeConferenceInfoList()
self.owningTableView?.reloadData()
VoipDialog.toast(message: VoipTexts.conference_info_removed)
}, isDestructive:false)
let cancel = ButtonAttributes(text:VoipTexts.cancel, action: {}, isDestructive:true)
VoipDialog(message:VoipTexts.conference_info_confirm_removal, givenButtons: [cancel,delete]).show()
self.askConfirmationTodeleteEntry()
}
myContentView.addSubview(selectionCheckBox)
selectionCheckBox.alignParentRight(withMargin: delete_checkbox_margin).alignUnder(view:organiser, withMargin: delete_checkbox_margin).done()
ScheduledConferencesViewModel.shared.editionEnabled.readCurrentAndObserve { editing in
self.selectionCheckBox.isHidden = editing != true
}
onLongClick {
ScheduledConferencesViewModel.shared.editionEnabled.value = true
}
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.clockIcon.image = UIImage(named: "conference_schedule_time_default")?.tinted(with: VoipTheme.voipDrawableColor.get())
self.participantsIcon.image = UIImage(named: "conference_schedule_participants_default")?.tinted(with: VoipTheme.voipDrawableColor.get())
}
}
func askConfirmationTodeleteEntry() {
let delete = ButtonAttributes(text:VoipTexts.conference_info_confirm_removal_delete, action: {
self.deleteEntry()
VoipDialog.toast(message: VoipTexts.conference_info_removed)
}, isDestructive:false)
let cancel = ButtonAttributes(text:VoipTexts.cancel, action: {}, isDestructive:true)
VoipDialog(message:VoipTexts.conference_info_confirm_removal, givenButtons: [cancel,delete]).show()
}
func deleteEntry() {
self.conferenceData?.deleteConference()
ScheduledConferencesViewModel.shared.computeConferenceInfoList()
self.owningTableView?.reloadData()
}
required init?(coder: NSCoder) {

View file

@ -23,9 +23,12 @@ import Foundation
import linphonesw
@objc class ScheduledConferencesView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource, UITableViewDelegate {
let conferenceListView = UITableView()
let noConference = StyledLabel(VoipTheme.empty_list_font,VoipTexts.conference_no_schedule)
let filters = UIStackView()
let selectAllButton = CallControlButton(buttonTheme:VoipTheme.nav_button("deselect_all"))
let separator = UIView()
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 }
@ -35,17 +38,63 @@ import linphonesw
super.viewDidLoad(
backAction: {
PhoneMainView.instance().popView(self.compositeViewDescription())
if (ScheduledConferencesViewModel.shared.editionEnabled.value == true) {
ScheduledConferencesViewModel.shared.editionEnabled.value = false
} else {
PhoneMainView.instance().popView(self.compositeViewDescription())
}
},nextAction: {
ConferenceSchedulingViewModel.shared.reset()
PhoneMainView.instance().changeCurrentView(ConferenceSchedulingView.compositeDescription)
if (ScheduledConferencesViewModel.shared.editionEnabled.value == true) {
self.deleteSelection()
} else {
ConferenceSchedulingViewModel.shared.reset()
PhoneMainView.instance().changeCurrentView(ConferenceSchedulingView.compositeDescription)
}
},
nextActionEnableCondition: MutableLiveData(),
title:VoipTexts.conference_scheduled)
super.titleLabel.applyStyle(VoipTheme.navigation_header_font)
// Select all
selectAllButton.setImage(UIImage(named: "deselect_all"), for: .selected)
selectAllButton.setImage(UIImage(named: "select_all_default"), for: .normal)
topBar.addSubview(selectAllButton)
selectAllButton.toLeftOf(nextButton,withRightMargin: CGFloat(side_buttons_margin)).matchParentHeight().done()
// Filter buttons
let showTerminated = getFilterButton(title: VoipTexts.conference_scheduled_terminated_filter)
showTerminated.onClick {
ScheduledConferencesViewModel.shared.showTerminated.value = true
}
filters.addArrangedSubview(showTerminated)
let showScheduled = getFilterButton(title: VoipTexts.conference_scheduled_future_filter)
showScheduled.onClick {
ScheduledConferencesViewModel.shared.showTerminated.value = false
}
filters.addArrangedSubview(showScheduled)
ScheduledConferencesViewModel.shared.showTerminated.readCurrentAndObserve { it in
showTerminated.isSelected = it == true
showScheduled.isSelected = it != true
self.noConference.text = it != true ? VoipTexts.conference_no_schedule : VoipTexts.conference_no_terminated_schedule
ScheduledConferencesViewModel.shared.computeConferenceInfoList()
self.conferenceListView.reloadData()
self.noConference.isHidden = !ScheduledConferencesViewModel.shared.daySplitted.isEmpty
}
self.view.addSubview(filters)
filters.spacing = 10
filters.alignParentLeft(withMargin: 10).alignUnder(view: super.topBar,withMargin: self.form_margin).done()
super.nextButton.applyTintedIcons(tintedIcons: VoipTheme.conference_create_button)
self.view.addSubview(separator)
separator.matchParentSideBorders().height(1).alignUnder(view: filters,withMargin: self.form_margin).done()
// Conference list
self.view.addSubview(conferenceListView)
conferenceListView.alignUnder(view: filters).done()
conferenceListView.isScrollEnabled = true
conferenceListView.dataSource = self
conferenceListView.delegate = self
@ -56,11 +105,53 @@ import linphonesw
conferenceListView.allowsFocus = false
}
conferenceListView.separatorStyle = .singleLine
conferenceListView.separatorColor = .white
conferenceListView.backgroundColor = .clear
view.addSubview(noConference)
noConference.center().done()
ScheduledConferencesViewModel.shared.editionEnabled.readCurrentAndObserve { editing in
if (editing == true) {
self.selectAllButton.isSelected = false
self.selectAllButton.isHidden = false
super.nextButton.applyTintedIcons(tintedIcons: VoipTheme.generic_delete_button)
super.backButton.applyTintedIcons(tintedIcons: VoipTheme.generic_cancel)
self.nextButton.isEnabled = ScheduledConferencesViewModel.shared.conferences.value?.filter{$0.selectedForDeletion.value == true}.count ?? 0 > 0
} else {
self.selectAllButton.isHidden = true
ScheduledConferencesViewModel.shared.conferences.value?.forEach {$0.selectedForDeletion.value = false}
super.nextButton.applyTintedIcons(tintedIcons: VoipTheme.conference_create_button)
super.backButton.applyTintedIcons(tintedIcons: VoipTheme.generic_back)
self.nextButton.isEnabled = true
}
}
self.selectAllButton.onClick {
let selectIt = !self.selectAllButton.isSelected
ScheduledConferencesViewModel.shared.conferences.value?.forEach {$0.selectedForDeletion.value = selectIt}
}
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.view.backgroundColor = VoipTheme.voipBackgroundBWColor.get()
self.separator.backgroundColor = VoipTheme.separatorColor.get()
self.conferenceListView.separatorColor = .clear
self.conferenceListView.reloadData()
}
}
func getFilterButton(title:String) -> UIButton {
let filter_button_height = 35.0
let button = ButtonWithStateBackgrounds(backgroundStateColors: VoipTheme.button_conference_list_filter)
button.setTitle(title, for: .normal)
button.setTitleColor(.black, for: .normal)
button.setTitleColor(VoipTheme.primary_color, for: .selected)
button.height(filter_button_height).done()
button.layer.cornerRadius = filter_button_height / 2
button.clipsToBounds = true
button.applyTitleStyle(VoipTheme.conf_list_filter_button_font)
button.width(0).done()
button.addSidePadding()
return button
}
@ -69,9 +160,10 @@ import linphonesw
super.viewWillAppear(animated)
self.conferenceListView.reloadData()
self.conferenceListView.removeConstraints().done()
self.conferenceListView.matchParentSideBorders(insetedByDx: 10).alignUnder(view: super.topBar,withMargin: self.form_margin).alignParentBottom().done()
self.conferenceListView.matchParentSideBorders(insetedByDx: 10).alignUnder(view: separator).alignParentBottom().done()
noConference.isHidden = !ScheduledConferencesViewModel.shared.daySplitted.isEmpty
super.nextButton.isEnabled = Core.get().defaultAccount != nil
ScheduledConferencesViewModel.shared.editionEnabled.value = false
}
// TableView datasource delegate
@ -99,12 +191,7 @@ import linphonesw
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed())
let day = daysArray[indexPath.section]
guard let data = ScheduledConferencesViewModel.shared.daySplitted[day]?[indexPath.row] else {
return UITableView.automaticDimension
}
return data.expanded.value! ? UITableView.automaticDimension : 100
return UITableView.automaticDimension
}
@ -117,8 +204,38 @@ import linphonesw
}
cell.conferenceData = data
cell.owningTableView = tableView
data.selectedForDeletion.readCurrentAndObserve { selected in
let selectedCount = ScheduledConferencesViewModel.shared.conferences.value?.filter{$0.selectedForDeletion.value == true}.count ?? 0
let totalCount = ScheduledConferencesViewModel.shared.conferences.value?.count ?? 0
self.nextButton.isEnabled = ScheduledConferencesViewModel.shared.editionEnabled.value == false || selectedCount > 0
self.selectAllButton.isSelected = selectedCount == totalCount
}
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let cell = tableView.cellForRow(at: indexPath) as! ScheduledConferencesCell
cell.askConfirmationTodeleteEntry()
}
}
func deleteSelection () {
let selectedCount = ScheduledConferencesViewModel.shared.conferences.value?.filter{$0.selectedForDeletion.value == true}.count ?? 0
let delete = ButtonAttributes(text:VoipTexts.conference_info_confirm_removal_delete, action: {
ScheduledConferencesViewModel.shared.conferences.value?.forEach {
$0.deleteConference()
}
ScheduledConferencesViewModel.shared.computeConferenceInfoList()
self.conferenceListView.reloadData()
VoipDialog.toast(message: selectedCount == 1 ? VoipTexts.conference_info_removed : VoipTexts.conference_infos_removed)
ScheduledConferencesViewModel.shared.editionEnabled.value = false
}, isDestructive:false)
let cancel = ButtonAttributes(text:VoipTexts.cancel, action: {}, isDestructive:true)
VoipDialog(message:selectedCount == 1 ? VoipTexts.conference_info_confirm_removal : VoipTexts.conference_infos_confirm_removal, givenButtons: [cancel,delete]).show()
}
}

View file

@ -0,0 +1,54 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
import UIKit
import Photos
import linphonesw
@objc class SnapkitBridge: NSObject {
@objc static func matchParentDimensions(view:UIView) {
view.matchParentDimmensions().done()
}
@objc static func matchParentDimensions(view:UIView,leftInset:CGFloat) {
view.matchParentDimmensions(insetedBy: UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: 0)).done()
}
@objc static func matchParentDimensions(view:UIView,topInset:CGFloat) {
view.matchParentDimmensions(insetedBy: UIEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0)).done()
}
@objc static func height(view:UIView,heiht:Int) {
view.height(heiht).done()
}
@objc static func square(view:UIView,size:Int) {
view.square(size).done()
}
@objc static func alignParentLeft(view:UIView) {
view.alignParentLeft().done()
}
@objc static func centerY(view:UIView) {
view.centerY().done()
}
}

View file

@ -22,9 +22,9 @@ import SnapKit
import UIKit
extension UIButton {
func addSidePadding(p:CGFloat = 10) {
func addSidePadding(p:CGFloat = 10) { // Requires a width to be set prior to this ! SnapKit does not support updateOrCreate.
if let w = titleLabel?.textWidth {
width(w+2*p).done()
updateWidth(w+2*p).done()
}
}

View file

@ -48,10 +48,32 @@ extension UIDevice {
}
static func notchHeight() -> CGFloat {
guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top else {
guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top, let sidePadding = UIApplication.shared.keyWindow?.safeAreaInsets.left else {
return 0
}
return topPadding
return [.landscapeRight,.landscapeLeft].contains(UIDevice.current.orientation) ? sidePadding : topPadding
}
static func switchedDisplayMode() -> Bool {
let displayMode = UserDefaults.standard.string(forKey: "displayMode")
if #available(iOS 13.0, *) {
if UITraitCollection.current.userInterfaceStyle == .light {
UserDefaults.standard.set("light", forKey: "displayMode")
} else {
UserDefaults.standard.set("dark", forKey: "displayMode")
}
}
return displayMode != nil && displayMode != UserDefaults.standard.string(forKey: "displayMode")
}
}
@objc class UIDeviceBridge : NSObject {
static let displayModeSwitched = MutableLiveData<Bool>()
@objc static func switchedDisplayMode() -> Bool {
return UIDevice.switchedDisplayMode()
}
@objc static func notifyDisplayModeSwitch() {
displayModeSwitched.notifyValue()
}
}

View file

@ -29,4 +29,10 @@ extension UILabel {
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: myFont], context: nil)
return ceil(labelSize.width)
}
func addSidePadding(p:CGFloat = 5.0) {
if let w = textWidth {
width(w+2*p).done()
}
}
}

View file

@ -39,6 +39,14 @@ extension UIView {
return self
}
func squareMax(_ size:Int) -> UIView {
snp.makeConstraints { (make) in
make.height.lessThanOrEqualTo(size).priority(.high)
make.width.equalTo(snp.height).priority(.high)
}
return self
}
func makeHeightMatchWidth() -> UIView {
snp.makeConstraints { (make) in
@ -47,6 +55,13 @@ extension UIView {
return self
}
func makeWidthMatchHeight() -> UIView {
snp.makeConstraints { (make) in
make.width.equalTo(snp.height)
}
return self
}
func size(w:CGFloat,h:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.width.equalTo(w)
@ -55,6 +70,14 @@ extension UIView {
return self
}
func updateSize(w:CGFloat,h:CGFloat) -> UIView {
snp.updateConstraints { (make) in
make.width.equalTo(w)
make.height.equalTo(h)
}
return self
}
func height(_ h:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.height.equalTo(h)
@ -73,6 +96,13 @@ extension UIView {
return self
}
func updateWidth(_ h:CGFloat) -> UIView {
snp.updateConstraints { (make) in
make.width.equalTo(h)
}
return self
}
func width(_ h:Int) -> UIView {
return width(CGFloat(h))
}
@ -122,7 +152,17 @@ extension UIView {
return self
}
func matchDimensionsWith(view:UIView, insetedByDx:CGFloat = 0) -> UIView {
func matchParentDimmensions(insetedBy:UIEdgeInsets) -> UIView {
snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(insetedBy.left)
make.top.equalToSuperview().offset(insetedBy.top)
make.right.equalToSuperview().offset(-insetedBy.right)
make.bottom.equalToSuperview().offset(-insetedBy.bottom)
}
return self
}
func matchBordersWith(view:UIView, insetedByDx:CGFloat = 0) -> UIView {
snp.makeConstraints { (make) in
make.left.top.equalTo(view).offset(insetedByDx)
make.right.bottom.equalTo(view).offset(-insetedByDx)
@ -130,6 +170,13 @@ extension UIView {
return self
}
func updateTopBorderWith(view:UIView, inset:CGFloat = 0) -> UIView {
snp.updateConstraints { (make) in
make.top.equalTo(view).offset(inset)
}
return self
}
func matchParentEdges() -> UIView {
snp.makeConstraints { (make) in
make.edges.equalToSuperview()
@ -222,10 +269,21 @@ extension UIView {
return self
}
func updateAlignParentBottom(withMargin:CGFloat = 0.0) -> UIView {
snp.updateConstraints { (make) in
make.bottom.equalToSuperview().offset(-withMargin)
}
return self
}
func alignParentBottom(withMargin:Int) -> UIView {
return alignParentBottom(withMargin:CGFloat(withMargin))
}
func updateAlignParentBottom(withMargin:Int) -> UIView {
return updateAlignParentBottom(withMargin:CGFloat(withMargin))
}
func alignAbove(view:UIView, withMargin:CGFloat = 0.0) -> UIView {
snp.makeConstraints { (make) in
make.bottom.equalTo(view.snp.top).offset(-withMargin)
@ -258,6 +316,13 @@ extension UIView {
return self
}
func updateAlignParentLeft(withMargin:CGFloat = 0.0) -> UIView {
snp.updateConstraints { (make) in
make.left.equalToSuperview().offset(withMargin)
}
return self
}
func alignParentLeft(withMargin:Int) -> UIView {
return alignParentLeft(withMargin:CGFloat(withMargin))
}
@ -269,10 +334,23 @@ extension UIView {
return self
}
func updateAlignParentRight(withMargin:CGFloat = 0) -> UIView {
snp.updateConstraints { (make) in
make.right.equalToSuperview().offset(-withMargin)
}
return self
}
func alignParentRight(withMargin:CGFloat) -> UIView {
return alignParentRight(withMargin:Int(withMargin))
}
func alignRightWith(_ view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.right.equalTo(view.snp.right)
}
return self
}
func toRightOf(_ view:UIView, withLeftMargin:Int = 0) -> UIView {
snp.makeConstraints { (make) in
@ -363,12 +441,28 @@ extension UIView {
return self
}
func wrapContent(inset:UIEdgeInsets) -> UIView {
subviews.first?.snp.makeConstraints({ make in
make.left.equalToSuperview().offset(inset.left)
})
subviews.last?.snp.makeConstraints({ make in
make.right.equalToSuperview().offset(-inset.right)
})
subviews.first?.snp.makeConstraints({ make in
make.top.equalToSuperview().offset(inset.top)
})
subviews.last?.snp.makeConstraints({ make in
make.bottom.equalToSuperview().offset(-inset.bottom)
})
return self
}
func done() {
// to avoid the unused variable warning
}
// Onclick
// Single click
class TapGestureRecognizer: UITapGestureRecognizer {
var action : (()->Void)? = nil
}
@ -387,6 +481,22 @@ extension UIView {
sender.action!()
}
// Long click
class LongPressGestureRecognizer: UILongPressGestureRecognizer {
var action : (()->Void)? = nil
}
func onLongClick(action : @escaping ()->Void ){
let tap = LongPressGestureRecognizer(target: self , action: #selector(self.handleLongClick(_:)))
tap.action = action
tap.cancelsTouchesInView = false
self.addGestureRecognizer(tap)
self.isUserInteractionEnabled = true
}
@objc func handleLongClick(_ sender: LongPressGestureRecognizer) {
sender.action!()
}
func VIEW<T>( _ desc: UICompositeViewDescription) -> T{
return PhoneMainView.instance().mainViewController.getCachedController(desc.name) as! T
}

View file

@ -23,15 +23,16 @@ import linphonesw
extension Address {
func initials() -> String? {
var initials = initials(displayName: addressBookEnhancedDisplayName())
var initials = Address.initials(displayName: addressBookEnhancedDisplayName())
if (initials == nil || initials!.isEmpty) {
initials = String(username.prefix(1))
}
return initials
}
private func initials(displayName: String?) -> String? { // Basic ImproveMe
return displayName?.components(separatedBy: " ")
static func initials(displayName: String?) -> String? { // Basic ImproveMe
let separator = displayName?.contains(" ") == true ? " " : "."
return displayName?.components(separatedBy: separator)
.reduce("") {
($0.isEmpty ? "" : "\($0.first?.uppercased() ?? "")") +
($1.isEmpty ? "" : "\($1.first?.uppercased() ?? "")")

View file

@ -39,10 +39,7 @@ extension Call {
extension Call : CustomStringConvertible {
public var description: String {
if let callId = callLog?.callId {
return "<Call-ID: \(callId)>"
}
return "<Raw pointer:\(Unmanaged.passUnretained(self).toOpaque())>"
return "<Call-ID: \(callLog?.callId ?? "pending") pointer:\(Unmanaged.passUnretained(self).toOpaque()) is conference:\(conference != nil) >"
}
}

View file

@ -0,0 +1,139 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
import UIKit
import linphonesw
@objc class FileUtil: NSObject {
public class func bundleFilePath(_ file: NSString) -> String? {
return Bundle.main.path(forResource: file.deletingPathExtension, ofType: file.pathExtension)
}
public class func bundleFilePathAsUrl(_ file: NSString) -> URL? {
if let bPath = bundleFilePath(file) {
return URL.init(fileURLWithPath: bPath)
}
return nil
}
public class func documentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
public class func libraryDirectory() -> URL {
let paths = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
public class func sharedContainerUrl(appGroupName:String) -> URL {
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)!
}
@objc public class func ensureDirectoryExists(path:String) {
if !FileManager.default.fileExists(atPath: path) {
do {
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error)
}
}
}
public class func ensureFileExists(path:String) {
if !FileManager.default.fileExists(atPath: path) {
FileManager.default.createFile(atPath: path, contents: nil, attributes: nil)
}
}
public class func fileExists(path:String) -> Bool {
return FileManager.default.fileExists(atPath: path)
}
public class func fileExistsAndIsNotEmpty(path:String) -> Bool {
guard FileManager.default.fileExists(atPath: path) else {return false}
do {
let attribute = try FileManager.default.attributesOfItem(atPath: path)
if let size = attribute[FileAttributeKey.size] as? NSNumber {
return size.doubleValue > 0
} else {
return false
}
} catch {
print(error)
return false
}
}
public class func write(string:String, toPath:String) {
do {
try string.write(to: URL(fileURLWithPath:toPath), atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
}
}
public class func delete(path:String) {
do {
try FileManager.default.removeItem(atPath: path)
print("FIle \(path) was removed")
} catch {
print("Error deleting file at path \(path) error is \(error)")
}
}
public class func mkdir(path:String) {
do {
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
print("Dir \(path) was created")
} catch {
print("Error creating dir at path \(path) error is \(error)")
}
}
public class func copy(_ fromPath:String, _ toPath: String, overWrite:Bool) {
do {
if (overWrite && fileExists(path: toPath)) {
delete(path: toPath)
}
try FileManager.default.copyItem(at: URL(fileURLWithPath:fromPath), to: URL(fileURLWithPath:toPath))
} catch {
print(error)
}
}
// For debugging
public class func showListOfFilesInSharedDir(appGroupName:String) {
let fileManager = FileManager.default
do {
let fileURLs = try fileManager.contentsOfDirectory(at: FileUtil.sharedContainerUrl(appGroupName: appGroupName), includingPropertiesForKeys: nil)
fileURLs.forEach{print($0)}
} catch {
print("Error while enumerating files \(error.localizedDescription)")
}
}
}

View file

@ -192,7 +192,7 @@ extension ProviderDelegate: CXProviderDelegate {
if (UIApplication.shared.applicationState != .active) {
CallManager.instance().backgroundContextCall = call
CallManager.instance().backgroundContextCameraIsEnabled = call!.params?.videoEnabled ?? false
CallManager.instance().backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
call?.cameraEnabled = false // Disable camera while app is not on foreground
}
CallManager.instance().callkitAudioSessionActivated = false
@ -205,8 +205,10 @@ extension ProviderDelegate: CXProviderDelegate {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId
let call = CallManager.instance().callByCallId(callId: callId)
action.fulfill()
if (call == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "CXSetHeldCallAction: no call !")
action.fail()
return
}
@ -215,33 +217,52 @@ extension ProviderDelegate: CXProviderDelegate {
try CallManager.instance().lc?.leaveConference()
Log.directLog(BCTBX_LOG_DEBUG, text: "CallKit: call-id: [\(String(describing: callId))] leaving conference")
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
return
}
let state = action.isOnHold ? "Paused" : "Resumed"
Log.directLog(BCTBX_LOG_DEBUG, text: "CallKit: Call with call-id: [\(String(describing: callId))] and UUID: [\(uuid)] paused status changed to: [\(state)]")
if (action.isOnHold) {
if (call!.params?.localConferenceMode ?? false) {
return
}
CallManager.instance().speakerBeforePause = CallManager.instance().isSpeakerEnabled()
try call!.pause()
} else {
if (CallManager.instance().lc?.conference != nil && CallManager.instance().lc?.callsNb ?? 0 > 1) {
try CallManager.instance().lc?.enterConference()
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
action.fulfill()
}else{
let state = action.isOnHold ? "Paused" : "Resumed"
Log.directLog(BCTBX_LOG_DEBUG, text: "CallKit: Call with call-id: [\(String(describing: callId))] and UUID: [\(uuid)] paused status changed to: [\(state)]")
if (action.isOnHold) {
CallManager.instance().speakerBeforePause = CallManager.instance().isSpeakerEnabled()
try call!.pause()
// fullfill() the action now to indicate to Callkit that this call is no longer active, even if the
// SIP transaction is not completed yet. At this stage, the media streams are off.
// If callkit is not aware that the pause action is completed, it will terminate this call if we
// attempt to resume another one.
action.fulfill()
} else {
try call!.resume()
if (CallManager.instance().lc?.conference != nil && CallManager.instance().lc?.callsNb ?? 0 > 1) {
try CallManager.instance().lc?.enterConference()
action.fulfill()
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
} else {
try call!.resume()
// We'll notify callkit that the action is fulfilled when receiving the 200Ok, which is the point
// where we actually start the media streams.
CallManager.instance().actionToFulFill = action;
// HORRIBLE HACK HERE - PLEASE APPLE FIX THIS !!
// When resuming a SIP call after a native call has ended remotely, didActivate: audioSession
// is never called.
// It looks like in this case, it is implicit.
// As a result we have to notify the Core that the AudioSession is active.
// The SpeakerBox demo application written by Apple exhibits this behavior.
// https://developer.apple.com/documentation/callkit/making_and_receiving_voip_calls_with_callkit
// We can clearly see there that startAudio() is called immediately in the CXSetHeldCallAction
// handler, while it is called from didActivate: audioSession otherwise.
// Callkit's design is not consistent, or its documentation imcomplete, wich is somewhat disapointing.
//
Log.directLog(BCTBX_LOG_DEBUG, text: "Assuming AudioSession is active when executing a CXSetHeldCallAction with isOnHold=false.")
CallManager.instance().lc?.activateAudioSession(actived: true)
CallManager.instance().callkitAudioSessionActivated = true
}
}
}
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call set held (paused or resumed) \(uuid) failed because \(error)")
action.fail()
}
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
do {
let uuid = action.callUUID

View file

@ -79,14 +79,9 @@ import linphonesw
scrollView.alignUnder(view: topBar, withMargin: content_margin_top).alignParentBottom().matchParentSideBorders().done()
scrollView.addSubview(contentView)
contentView.matchBordersOf(view: view).alignParentBottom().alignParentTop().done() // don't forget a bottom constraint b/w last element of contentview and contentview
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
topBar.backgroundColor = VoipTheme.voipToolbarBackgroundColor.get()
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.topBar.backgroundColor = VoipTheme.voipToolbarBackgroundColor.get()
}
}
}

View file

@ -57,7 +57,10 @@ class TimestampUtils {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .none
return dateFormatter.string(from: date)
let dayFormatter = DateFormatter()
dayFormatter.dateFormat = "EEEE"
let day = dayFormatter.string(from: date)
return day.prefix(1).uppercased() + day.dropFirst()+" "+dateFormatter.string(from: date)
}
static func timeToString(date:Date) -> String {

View file

@ -19,7 +19,7 @@
import Foundation
class LightDarkColor {
@objc class LightDarkColor : NSObject {
var light: UIColor
var dark : UIColor
init(_ l:UIColor,_ d:UIColor){
@ -27,7 +27,7 @@ class LightDarkColor {
dark = d
}
func get() -> UIColor {
@objc func get() -> UIColor {
if #available(iOS 13.0, *) {
if UITraitCollection.current.userInterfaceStyle == .light {
return light

Some files were not shown because too many files have changed in this diff Show more