Compare commits
118 commits
6.1.0-alph
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5c970d177 | ||
|
|
986276c04f | ||
|
|
221f0b10d7 | ||
|
|
aed6876065 | ||
|
|
4f7e4b0c36 | ||
|
|
6575a4b0f2 | ||
|
|
9723c0de04 | ||
|
|
990d2f36af | ||
|
|
8d5c0ce79b | ||
|
|
61931138b2 | ||
|
|
1dbbe6a53d | ||
|
|
4c45a9bb1e | ||
|
|
4feae7fccd | ||
|
|
1501dae019 | ||
|
|
84ec09173c | ||
|
|
4cd63b53b2 | ||
|
|
d4b10d38ae | ||
|
|
6827bdc1dc | ||
|
|
ed08190ff4 | ||
|
|
57b7b857bc | ||
|
|
f16a0f42ae | ||
|
|
c7ddf2d8d0 | ||
|
|
7decb12a3f | ||
|
|
da1abd75ae | ||
|
|
33b379285f | ||
|
|
b45a328ad4 | ||
|
|
c9f2915ca0 | ||
|
|
fe8432f128 | ||
|
|
36fa752ccf | ||
|
|
0f8df65dff | ||
|
|
221e3cbb4b | ||
|
|
2b64c26518 | ||
|
|
5492a3e3a9 | ||
|
|
773102e4bd | ||
|
|
b462657a77 | ||
|
|
5d13a2b49d | ||
|
|
7bdb8fa92d | ||
|
|
0daba4fe03 | ||
|
|
07dbf407b0 | ||
|
|
7972fd7c1f | ||
|
|
fa1f8386b4 | ||
|
|
a421d90d0c | ||
|
|
b904f71f79 | ||
|
|
e748a001bf | ||
|
|
4fbb43f38c | ||
|
|
bcee4439f5 | ||
|
|
b49445d50d | ||
|
|
a3d2c74592 | ||
|
|
4f6dc2c0c7 | ||
|
|
08ab9a1078 | ||
|
|
db72bdf242 | ||
|
|
9413f6f5dc | ||
|
|
7237a5f4a7 | ||
|
|
8c9784a21d | ||
|
|
bb4134ede0 | ||
|
|
8cd322ff0e | ||
|
|
f8b7e3b319 | ||
|
|
aefa334038 | ||
|
|
9559701a5e | ||
|
|
5bb757d150 | ||
|
|
ac6b478eb1 | ||
|
|
41f9db8199 | ||
|
|
4b3d99245f | ||
|
|
aae7c290be | ||
|
|
0a06644473 | ||
|
|
b75db70d19 | ||
|
|
9beadaadd9 | ||
|
|
888b75a2d4 | ||
|
|
068b93cf1c | ||
|
|
e9eebbd45a | ||
|
|
1c3680df65 | ||
|
|
1389565b83 | ||
|
|
4fde1933dd | ||
|
|
a33a8666ae | ||
|
|
3b2ce2ed4b | ||
|
|
4cf1dbd8b5 | ||
|
|
1d0df11c61 | ||
|
|
ee1c09e98f | ||
|
|
8bd84ca8a5 | ||
|
|
1e53619eaa | ||
|
|
112029d0df | ||
|
|
8737bcb40d | ||
|
|
5837e5e85d | ||
|
|
e81699052a | ||
|
|
147682a0e5 | ||
|
|
0dfdb5551c | ||
|
|
880967d2e3 | ||
|
|
b36f220911 | ||
|
|
a2564c4cc0 | ||
|
|
d3984b4b1a | ||
|
|
91c6f7a311 | ||
|
|
de9656d94d | ||
|
|
43d63ae081 | ||
|
|
c7f7606859 | ||
|
|
54ca929f88 | ||
|
|
c6291f61d6 | ||
|
|
42997d6891 | ||
|
|
014e7c575e | ||
|
|
773696260b | ||
|
|
304f46ba02 | ||
|
|
e0229fde0f | ||
|
|
108df56148 | ||
|
|
c7a9edf25f | ||
|
|
90f2ad7e58 | ||
|
|
1485e7a574 | ||
|
|
8f131ad335 | ||
|
|
bc1c737973 | ||
|
|
926107061a | ||
|
|
3aa9419c5d | ||
|
|
a5bef93587 | ||
|
|
525e705b59 | ||
|
|
a7e2ebb600 | ||
|
|
14635250f8 | ||
|
|
357418287c | ||
|
|
3c56f09130 | ||
|
|
5b6e2e1c0a | ||
|
|
53a135a4b6 | ||
|
|
5035c6a924 |
102
CHANGELOG.md
|
|
@ -10,51 +10,121 @@ 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.
|
||||
|
||||
## [6.0.0] - 2025-03-11
|
||||
|
||||
## [6.1.0] - 2025-12-08
|
||||
|
||||
### Added
|
||||
- LDAP and CardDAV settings
|
||||
- Advanced settings in third-party SIP account login view
|
||||
- Phone number calls in contact details
|
||||
- Recording player
|
||||
- Recording list
|
||||
- Automatic Git commit, branch, and tag info for Help views
|
||||
- Message deletion feature
|
||||
- Message editing feature
|
||||
|
||||
### Changed
|
||||
- Launch Screen (Splash Screen)
|
||||
- Updated translations from Weblate
|
||||
- Updated SPM dependencies
|
||||
- Disabled meetings view when audio/video conference factory address is missing
|
||||
- Moved disable_chat_feature to UI section
|
||||
- Updated configuration files
|
||||
- Updated last message text in conversation list
|
||||
- Updated PopupView UI
|
||||
- Display core call logs instead of account call logs when the user has only one account
|
||||
|
||||
### Fixed
|
||||
- International prefix reset in settings
|
||||
- Prevent editing of read-only (LDAP) contacts
|
||||
- Crash when editing a contact (safe unwrapping of friend/photo)
|
||||
- EditContactFragment view and “+” allowed in dialer
|
||||
- Dial plan selector and default dial plan
|
||||
- Encryption update when call state changes
|
||||
- Unread message counter update in onMessageRetracted
|
||||
- French translation of message_content_deleted
|
||||
- Stop composing when the user stops typing
|
||||
- Refresh presence info in history detail
|
||||
- Refresh displayed friend when the contacts list is updated
|
||||
- Prefix handling in interpretUrl when calling a phone number
|
||||
- SIP contacts filter
|
||||
|
||||
|
||||
## [6.0.2] - 2025-09-26
|
||||
|
||||
### Added
|
||||
- Advanced settings to third-party SIP account login view
|
||||
- Burger button to open the side menu
|
||||
|
||||
### Changed
|
||||
- Layout icon in conference call
|
||||
- Translations from Weblate
|
||||
- Disable meetings view when audio/video conference factory address is missing
|
||||
|
||||
### Fixed
|
||||
- EditContactFragment view and allow '+' in number dialer
|
||||
- Dial plan selector and dial plan default
|
||||
- Crash when editing a contact by safely unwrapping friend/photo
|
||||
- Meeting scheduler
|
||||
|
||||
|
||||
## [6.0.1] - 2025-09-12
|
||||
|
||||
### Added
|
||||
- Done button toolbar to number pads
|
||||
- Help view to login page
|
||||
|
||||
### Changed
|
||||
- textToImage updated to generate image on the core queue
|
||||
- Send DTMF execution moved to the core queue
|
||||
- Use saveImage on core queue
|
||||
- Use point_to_point string for encrypted calls in conference
|
||||
- Hide VFS setting
|
||||
|
||||
### Fixed
|
||||
- Avatar photo refresh
|
||||
- onEphemeralMessageTimerStarted callback
|
||||
- Crash in updateEncryption by safely handling optional currentCall
|
||||
- Sorted list in MagicSearch when friend is nil
|
||||
- Friend list refresh triggered by onPresenceReceived
|
||||
- Crash when adding or removing SIP addresses and phone numbers in EditContactFragment
|
||||
- awaitDataWrite execution on main queue
|
||||
- Crash by copying Friend addresses and phone numbers before removal
|
||||
- Ensure core is On before stopping it on background entry
|
||||
- textToImage crash
|
||||
|
||||
|
||||
## [6.0.0] - 2025-09-01
|
||||
|
||||
6.0.0 release is a complete rework of Linphone, with a fully redesigned UI, so it is impossible to list everything here.
|
||||
|
||||
### Changed
|
||||
- Separated threads: Contrary to previous versions, our SDK is now running in it's own thread, meaning it won't freeze the UI anymore in case of heavy work, thus reducing the number of ANR and greatly increasing the fluidity of the app.
|
||||
- Separated threads: Contrary to previous versions, our SDK is now running in it's own thread, meaning it won't freeze the UI anymore in case of heavy work.
|
||||
- Asymmetrical video : you no longer need to send your own camera feed to receive the one from the remote end of the call, and vice versa.
|
||||
- Improved multi account: you'll only see history, conversations, meetings etc... related to currently selected account, and you can switch the default account in two clicks.
|
||||
- Call transfer: Blind & Attended call transfer have been merged into one: during a call, if you initiate a transfer action, either pick another call to do the attended transfer or select a contact from the list (you can input a SIP URI not already in the suggestions list) to start a blind transfer.
|
||||
- User can only send up to 12 files in a single chat message.
|
||||
- IMDNs are now only sent to the message sender, preventing huge traffic in large groups, and thus the delivery status icon for received messages is now hidden in groups (as it was in 1-1 conversations).
|
||||
- Settings: a lot of them are gone, the one that are still there have been reworked to increase user friendliness.
|
||||
- Default screen (between contacts, call history, conversations & meetings list) will change depending on where you were when the app was paused or killed, and you will return to that last visited screen on the next startup.
|
||||
- Gradle files have been migrated from Groovy to Kotlin DSL, and dependencies are now in a separated file (libs.versions.toml).
|
||||
- Account creation no longer allows you to use your phone number as username, but it is still required to provide it to receive activation code by SMS.
|
||||
- Minimum supported iOS version is now 15.
|
||||
- Some settings have changed name and/or section in linphonerc file.
|
||||
|
||||
### Added
|
||||
- Contacts trust: contacts for which all devices have been validated through a ZRTP call with SAS exchange are now highlighted with a blue circle (and with a red one in case of mistrust). That trust is now handled at contact level (instead of conversation level in previous versions).
|
||||
- Media & documents exchanged in a conversation can be easily found through a dedicated screen.
|
||||
- A brand new chat message search feature has been added to conversations.
|
||||
- You can now react to a chat message using any emoji.
|
||||
- If next message is also a voice recording, playback will automatically start after the currently playing one ends.
|
||||
- Chat while in call: a shortcut to a conversation screen with the remote.
|
||||
- Chat while in a conference: if the conference has a text stream enabled, you can chat with the other participants of the conference while it lasts. At the end, you'll find the messages history in the call history (and not in the list of conversations).
|
||||
- Auto export of media to native gallery even when auto download is enabled (but still not if VFS is enabled nor for ephemeral messages).
|
||||
- Save / export document & media from ephemeral messages will be disabled, and secure policy that prevents screenshots will be enforced in file viewer even if the setting is disabled.
|
||||
- Notification showing upload/download of files shared through chat will let user know the progress and keep the app alive during that process.
|
||||
- Screen sharing in conference: only desktop app starting with 6.0 version is able to start it, but on mobiles you'll be able to see it.
|
||||
- Security focus: security & trust is more visible than ever, and unsecure conversations & calls are even more visible than before.
|
||||
- OpenID: when used with a SSO compliant SIP server (such as Flexisip), we support single-sign-on login.
|
||||
- MWI support: display and allow to call your voicemail when you have new messages (if supported by your VoIP provider and properly configured in your account params).
|
||||
- CCMP support: if you configure a CCMP server URL in your accounts params, it will be used when scheduling meetings & to fetch list of meetings you've organized/been invited to.
|
||||
- Devices list: check on which device your sip.linphone.org account is connected and the last connection date & time (like on subscribe.linphone.org).
|
||||
- Protobuf dependency to allow logging native crashes stack traces at next app startup.
|
||||
- Dialer & in-call numpad show letters under the digit.
|
||||
|
||||
### Removed
|
||||
- Dialer: the previous home screen (dialer) has been removed, you'll find it as an input option in the new start call screen.
|
||||
- Peer-to-peer: a SIP account (sip.linphone.org or other) is now required.
|
||||
- Contacts: we no longer add contacts created in-app in the native addressbook (WRITE_CONTACTS permission was removed), but we still import them if you grant us the READ_CONTACTS permission.
|
||||
|
||||
### Fixed
|
||||
- AAudio driver no longer causes delay when switching between devices (SDK fix).
|
||||
|
||||
## [5.2.0] - 2023-28-12
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#4e6074" viewBox="0 0 256 256"><path d="M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM72,48v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80H48V48ZM208,208H48V96H208V208Zm-96-88v64a8,8,0,0,1-16,0V132.94l-4.42,2.22a8,8,0,0,1-7.16-14.32l16-8A8,8,0,0,1,112,120Zm59.16,30.45L152,176h16a8,8,0,0,1,0,16H136a8,8,0,0,1-6.4-12.8l28.78-38.37A8,8,0,1,0,145.07,132a8,8,0,1,1-13.85-8A24,24,0,0,1,176,136,23.76,23.76,0,0,1,171.16,150.45Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM72,48v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80H48V48ZM208,208H48V96H208V208Zm-96-88v64a8,8,0,0,1-16,0V132.94l-4.42,2.22a8,8,0,0,1-7.16-14.32l16-8A8,8,0,0,1,112,120Zm59.16,30.45L152,176h16a8,8,0,0,1,0,16H136a8,8,0,0,1-6.4-12.8l28.78-38.37A8,8,0,1,0,145.07,132a8,8,0,1,1-13.85-8A24,24,0,0,1,176,136,23.76,23.76,0,0,1,171.16,150.45Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 604 B |
21
Linphone/Assets.xcassets/layout.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "layout.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/layout.imageset/layout.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V96H40V56ZM40,112H96v88H40Zm176,88H112V112H216v88Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 274 B |
21
Linphone/Assets.xcassets/list.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "list.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/list.imageset/list.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM40,72H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 277 B |
21
Linphone/Assets.xcassets/music-notes.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "music-notes.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/music-notes.imageset/music-notes.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><circle cx="180" cy="164" r="28" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><circle cx="52" cy="196" r="28" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="208" y1="72" x2="80" y2="104" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="80 196 80 56 208 24 208 164" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
|
||||
|
After Width: | Height: | Size: 664 B |
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" fill="#4e6074" viewBox="0 0 256 256"><path d="M222.37,158.46l-47.11-21.11-.13-.06a16,16,0,0,0-15.17,1.4,8.12,8.12,0,0,0-.75.56L134.87,160c-15.42-7.49-31.34-23.29-38.83-38.51l20.78-24.71c.2-.25.39-.5.57-.77a16,16,0,0,0,1.32-15.06l0-.12L97.54,33.64a16,16,0,0,0-16.62-9.52A56.26,56.26,0,0,0,32,80c0,79.4,64.6,144,144,144a56.26,56.26,0,0,0,55.88-48.92A16,16,0,0,0,222.37,158.46ZM176,208A128.14,128.14,0,0,1,48,80,40.2,40.2,0,0,1,82.87,40a.61.61,0,0,0,0,.12l21,47L83.2,111.86a6.13,6.13,0,0,0-.57.77,16,16,0,0,0-1,15.7c9.06,18.53,27.73,37.06,46.46,46.11a16,16,0,0,0,15.75-1.14,8.44,8.44,0,0,0,.74-.56L168.89,152l47,21.05h0s.08,0,.11,0A40.21,40.21,0,0,1,176,208Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M222.37,158.46l-47.11-21.11-.13-.06a16,16,0,0,0-15.17,1.4,8.12,8.12,0,0,0-.75.56L134.87,160c-15.42-7.49-31.34-23.29-38.83-38.51l20.78-24.71c.2-.25.39-.5.57-.77a16,16,0,0,0,1.32-15.06l0-.12L97.54,33.64a16,16,0,0,0-16.62-9.52A56.26,56.26,0,0,0,32,80c0,79.4,64.6,144,144,144a56.26,56.26,0,0,0,55.88-48.92A16,16,0,0,0,222.37,158.46ZM176,208A128.14,128.14,0,0,1,48,80,40.2,40.2,0,0,1,82.87,40a.61.61,0,0,0,0,.12l21,47L83.2,111.86a6.13,6.13,0,0,0-.57.77,16,16,0,0,0-1,15.7c9.06,18.53,27.73,37.06,46.46,46.11a16,16,0,0,0,15.75-1.14,8.44,8.44,0,0,0,.74-.56L168.89,152l47,21.05h0s.08,0,.11,0A40.21,40.21,0,0,1,176,208Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 735 B After Width: | Height: | Size: 733 B |
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#4e6074" viewBox="0 0 256 256"><path d="M176,160a39.89,39.89,0,0,0-28.62,12.09l-46.1-29.63a39.8,39.8,0,0,0,0-28.92l46.1-29.63a40,40,0,1,0-8.66-13.45l-46.1,29.63a40,40,0,1,0,0,55.82l46.1,29.63A40,40,0,1,0,176,160Zm0-128a24,24,0,1,1-24,24A24,24,0,0,1,176,32ZM64,152a24,24,0,1,1,24-24A24,24,0,0,1,64,152Zm112,72a24,24,0,1,1,24-24A24,24,0,0,1,176,224Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M176,160a39.89,39.89,0,0,0-28.62,12.09l-46.1-29.63a39.8,39.8,0,0,0,0-28.92l46.1-29.63a40,40,0,1,0-8.66-13.45l-46.1,29.63a40,40,0,1,0,0,55.82l46.1,29.63A40,40,0,1,0,176,160Zm0-128a24,24,0,1,1-24,24A24,24,0,0,1,176,32ZM64,152a24,24,0,1,1,24-24A24,24,0,0,1,64,152Zm112,72a24,24,0,1,1,24-24A24,24,0,0,1,176,224Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
21
Linphone/Assets.xcassets/trash.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "trash.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/trash.imageset/trash.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 415 B |
21
Linphone/Assets.xcassets/voicemail.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "voicemail.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/voicemail.imageset/voicemail.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M200,72a56,56,0,0,0-39.14,96H95.14A56,56,0,1,0,56,184H200a56,56,0,0,0,0-112ZM16,128a40,40,0,1,1,40,40A40,40,0,0,1,16,128Zm184,40a40,40,0,1,1,40-40A40,40,0,0,1,200,168Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 291 B |
|
|
@ -113,6 +113,8 @@ final class ContactsManager: ObservableObject {
|
|||
core.addFriendList(list: tempRemoteFriendList)
|
||||
}
|
||||
}
|
||||
|
||||
self.refreshCardDavContacts()
|
||||
}
|
||||
|
||||
let store = CNContactStore()
|
||||
|
|
@ -154,25 +156,22 @@ final class ContactsManager: ObservableObject {
|
|||
|
||||
let imageThumbnail = UIImage(data: contact.thumbnailImageData ?? Data())
|
||||
if let image = imageThumbnail {
|
||||
DispatchQueue.main.async {
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: contact.givenName + contact.familyName,
|
||||
prefix: "",
|
||||
contact: newContact, linphoneFriend: self.nativeAddressBookFriendList, existingFriend: nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: contact.givenName + contact.familyName,
|
||||
prefix: "",
|
||||
contact: newContact, linphoneFriend: self.nativeAddressBookFriendList, existingFriend: nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
} else {
|
||||
self.textToImageInMainThread(firstName: contact.givenName, lastName: contact.familyName) { image in
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: contact.givenName + contact.familyName,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: self.nativeAddressBookFriendList, existingFriend: nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
let image = self.textToImage(firstName: contact.givenName, lastName: contact.familyName)
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: contact.givenName + contact.familyName,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: self.nativeAddressBookFriendList, existingFriend: nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -197,154 +196,165 @@ final class ContactsManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func textToImageInMainThread(firstName: String, lastName: String, completion: @escaping (UIImage) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
let lblNameInitialize = UILabel()
|
||||
lblNameInitialize.frame.size = CGSize(width: 200.0, height: 200.0)
|
||||
lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 80)
|
||||
lblNameInitialize.textColor = UIColor(Color.grayMain2c600)
|
||||
func textToImage(firstName: String?, lastName: String?) -> UIImage {
|
||||
let firstInitial = firstName?.first.map { String($0) } ?? ""
|
||||
let lastInitial = lastName?.first.map { String($0) } ?? ""
|
||||
let textToDisplay = (firstInitial + lastInitial).uppercased()
|
||||
|
||||
let size = CGSize(width: 200, height: 200)
|
||||
let renderer = UIGraphicsImageRenderer(size: size)
|
||||
|
||||
return renderer.image { _ in
|
||||
let rect = CGRect(origin: .zero, size: size)
|
||||
|
||||
let textToDisplay = (firstName.first.map { String($0) } ?? "") + (lastName.first.map { String($0) } ?? "")
|
||||
UIColor(Color.grayMain2c200).setFill()
|
||||
UIBezierPath(roundedRect: rect, cornerRadius: 10).fill()
|
||||
|
||||
lblNameInitialize.text = textToDisplay.uppercased()
|
||||
lblNameInitialize.textAlignment = .center
|
||||
lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200)
|
||||
lblNameInitialize.layer.cornerRadius = 10.0
|
||||
lblNameInitialize.clipsToBounds = true
|
||||
|
||||
UIGraphicsBeginImageContext(lblNameInitialize.frame.size)
|
||||
defer { UIGraphicsEndImageContext() }
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
completion(UIImage())
|
||||
return
|
||||
}
|
||||
|
||||
lblNameInitialize.layer.render(in: context)
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
|
||||
completion(image)
|
||||
let paragraph = NSMutableParagraphStyle()
|
||||
paragraph.alignment = .center
|
||||
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
.font: UIFont(name: "NotoSans-ExtraBold", size: 80) ?? UIFont.boldSystemFont(ofSize: 80),
|
||||
.foregroundColor: UIColor(Color.grayMain2c600),
|
||||
.paragraphStyle: paragraph
|
||||
]
|
||||
|
||||
let textSize = textToDisplay.size(withAttributes: attributes)
|
||||
let textRect = CGRect(
|
||||
x: (size.width - textSize.width) / 2,
|
||||
y: (size.height - textSize.height) / 2,
|
||||
width: textSize.width,
|
||||
height: textSize.height
|
||||
)
|
||||
|
||||
textToDisplay.draw(in: textRect, withAttributes: attributes)
|
||||
}
|
||||
}
|
||||
|
||||
func textToImage(firstName: String, lastName: String) -> UIImage {
|
||||
let lblNameInitialize = UILabel()
|
||||
lblNameInitialize.frame.size = CGSize(width: 200.0, height: 200.0)
|
||||
lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 80)
|
||||
lblNameInitialize.textColor = UIColor(Color.grayMain2c600)
|
||||
|
||||
var textToDisplay = ""
|
||||
if firstName.first != nil {
|
||||
textToDisplay += String(firstName.first!)
|
||||
}
|
||||
if lastName.first != nil {
|
||||
textToDisplay += String(lastName.first!)
|
||||
func imageFromBase64(_ base64String: String) -> UIImage? {
|
||||
let cleanedString: String
|
||||
if let range = base64String.range(of: "base64,") {
|
||||
cleanedString = String(base64String[range.upperBound...])
|
||||
} else {
|
||||
cleanedString = base64String
|
||||
}
|
||||
|
||||
lblNameInitialize.text = textToDisplay.uppercased()
|
||||
lblNameInitialize.textAlignment = .center
|
||||
lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200)
|
||||
lblNameInitialize.layer.cornerRadius = 10.0
|
||||
guard let imageData = Data(base64Encoded: cleanedString, options: .ignoreUnknownCharacters) else {
|
||||
print("Error: failed to decode Base64 string")
|
||||
return nil
|
||||
}
|
||||
|
||||
var IBImgViewUserProfile = UIImage()
|
||||
UIGraphicsBeginImageContext(lblNameInitialize.frame.size)
|
||||
lblNameInitialize.layer.render(in: UIGraphicsGetCurrentContext()!)
|
||||
IBImgViewUserProfile = UIGraphicsGetImageFromCurrentImageContext()!
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
return IBImgViewUserProfile
|
||||
return UIImage(data: imageData)
|
||||
}
|
||||
|
||||
func saveImage(image: UIImage, name: String, prefix: String, contact: Contact, linphoneFriend: String, existingFriend: Friend?, completion: @escaping () -> Void) {
|
||||
func saveImage(image: UIImage, name: String, prefix: String, contact: Contact, linphoneFriend: String, existingFriend: Friend?, editingFriend: Bool = false, completion: @escaping () -> Void) {
|
||||
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
|
||||
return
|
||||
}
|
||||
|
||||
awaitDataWrite(data: data, name: name, prefix: prefix) { _, result in
|
||||
self.saveFriend(result: result, contact: contact, existingFriend: existingFriend) { resultFriend in
|
||||
if resultFriend != nil {
|
||||
if linphoneFriend != self.nativeAddressBookFriendList && existingFriend == nil {
|
||||
if let linphoneFL = self.linphoneFriendList, linphoneFriend == linphoneFL.displayName {
|
||||
_ = linphoneFL.addFriend(linphoneFriend: resultFriend!)
|
||||
} else if let linphoneFL = self.tempRemoteFriendList {
|
||||
_ = linphoneFL.addFriend(linphoneFriend: resultFriend!)
|
||||
}
|
||||
} else if existingFriend == nil {
|
||||
if let friendListTmp = self.friendList {
|
||||
_ = friendListTmp.addLocalFriend(linphoneFriend: resultFriend!)
|
||||
let base64Tmp = existingFriend?.friendList?.type == .CardDAV || linphoneAddressBookFriendList != CorePreferences.friendListInWhichStoreNewlyCreatedFriends
|
||||
|
||||
awaitDataWrite(data: data, name: name, prefix: prefix, base64: base64Tmp) { result in
|
||||
if existingFriend?.friendList?.type != .CardDAV
|
||||
|| (existingFriend?.friendList?.type == .CardDAV && linphoneFriend == self.linphoneAddressBookFriendList)
|
||||
|| (editingFriend && linphoneFriend == CorePreferences.friendListInWhichStoreNewlyCreatedFriends) {
|
||||
self.saveFriend(result: result, contact: contact, existingFriend: existingFriend) { resultFriend in
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
if let friend = resultFriend {
|
||||
if linphoneFriend != self.nativeAddressBookFriendList && existingFriend == nil {
|
||||
if let linphoneFL = self.linphoneFriendList, linphoneFriend == linphoneFL.displayName {
|
||||
_ = linphoneFL.addFriend(linphoneFriend: friend)
|
||||
} else if let linphoneFL = core.friendsLists.first(where: { $0.type == .CardDAV && $0.displayName == CorePreferences.friendListInWhichStoreNewlyCreatedFriends }) {
|
||||
if linphoneFL.type == .CardDAV {
|
||||
_ = linphoneFL.addFriend(linphoneFriend: friend)
|
||||
}
|
||||
} else if let linphoneFL = self.tempRemoteFriendList {
|
||||
if friend.friendList?.type != .CardDAV {
|
||||
_ = linphoneFL.addFriend(linphoneFriend: friend)
|
||||
}
|
||||
}
|
||||
} else if existingFriend == nil {
|
||||
if let friendListTmp = self.friendList {
|
||||
_ = friendListTmp.addLocalFriend(linphoneFriend: friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
completion()
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func saveFriend(result: String, contact: Contact, existingFriend: Friend?, completion: @escaping (Friend?) -> Void) {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
do {
|
||||
// Create or use existing friend
|
||||
let friend = try existingFriend ?? core.createFriend()
|
||||
|
||||
|
||||
// Strong capture in closure to avoid threading issues
|
||||
friend.edit()
|
||||
|
||||
friend.nativeUri = contact.identifier
|
||||
try friend.setName(newValue: contact.firstName + " " + contact.lastName)
|
||||
|
||||
let friendvCard = friend.vcard
|
||||
|
||||
if friendvCard != nil {
|
||||
friendvCard!.givenName = contact.firstName
|
||||
friendvCard!.familyName = contact.lastName
|
||||
// Safely update vCard
|
||||
if let vcard = friend.vcard {
|
||||
vcard.givenName = contact.firstName
|
||||
vcard.familyName = contact.lastName
|
||||
}
|
||||
|
||||
friend.organization = contact.organizationName
|
||||
|
||||
var friendAddresses: [Address] = []
|
||||
friend.addresses.forEach({ address in
|
||||
friend.removeAddress(address: address)
|
||||
})
|
||||
contact.sipAddresses.forEach { sipAddress in
|
||||
if !sipAddress.isEmpty {
|
||||
let address = core.interpretUrl(url: sipAddress, applyInternationalPrefix: true)
|
||||
|
||||
if address != nil && ((friendAddresses.firstIndex(where: {$0.asString() == address?.asString()})) == nil) {
|
||||
friend.addAddress(address: address!)
|
||||
friendAddresses.append(address!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var friendPhoneNumbers: [PhoneNumber] = []
|
||||
friend.phoneNumbersWithLabel.forEach({ phoneNumber in
|
||||
friend.removePhoneNumberWithLabel(phoneNumber: phoneNumber)
|
||||
})
|
||||
contact.phoneNumbers.forEach { phone in
|
||||
do {
|
||||
if (friendPhoneNumbers.firstIndex(where: {$0.num == phone.num})) == nil {
|
||||
let labelDrop = String(phone.numLabel.dropFirst(4).dropLast(4))
|
||||
let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: labelDrop)
|
||||
friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber)
|
||||
friendPhoneNumbers.append(phone)
|
||||
}
|
||||
} catch let error {
|
||||
print("\(#function) - Failed to create friend phone number for \(phone.numLabel):", error)
|
||||
}
|
||||
}
|
||||
|
||||
friend.photo = "file:/" + result
|
||||
friend.organization = contact.organizationName
|
||||
friend.jobTitle = contact.jobTitle
|
||||
|
||||
// Clear existing addresses and add new ones
|
||||
friend.addresses.forEach { friend.removeAddress(address: $0) }
|
||||
for sipAddress in contact.sipAddresses where !sipAddress.isEmpty {
|
||||
if let address = core.interpretUrl(url: sipAddress, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)),
|
||||
!friend.addresses.contains(where: { $0.asString() == address.asString() }) {
|
||||
friend.addAddress(address: address)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear existing phone numbers and add new ones
|
||||
friend.phoneNumbersWithLabel.forEach { friend.removePhoneNumberWithLabel(phoneNumber: $0) }
|
||||
for phone in contact.phoneNumbers {
|
||||
do {
|
||||
let labelDrop = String(phone.numLabel.dropFirst(4).dropLast(4))
|
||||
let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: labelDrop)
|
||||
friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber)
|
||||
} catch {
|
||||
print("saveFriend - Failed to create friend phone number for \(phone.numLabel):", error)
|
||||
}
|
||||
}
|
||||
|
||||
// Set photo
|
||||
friend.photo = (friend.friendList?.type != .CardDAV && self.linphoneAddressBookFriendList == CorePreferences.friendListInWhichStoreNewlyCreatedFriends ? "file:/" : "") + result
|
||||
|
||||
// Linphone subscription settings
|
||||
try friend.setSubscribesenabled(newValue: false)
|
||||
try friend.setIncsubscribepolicy(newValue: .SPDeny)
|
||||
|
||||
// Commit changes
|
||||
friend.done()
|
||||
|
||||
// Notify completion safely
|
||||
completion(friend)
|
||||
} catch let error {
|
||||
print("Failed to enumerate contact", error)
|
||||
} catch {
|
||||
print("saveFriend - Failed to save friend:", error)
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getImagePath(friendPhotoPath: String) -> URL {
|
||||
let friendPath = String(friendPhotoPath.dropFirst(6))
|
||||
|
|
@ -354,25 +364,40 @@ final class ContactsManager: ObservableObject {
|
|||
return imagePath
|
||||
}
|
||||
|
||||
func awaitDataWrite(data: Data, name: String, prefix: String, completion: @escaping ((), String) -> Void) {
|
||||
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
|
||||
|
||||
if directory != nil {
|
||||
DispatchQueue.main.async {
|
||||
do {
|
||||
if let urlName = URL(string: name + prefix) {
|
||||
let imagePath = urlName.absoluteString.replacingOccurrences(of: "%", with: "")
|
||||
|
||||
let decodedData: () = try data.write(to: directory!.appendingPathComponent(imagePath + ".png"))
|
||||
|
||||
completion(decodedData, imagePath + ".png")
|
||||
} else {
|
||||
completion((), "")
|
||||
}
|
||||
} catch {
|
||||
print("Error: ", error)
|
||||
completion((), "")
|
||||
func awaitDataWrite(data: Data, name: String, prefix: String, base64: Bool? = false, completion: @escaping (String) -> Void) {
|
||||
guard let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
completion("")
|
||||
return
|
||||
}
|
||||
if base64 == false {
|
||||
do {
|
||||
let fileName = name + prefix + ".png"
|
||||
|
||||
let fileURL = directory.appendingPathComponent(fileName.replacingOccurrences(of: " ", with: ""))
|
||||
|
||||
try data.write(to: fileURL)
|
||||
completion(fileName.replacingOccurrences(of: " ", with: ""))
|
||||
} catch {
|
||||
print("Error writing image: \(error)")
|
||||
completion("")
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
let fileName = name + prefix + ".png"
|
||||
|
||||
let fileURL = directory.appendingPathComponent(fileName.replacingOccurrences(of: " ", with: ""))
|
||||
|
||||
try data.write(to: fileURL)
|
||||
|
||||
if prefix.isEmpty {
|
||||
let base64 = data.base64EncodedString()
|
||||
completion("data:image/jpeg;base64,\(base64)")
|
||||
} else {
|
||||
completion("")
|
||||
}
|
||||
} catch {
|
||||
print("Error writing image: \(error)")
|
||||
completion("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -399,15 +424,22 @@ final class ContactsManager: ObservableObject {
|
|||
var friend: Friend?
|
||||
|
||||
if let friendList = self.friendList {
|
||||
friend = friendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) })
|
||||
friend = friendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) || $0.phoneNumbers.contains(where: { $0 == address.username }) })
|
||||
}
|
||||
|
||||
if friend == nil, let linphoneFriendList = self.linphoneFriendList {
|
||||
friend = linphoneFriendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) })
|
||||
friend = linphoneFriendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) || $0.phoneNumbers.contains(where: { $0 == address.username }) })
|
||||
}
|
||||
if friend == nil, let tempRemoteFriendList = self.tempRemoteFriendList {
|
||||
friend = tempRemoteFriendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) })
|
||||
friend = tempRemoteFriendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) || $0.phoneNumbers.contains(where: { $0 == address.username }) })
|
||||
}
|
||||
|
||||
CoreContext.shared.mCore.friendsLists.forEach { friendList in
|
||||
if friendList.type == .CardDAV {
|
||||
friend = friendList.friends.first(where: { $0.addresses.contains(where: { $0.asStringUriOnly() == sipUri }) || $0.phoneNumbers.contains(where: { $0 == address.username }) })
|
||||
}
|
||||
}
|
||||
|
||||
return friend
|
||||
}
|
||||
|
||||
|
|
@ -425,6 +457,13 @@ final class ContactsManager: ObservableObject {
|
|||
friendList.updateSubscriptions()
|
||||
}
|
||||
|
||||
if let friendListDelegateToDelete = self.friendListDelegate {
|
||||
CoreContext.shared.mCore.friendsLists.forEach { friendList in
|
||||
friendList.removeDelegate(delegate: friendListDelegateToDelete)
|
||||
}
|
||||
}
|
||||
self.friendListDelegate = nil
|
||||
|
||||
let friendListDelegateTmp = FriendListDelegateStub(
|
||||
onContactCreated: { (friendList: FriendList, linphoneFriend: Friend) in
|
||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactCreated")
|
||||
|
|
@ -436,55 +475,142 @@ final class ContactsManager: ObservableObject {
|
|||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onContactUpdated")
|
||||
},
|
||||
onSyncStatusChanged: { (friendList: FriendList, status: FriendList.SyncStatus?, message: String?) in
|
||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onSyncStatusChanged")
|
||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onSyncStatusChanged \(friendList.displayName ?? "No Display Name") -- Status: \(status != nil ? String(describing: status!) : "No Status")")
|
||||
if status == .Successful {
|
||||
if friendList.displayName != self.nativeAddressBookFriendList && friendList.displayName != self.linphoneAddressBookFriendList {
|
||||
if let tempRemoteFriendList = self.tempRemoteFriendList {
|
||||
tempRemoteFriendList.friends.forEach { friend in
|
||||
_ = tempRemoteFriendList.removeFriend(linphoneFriend: friend)
|
||||
tempRemoteFriendList.friends.forEach { friend in
|
||||
if let friendAddress = friend.address,
|
||||
friendList.friends.contains(where: { $0.address?.weakEqual(address2: friendAddress) == true }) {
|
||||
_ = tempRemoteFriendList.removeFriend(linphoneFriend: friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
|
||||
friendList.friends.forEach { friend in
|
||||
dispatchGroup.enter()
|
||||
let addressTmp = friend.address?.clone()?.asStringUriOnly() ?? ""
|
||||
|
||||
let newContact = Contact(
|
||||
identifier: UUID().uuidString,
|
||||
firstName: friend.name ?? addressTmp,
|
||||
lastName: "",
|
||||
organizationName: "",
|
||||
jobTitle: "",
|
||||
firstName: friend.firstName ?? addressTmp,
|
||||
lastName: friend.lastName ?? "",
|
||||
organizationName: friend.organization ?? "",
|
||||
jobTitle: friend.jobTitle ?? "",
|
||||
displayName: friend.address?.displayName ?? "",
|
||||
sipAddresses: friend.addresses.map { $0.asStringUriOnly() },
|
||||
phoneNumbers: [],
|
||||
phoneNumbers: friend.phoneNumbersWithLabel.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.phoneNumber)},
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
self.textToImageInMainThread(firstName: friend.name ?? addressTmp, lastName: "") { image in
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: friend.name ?? addressTmp,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: nil) {
|
||||
dispatchGroup.leave()
|
||||
let image: UIImage?
|
||||
|
||||
if let photo = friend.photo, !photo.isEmpty, friendList.type == .CardDAV {
|
||||
if let imageTmp = self.imageFromBase64(photo) {
|
||||
image = imageTmp
|
||||
if let image = image {
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: friend.name ?? addressTmp,
|
||||
prefix: "",
|
||||
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: friend.friendList?.type == .CardDAV ? friend : nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
image = self.textToImage(firstName: friend.name ?? addressTmp, lastName: "")
|
||||
if let image = image {
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: friend.name ?? addressTmp,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: friend.friendList?.type == .CardDAV ? friend : nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
image = self.textToImage(firstName: friend.name ?? addressTmp, lastName: "")
|
||||
if let image = image {
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: friend.name ?? addressTmp,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: friend.friendList?.type == .CardDAV ? friend : nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
MagicSearchSingleton.shared.searchForContacts()
|
||||
if let linphoneFL = self.tempRemoteFriendList {
|
||||
linphoneFL.updateSubscriptions()
|
||||
}
|
||||
self.coreContext.doOnCoreQueue { _ in
|
||||
MagicSearchSingleton.shared.searchForContacts()
|
||||
if let linphoneFL = self.tempRemoteFriendList {
|
||||
linphoneFL.updateSubscriptions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onPresenceReceived: { (friendList: FriendList, friends: [Friend?]) in
|
||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onPresenceReceived \(friends.count)")
|
||||
if (friendList.isSubscriptionBodyless) {
|
||||
Log.info("\(ContactsManager.TAG) Bodyless friendlist \(friendList.displayName ?? "No Display Name") presence received")
|
||||
|
||||
if friendList.displayName != self.nativeAddressBookFriendList && friendList.displayName != self.linphoneAddressBookFriendList {
|
||||
if let tempRemoteFriendList = self.tempRemoteFriendList {
|
||||
tempRemoteFriendList.friends.forEach { friend in
|
||||
if let friendAddress = friend.address,
|
||||
friends.contains(where: { $0?.address?.weakEqual(address2: friendAddress) == true }) {
|
||||
_ = tempRemoteFriendList.removeFriend(linphoneFriend: friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
|
||||
friends.forEach { friend in
|
||||
dispatchGroup.enter()
|
||||
if let friend = friend {
|
||||
let addressTmp = friend.address?.clone()?.asStringUriOnly() ?? ""
|
||||
Log.debug("\(ContactsManager.TAG) Newly discovered SIP Address \(addressTmp) for friend \(friend.name ?? "No Name") in bodyless list \(friendList.displayName ?? "No Display Name")")
|
||||
|
||||
let newContact = Contact(
|
||||
identifier: UUID().uuidString,
|
||||
firstName: friend.name ?? addressTmp,
|
||||
lastName: "",
|
||||
organizationName: "",
|
||||
jobTitle: "",
|
||||
displayName: friend.address?.displayName ?? "",
|
||||
sipAddresses: friend.addresses.map { $0.asStringUriOnly() },
|
||||
phoneNumbers: [],
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
let image = self.textToImage(firstName: friend.name ?? addressTmp, lastName: "")
|
||||
self.saveImage(
|
||||
image: image,
|
||||
name: friend.name ?? addressTmp,
|
||||
prefix: "-default",
|
||||
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: nil) {
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
self.coreContext.doOnCoreQueue { _ in
|
||||
MagicSearchSingleton.shared.searchForContacts()
|
||||
if let linphoneFL = self.tempRemoteFriendList {
|
||||
linphoneFL.updateSubscriptions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onNewSipAddressDiscovered: { (friendList: FriendList, linphoneFriend: Friend, sipUri: String) in
|
||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onNewSipAddressDiscovered \(linphoneFriend.name ?? "")")
|
||||
|
|
@ -522,6 +648,8 @@ final class ContactsManager: ObservableObject {
|
|||
}
|
||||
)
|
||||
|
||||
self.friendListDelegate = friendListDelegateTmp
|
||||
|
||||
CoreContext.shared.mCore.friendsLists.forEach { friendList in
|
||||
friendList.addDelegate(delegate: friendListDelegateTmp)
|
||||
}
|
||||
|
|
@ -530,6 +658,11 @@ final class ContactsManager: ObservableObject {
|
|||
|
||||
func addCoreDelegate(core: Core) {
|
||||
self.coreContext.doOnCoreQueue { _ in
|
||||
if let coreDelegate = self.coreDelegate {
|
||||
core.removeDelegate(delegate: coreDelegate)
|
||||
self.coreDelegate = nil
|
||||
}
|
||||
|
||||
self.coreDelegate = CoreDelegateStub(
|
||||
onFriendListCreated: { (_: Core, friendList: FriendList) in
|
||||
Log.info("\(ContactsManager.TAG) Friend list \(friendList.displayName) created")
|
||||
|
|
@ -558,15 +691,28 @@ final class ContactsManager: ObservableObject {
|
|||
for contact in avatarListModel {
|
||||
contact.$starred
|
||||
.sink { [weak self] _ in
|
||||
self?.starredChangeTrigger = UUID() // 🔁 Déclenche le refresh de la vue
|
||||
self?.starredChangeTrigger = UUID()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSubscriptionsLinphoneList() {
|
||||
if let linphoneFL = self.linphoneFriendList {
|
||||
linphoneFL.updateSubscriptions()
|
||||
self.coreContext.doOnCoreQueue { _ in
|
||||
if let linphoneFL = self.linphoneFriendList {
|
||||
linphoneFL.updateSubscriptions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshCardDavContacts() {
|
||||
self.coreContext.doOnCoreQueue { core in
|
||||
core.friendsLists.forEach{ friendList in
|
||||
if (friendList.type == .CardDAV) {
|
||||
Log.info("\(ContactsManager.TAG) Found CardDAV friend list \(friendList.displayName), starting update")
|
||||
friendList.synchronizeFriendsFromServer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ class CoreContext: ObservableObject {
|
|||
|
||||
var digestAuthInfoPendingPasswordUpdate: AuthInfo?
|
||||
|
||||
@Published var reloadID = UUID()
|
||||
|
||||
private init() {
|
||||
do {
|
||||
try initialiseCore()
|
||||
|
|
@ -121,7 +123,7 @@ class CoreContext: ObservableObject {
|
|||
|
||||
coreQueue.async {
|
||||
LoggingService.Instance.logLevel = LogLevel.Debug
|
||||
Factory.Instance.logCollectionPath = Factory.Instance.getConfigDir(context: nil)
|
||||
Factory.Instance.logCollectionPath = Factory.Instance.getDataDir(context: UnsafeMutablePointer<Int8>(mutating: (Config.appGroupName as NSString).utf8String))
|
||||
Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled)
|
||||
|
||||
Log.info("Checking if linphonerc file exists already. If not, creating one as a copy of linphonerc-default")
|
||||
|
|
@ -148,13 +150,21 @@ class CoreContext: ObservableObject {
|
|||
self.mCore.callkitEnabled = true
|
||||
self.mCore.pushNotificationEnabled = true
|
||||
|
||||
let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String
|
||||
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
let appGitVersion = AppGitInfo.commit
|
||||
let appGitBranch = AppGitInfo.branch
|
||||
let appGitTag = AppGitInfo.tag
|
||||
let sdkGitVersion = linphonesw.sdkVersion
|
||||
var sdkGitBranch = linphonesw.sdkBranch
|
||||
|
||||
let userAgent = "LinphoneiOS/\(version ?? "6.0.0") (\(UIDevice.current.localizedModel.replacingOccurrences(of: "'", with: ""))) LinphoneSDK"
|
||||
if sdkGitBranch.hasPrefix("remotes/origin/") {
|
||||
sdkGitBranch = String(sdkGitBranch.dropFirst("remotes/origin/".count))
|
||||
}
|
||||
|
||||
Log.info("Git Info — App: \(appGitTag + "-" + appGitVersion) [\(appGitBranch)] | SDK: \(sdkGitVersion) [\(sdkGitBranch)]")
|
||||
|
||||
let userAgent = "LinphoneiOS/\(appGitTag) (\(UIDevice.current.localizedModel.replacingOccurrences(of: "'", with: ""))) LinphoneSDK"
|
||||
self.mCore.setUserAgent(name: userAgent, version: self.coreVersion)
|
||||
self.mCore.videoCaptureEnabled = true
|
||||
self.mCore.videoDisplayEnabled = true
|
||||
|
||||
self.mCore.videoPreviewEnabled = false
|
||||
self.mCore.fecEnabled = true
|
||||
|
||||
|
|
@ -200,6 +210,26 @@ class CoreContext: ObservableObject {
|
|||
self.forceRemotePushToMatchVoipPushSettings(account: acc)
|
||||
}
|
||||
|
||||
let container = FileUtil.sharedContainerUrl()
|
||||
let recordingsDir = container.appendingPathComponent("Library/Recordings")
|
||||
|
||||
let fm = FileManager.default
|
||||
|
||||
if !fm.fileExists(atPath: recordingsDir.path) {
|
||||
do {
|
||||
try fm.createDirectory(
|
||||
at: recordingsDir,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
print("Recordings directory created.")
|
||||
} catch {
|
||||
print("Error creating directory: \(error)")
|
||||
}
|
||||
} else {
|
||||
print("Recordings directory already exists.")
|
||||
}
|
||||
|
||||
self.mCoreDelegate = CoreDelegateStub(onGlobalStateChanged: { (core: Core, state: GlobalState, _: String) in
|
||||
if state == GlobalState.On {
|
||||
#if DEBUG
|
||||
|
|
@ -208,20 +238,14 @@ class CoreContext: ObservableObject {
|
|||
let pushEnvironment = ""
|
||||
#endif
|
||||
for account in core.accountList {
|
||||
|
||||
let newParams = account.params?.clone()
|
||||
if account.params?.pushNotificationConfig?.provider != ("apns" + pushEnvironment) {
|
||||
let newParams = account.params?.clone()
|
||||
|
||||
Log.info("Account \(String(describing: newParams?.identityAddress?.asStringUriOnly())) - updating apple push provider from \(String(describing: newParams?.pushNotificationConfig?.provider)) to apns\(pushEnvironment)")
|
||||
newParams?.pushNotificationConfig?.provider = "apns" + pushEnvironment
|
||||
|
||||
account.params = newParams
|
||||
}
|
||||
|
||||
if account.params?.internationalPrefix == nil {
|
||||
Log.info("Account \(account.displayName()): no international prefix set, adding 33 FRA by default: \(account.params?.internationalPrefix ?? "NIL")")
|
||||
newParams?.internationalPrefix = "33"
|
||||
newParams?.internationalPrefixIsoCountryCode = "FRA"
|
||||
newParams?.useInternationalPrefixForCallsAndChats = true
|
||||
}
|
||||
account.params = newParams
|
||||
}
|
||||
|
||||
self.actionsToPerformOnCoreQueueWhenCoreIsStarted.forEach { $0(core) }
|
||||
|
|
@ -305,6 +329,8 @@ class CoreContext: ObservableObject {
|
|||
}
|
||||
}, onConfiguringStatus: { (_: Core, status: ConfiguringState, message: String) in
|
||||
Log.info("New configuration state is \(status) = \(message)\n")
|
||||
let themeMainColor = CorePreferences.themeMainColor
|
||||
SharedMainViewModel.shared.updateConfigChanges()
|
||||
DispatchQueue.main.async {
|
||||
if status == ConfiguringState.Successful {
|
||||
var accountModels: [AccountModel] = []
|
||||
|
|
@ -312,6 +338,8 @@ class CoreContext: ObservableObject {
|
|||
accountModels.append(AccountModel(account: account, core: self.mCore))
|
||||
}
|
||||
self.accounts = accountModels
|
||||
ThemeManager.shared.applyTheme(named: themeMainColor)
|
||||
self.reloadID = UUID()
|
||||
}
|
||||
}
|
||||
}, onLogCollectionUploadStateChanged: { (_: Core, _: Core.LogCollectionUploadState, info: String) in
|
||||
|
|
@ -406,6 +434,31 @@ class CoreContext: ObservableObject {
|
|||
DispatchQueue.main.async {
|
||||
self.accounts = accountModels
|
||||
}
|
||||
}, onMessageWaitingIndicationChanged: { (core: Core, event: Event, mwi: MessageWaitingIndication) in
|
||||
if (mwi.hasMessageWaiting()) {
|
||||
let summaries = mwi.summaries
|
||||
Log.info(
|
||||
"[CoreContext][onMessageWaitingIndicationChanged] MWI NOTIFY received, messages are waiting (\(summaries.count) summaries)"
|
||||
)
|
||||
|
||||
if let defaultAccount = core.defaultAccount?.params?.identityAddress, let mwiAccount = mwi.accountAddress, defaultAccount.weakEqual(address2: mwiAccount){
|
||||
if !summaries.isEmpty {
|
||||
let summary = summaries.first
|
||||
DispatchQueue.main.async {
|
||||
withAnimation {
|
||||
SharedMainViewModel.shared.waitingMessageCount = Int(summary?.nbNew ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.info("[CoreContext][onMessageWaitingIndicationChanged] MWI NOTIFY received, no message is waiting")
|
||||
DispatchQueue.main.async {
|
||||
withAnimation {
|
||||
SharedMainViewModel.shared.waitingMessageCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.mCore.addDelegate(delegate: self.mCoreDelegate)
|
||||
|
|
@ -424,26 +477,24 @@ class CoreContext: ObservableObject {
|
|||
|
||||
func onEnterForeground() {
|
||||
coreQueue.async {
|
||||
// We can't rely on defaultAccount?.params?.isPublishEnabled
|
||||
// as it will be modified by the SDK when changing the presence status
|
||||
Log.info("[onEnterForegroundOrBackground] Entering foreground")
|
||||
|
||||
try? self.mCore.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func onEnterBackground() {
|
||||
coreQueue.async {
|
||||
// We can't rely on defaultAccount?.params?.isPublishEnabled
|
||||
// as it will be modified by the SDK when changing the presence status
|
||||
Log.info("App is in background, un-PUBLISHING presence info")
|
||||
Log.info("[onEnterForegroundOrBackground] Entering background, un-PUBLISHING presence info")
|
||||
|
||||
// We don't use ConsolidatedPresence.Busy but Offline to do an unsubscribe,
|
||||
// Flexisip will handle the Busy status depending on other devices
|
||||
self.updatePresence(core: self.mCore, presence: ConsolidatedPresence.Offline)
|
||||
self.updatePresence(core: self.mCore, presence: .Offline)
|
||||
self.mCore.iterate()
|
||||
|
||||
if self.mCore.currentCall == nil {
|
||||
if self.mCore.currentCall == nil && self.mCore.globalState == .On {
|
||||
Log.info("[onEnterForegroundOrBackground] Stopping core because no active calls")
|
||||
self.mCore.stop()
|
||||
} else {
|
||||
Log.info("[onEnterForegroundOrBackground] Skipped stop: core not fully On or active call in progress")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ class CorePreferences {
|
|||
|
||||
static var checkForUpdateServerUrl: String {
|
||||
get {
|
||||
return Config.get().getString(section: "misc", key: "version_check_url_root", defaultString: "")
|
||||
let raw = Config.get().getString(section: "misc", key: "version_check_url_root", defaultString: "")
|
||||
return safeString(raw, defaultValue: "")
|
||||
}
|
||||
set {
|
||||
Config.get().setString(section: "misc", key: "version_check_url_root", value: newValue)
|
||||
|
|
@ -86,7 +87,8 @@ class CorePreferences {
|
|||
|
||||
static var deviceName: String {
|
||||
get {
|
||||
return Config.get().getString(section: "app", key: "device", defaultString: "").trimmingCharacters(in: .whitespaces)
|
||||
let raw = Config.get().getString(section: "app", key: "device", defaultString: "").trimmingCharacters(in: .whitespaces)
|
||||
return safeString(raw, defaultValue: "")
|
||||
}
|
||||
set {
|
||||
Config.get().setString(section: "app", key: "device", value: newValue.trimmingCharacters(in: .whitespaces))
|
||||
|
|
@ -131,13 +133,23 @@ class CorePreferences {
|
|||
|
||||
static var contactsFilter: String {
|
||||
get {
|
||||
return Config.get().getString(section: "ui", key: "contacts_filter", defaultString: "")
|
||||
let raw = Config.get().getString(section: "ui", key: "contacts_filter", defaultString: "")
|
||||
return safeString(raw, defaultValue: "")
|
||||
}
|
||||
set {
|
||||
Config.get().setString(section: "ui", key: "contacts_filter", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
static var disableAddContact: Bool {
|
||||
get {
|
||||
return Config.get().getBool(section: "ui", key: "disable_add_contact", defaultValue: false)
|
||||
}
|
||||
set {
|
||||
Config.get().setBool(section: "ui", key: "disable_add_contact", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
static var showFavoriteContacts: Bool {
|
||||
get {
|
||||
return Config.get().getBool(section: "ui", key: "show_favorites_contacts", defaultValue: true)
|
||||
|
|
@ -147,6 +159,15 @@ class CorePreferences {
|
|||
}
|
||||
}
|
||||
|
||||
static var friendListInWhichStoreNewlyCreatedFriends: String {
|
||||
get {
|
||||
return Config.get().getString(section: "app", key: "friend_list_to_store_newly_created_contacts", defaultString: "Linphone address-book")
|
||||
}
|
||||
set {
|
||||
Config.get().setString(section: "app", key: "friend_list_to_store_newly_created_contacts", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
static var voiceRecordingMaxDuration: Int {
|
||||
get {
|
||||
return Config.get().getInt(section: "app", key: "voice_recording_max_duration", defaultValue: 600000)
|
||||
|
|
@ -177,13 +198,20 @@ class CorePreferences {
|
|||
|
||||
static var themeMainColor: String {
|
||||
get {
|
||||
return Config.get().getString(section: "ui", key: "theme_main_color", defaultString: "orange")
|
||||
let raw = Config.get().getString(section: "ui", key: "theme_main_color", defaultString: "orange")
|
||||
return safeString(raw, defaultValue: "orange")
|
||||
}
|
||||
set {
|
||||
Config.get().setString(section: "ui", key: "theme_main_color", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
static var themeAboutPictureUrl: String? {
|
||||
get {
|
||||
return Config.get().getString(section: "ui", key: "theme_about_picture_url", defaultString: nil)
|
||||
}
|
||||
}
|
||||
|
||||
static var darkModeAllowed: Bool {
|
||||
return Config.get().getBool(section: "ui", key: "dark_mode_allowed", defaultValue: true)
|
||||
}
|
||||
|
|
@ -244,21 +272,22 @@ class CorePreferences {
|
|||
|
||||
static var defaultDomain: String {
|
||||
get {
|
||||
return Config.get().getString(section: "app", key: "default_domain", defaultString: "sip.linphone.org")
|
||||
let raw = Config.get().getString(section: "app", key: "default_domain", defaultString: "sip.linphone.org")
|
||||
return safeString(raw, defaultValue: "sip.linphone.org")
|
||||
}
|
||||
set {
|
||||
Config.get().setString(section: "app", key: "default_domain", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
static var disableChatFeature: Bool {
|
||||
get {
|
||||
return Config.get().getBool(section: "app", key: "disable_chat_feature", defaultValue: false)
|
||||
}
|
||||
set {
|
||||
Config.get().setBool(section: "app", key: "disable_chat_feature", value: newValue)
|
||||
}
|
||||
}
|
||||
static var disableChatFeature: Bool {
|
||||
get {
|
||||
return Config.get().getBool(section: "ui", key: "disable_chat_feature", defaultValue: false)
|
||||
}
|
||||
set {
|
||||
Config.get().setBool(section: "ui", key: "disable_chat_feature", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
static var disableMeetings: Bool {
|
||||
get {
|
||||
|
|
@ -269,6 +298,15 @@ class CorePreferences {
|
|||
}
|
||||
}
|
||||
|
||||
static var hideSipAddresses: Bool {
|
||||
get {
|
||||
return Config.get().getBool(section: "ui", key: "hide_sip_addresses", defaultValue: false)
|
||||
}
|
||||
set {
|
||||
Config.get().setBool(section: "ui", key: "hide_sip_addresses", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private func copy(from: String, to: String, overrideIfExists: Bool = false) {
|
||||
let fileManager = FileManager.default
|
||||
if fileManager.fileExists(atPath: to), !overrideIfExists {
|
||||
|
|
@ -283,4 +321,12 @@ class CorePreferences {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func safeString(_ raw: String?, defaultValue: String = "") -> String {
|
||||
guard let raw = raw else { return defaultValue }
|
||||
if let data = raw.data(using: .utf8) {
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
Linphone/GeneratedGit.swift
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Foundation
|
||||
|
||||
public enum AppGitInfo {
|
||||
public static let branch = "master"
|
||||
public static let commit = "986276c04"
|
||||
public static let tag = "6.1.0-alpha"
|
||||
}
|
||||
|
|
@ -4,6 +4,16 @@
|
|||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>org.linphone.phone</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>linphone-mention</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
|
|
@ -115,12 +125,6 @@
|
|||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<true/>
|
||||
<key>ITSEncryptionExportComplianceCode</key>
|
||||
<string>b5cb085f-772a-4a4f-8c77-5d1332b1f93f</string>
|
||||
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSSupportsSuddenTermination</key>
|
||||
<false/>
|
||||
<key>UIAppFonts</key>
|
||||
|
|
@ -139,9 +143,6 @@
|
|||
<string>audio</string>
|
||||
</array>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict>
|
||||
<key>UIImageName</key>
|
||||
<string>linphone</string>
|
||||
</dict>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
44
Linphone/Launch Screen.storyboard
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="6L3-wz-ibv" image="linphone">
|
||||
<rect key="frame" x="0" y="0" width="240" height="128"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<!-- Center horizontally -->
|
||||
<constraint firstItem="6L3-wz-ibv" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="centerXConstraint"/>
|
||||
<!-- Center vertically -->
|
||||
<constraint firstItem="6L3-wz-ibv" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="centerYConstraint"/>
|
||||
<!-- Fixed width -->
|
||||
<constraint firstItem="6L3-wz-ibv" firstAttribute="width" constant="240" id="widthConstraint"/>
|
||||
<!-- Fixed height -->
|
||||
<constraint firstItem="6L3-wz-ibv" firstAttribute="height" constant="128" id="heightConstraint"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="linphone" width="100" height="102"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
import SwiftUI
|
||||
import linphonesw
|
||||
import UserNotifications
|
||||
import Intents
|
||||
|
||||
let accountTokenNotification = Notification.Name("AccountCreationTokenReceived")
|
||||
var displayedChatroomPeerAddr: String?
|
||||
|
|
@ -108,7 +109,28 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
continue userActivity: NSUserActivity,
|
||||
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
|
||||
guard let interaction = userActivity.interaction,
|
||||
let intent = interaction.intent as? INStartCallIntent,
|
||||
let person = intent.contacts?.first,
|
||||
let number = person.personHandle?.value else { return false }
|
||||
|
||||
let isVideo = intent.callCapability == .videoCall
|
||||
|
||||
Log.info("[AppDelegate][INStartCallIntent] Generic call intent received for number: \(number) isVideo: \(isVideo)")
|
||||
|
||||
CoreContext.shared.performActionOnCoreQueueWhenCoreIsStarted { core in
|
||||
if let address = core.interpretUrl(url: number, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) {
|
||||
TelecomManager.shared.doCallOrJoinConf(address: address, isVideo: isVideo)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
|
|
@ -240,6 +262,22 @@ struct RootView: View {
|
|||
pendingURL = url
|
||||
}
|
||||
}
|
||||
.onContinueUserActivity("INStartCallIntent") { activity in
|
||||
guard let interaction = activity.interaction,
|
||||
let intent = interaction.intent as? INStartCallIntent,
|
||||
let person = intent.contacts?.first,
|
||||
let number = person.personHandle?.value else { return }
|
||||
|
||||
let isVideo = intent.callCapability == .videoCall
|
||||
|
||||
Log.info("[INStartCallIntent] Generic call intent received for number: \(number) isVideo: \(isVideo)")
|
||||
|
||||
coreContext.performActionOnCoreQueueWhenCoreIsStarted { core in
|
||||
if let address = core.interpretUrl(url: number, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) {
|
||||
telecomManager.doCallOrJoinConf(address: address, isVideo: isVideo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -259,6 +297,7 @@ struct MainViewSwitcher: View {
|
|||
let sharedMainViewModel: SharedMainViewModel
|
||||
@Binding var pendingURL: URL?
|
||||
let appDelegate: AppDelegate
|
||||
@ObservedObject private var colors = ColorProvider.shared
|
||||
|
||||
var body: some View {
|
||||
selectedMainView()
|
||||
|
|
@ -277,5 +316,6 @@ struct MainViewSwitcher: View {
|
|||
navigationManager.openChatRoom(callId: callId, peerAddr: peerAddr, localAddr: localAddr)
|
||||
}
|
||||
}
|
||||
.id(colors.theme.name)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
Linphone/Localizable/ca.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"assistant_sip_account_transport_protocol" = "Transport";
|
||||
"contact_call_action" = "Volat";
|
||||
"conversation_action_call" = "Volat";
|
||||
"dialog_ok" = "OK";
|
||||
"dialog_confirm" = "Confirm";
|
||||
"drawer_menu_manage_account" = "Spravovat profil";
|
||||
"help_error_checking_version_toast_message" = "Během kontroly aktualizací nastala chyba";
|
||||
"settings_contacts_carddav_name_title" = "Zobrazené jméno";
|
||||
|
|
@ -474,9 +474,9 @@
|
|||
"message_copied_to_clipboard_toast" = "Zpráva zkopírována do schránky";
|
||||
"message_delivery_info_read_title" = "Přečteno";
|
||||
"message_delivery_info_received_title" = "Přijato";
|
||||
"message_meeting_invitation_cancelled_notification" = "📅 Schůzka byla zrušena";
|
||||
"message_meeting_invitation_notification" = "📅 Jste pozváni na schůzku";
|
||||
"message_meeting_invitation_updated_notification" = "📅 Schůzka byla aktualizována";
|
||||
"message_meeting_invitation_cancelled_notification" = "Schůzka byla zrušena";
|
||||
"message_meeting_invitation_notification" = "Jste pozváni na schůzku";
|
||||
"message_meeting_invitation_updated_notification" = "Schůzka byla aktualizována";
|
||||
"message_reactions_info_all_title" = "Reakce";
|
||||
"network_reachable_again" = "Síť je znovu dostupná";
|
||||
"menu_block_number" = "Blokovat číslo";
|
||||
|
|
@ -522,3 +522,4 @@
|
|||
"conversations_files_waiting_to_be_shared_single" = "1 soubor čekající na sdílení";
|
||||
"conversations_files_waiting_to_be_shared_multiple" = "%@ souborů čekajících na sdílení";
|
||||
"conversation_ephemeral_messages_duration_multiple_days" = "%d dnů";
|
||||
"authentication_id" = "ID pro ověření (je-li odlišné)";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"call_stats_audio_title" = "Audio";
|
||||
"Error" = "Fehler";
|
||||
"settings_advanced_accept_early_media_title" = "'Early Media' erlauben";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Ausgehende 'Early Media' erlauben";
|
||||
"settings_advanced_accept_early_media_title" = "Frühe Medienübertragung erlauben";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Ausgehende frühe Medianübertragung erlauben";
|
||||
"settings_advanced_audio_codecs_title" = "Audio-Codecs";
|
||||
"settings_advanced_audio_devices_title" = "Audiogeräte";
|
||||
"settings_advanced_device_id" = "Gerät ID";
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
"assistant_account_login_forbidden_error" = "Falscher Benutzername oder Passwort";
|
||||
"assistant_account_register" = "Registrieren";
|
||||
"assistant_account_register_push_notification_not_received_error" = "Push Benachrichtigung mit Authentifizierungstoken nicht innerhalb von 5 Sekunden empfangen, bitte versuchen Sie es später erneut";
|
||||
"assistant_account_register_unexpected_error" = "Unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.";
|
||||
"assistant_account_register_unexpected_error" = "Unerwarteter Fehler ist aufgetreten, bitte versuchen Sie es später erneut";
|
||||
"assistant_already_have_an_account" = "Haben Sie bereits ein Konto?";
|
||||
"assistant_create_account_using_email_on_our_web_platform" = "Erstellen Sie mit Ihrer E-Mail ein Konto bei:";
|
||||
"assistant_dialog_confirm_phone_number_title" = "Telefonnummer bestätigen";
|
||||
|
|
@ -67,10 +67,10 @@
|
|||
"call_action_change_layout" = "Layout";
|
||||
"call_action_go_to_calls_list" = "Anrufliste";
|
||||
"call_action_hang_up" = "Auflegen";
|
||||
"call_action_pause_call" = "Pause";
|
||||
"call_action_pause_call" = "Halten";
|
||||
"call_action_record_call" = "Aufnahmen";
|
||||
"call_action_resume_call" = "Fortsetzen";
|
||||
"call_action_show_dialer" = "Wähler";
|
||||
"call_action_show_dialer" = "Tastatur";
|
||||
"call_action_show_messages" = "Nachrichten";
|
||||
"call_action_start_new_call" = "Neuer Anruf";
|
||||
"call_audio_device_type_earpiece" = "Ohrhörer";
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
"conversation_action_delete" = "Gespräch löschen";
|
||||
"conversation_action_leave_group" = "Verlasse die Gruppe";
|
||||
"conversation_action_mark_as_read" = "Als gelesen markieren";
|
||||
"conversation_action_mute" = "Stumm";
|
||||
"conversation_action_mute" = "Stummschalten";
|
||||
"conversation_action_unmute" = "Stumm aufheben";
|
||||
"conversation_add_participants_title" = "Teilnehmer hinzufügen";
|
||||
"conversation_dialog_edit_subject" = "Gesprächsbetreff bearbeiten";
|
||||
|
|
@ -156,7 +156,7 @@
|
|||
"conversation_ephemeral_messages_duration_three_days" = "3 Tage";
|
||||
"conversation_ephemeral_messages_subtitle" = "Neue Nachrichten werden automatisch gelöscht, sobald sie von allen gelesen wurden.\nWählen Sie eine Dauer:";
|
||||
"conversation_ephemeral_messages_title" = "Kurzlebiger Nachrichten";
|
||||
"conversation_event_conference_created" = "Sie haben der Gruppe beigetreten";
|
||||
"conversation_event_conference_created" = "Sie sind der Gruppe beigetreten";
|
||||
"conversation_event_conference_destroyed" = "Sie haben der Gruppe verlassen";
|
||||
"conversation_event_ephemeral_messages_disabled" = "Kurzlebige Nachrichten wurden deaktiviert";
|
||||
"conversation_event_ephemeral_messages_enabled" = "Kurzlebige Nachrichten wurden aktiviert";
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
"dialog_deny" = "Ablehnen";
|
||||
"dialog_install" = "Installieren";
|
||||
"dialog_no" = "Nein";
|
||||
"dialog_ok" = "OK";
|
||||
"dialog_confirm" = "Confirm";
|
||||
"dialog_yes" = "Ja";
|
||||
"drawer_menu_account_connection_status_cleared" = "Deaktiviert";
|
||||
"drawer_menu_account_connection_status_connected" = "Verbunden";
|
||||
|
|
@ -288,17 +288,17 @@
|
|||
"menu_delete_selected_item" = "Löschen";
|
||||
"menu_forward_chat_message" = "Weiterleiten";
|
||||
"menu_invite" = "Einladen";
|
||||
"menu_reply_to_chat_message" = "Antwort";
|
||||
"menu_reply_to_chat_message" = "Antworten";
|
||||
"menu_resend_chat_message" = "Erneut senden";
|
||||
"menu_see_existing_contact" = "Siehe Kontakt";
|
||||
"menu_show_imdn" = "Lieferstatus";
|
||||
"menu_show_imdn" = "Zustelldetails";
|
||||
"message_delivery_info_error_title" = "Fehler";
|
||||
"message_forwarded_label" = "Weitergeleitet";
|
||||
"message_reaction_click_to_remove_label" = "Zum Entfernen klicken";
|
||||
"network_not_reachable" = "Sie sind nicht mit dem Internet verbunden";
|
||||
"new_conversation_create_group" = "Gruppengespräch erstellen";
|
||||
"new_conversation_search_bar_filter_hint" = "Kontakt suchen";
|
||||
"new_conversation_title" = "Neues Gespräch";
|
||||
"new_conversation_title" = "Neuer Chat";
|
||||
"next" = "Weiter";
|
||||
"notification_missed_call_title" = "Verpasster Anruf";
|
||||
"operation_in_progress_overlay" = "Vorgang wird ausgeführt, bitte warten";
|
||||
|
|
@ -318,29 +318,29 @@
|
|||
"settings_calls_auto_record_title" = "Automatische Anrufaufzeichnung starten";
|
||||
"settings_calls_calibrate_echo_canceller_title" = "Echokompensator kalibrieren";
|
||||
"settings_calls_change_ringtone_title" = "Klingelton ändern";
|
||||
"settings_calls_echo_canceller_subtitle" = "Verhindert, dass das Echo am entfernten Ende gehört wird, wenn kein Hardware Echokompensator verfügbar ist.";
|
||||
"settings_calls_echo_canceller_subtitle" = "Verhindert, dass das Echo am entfernten Ende gehört wird, wenn kein Hardware Echokompensator verfügbar ist";
|
||||
"settings_calls_echo_canceller_title" = "Verwenden Sie die Software Echounterdrückung";
|
||||
"settings_calls_enable_fec_title" = "Video FEC aktivieren";
|
||||
"settings_calls_enable_video_title" = "Video aktivieren";
|
||||
"settings_calls_title" = "Anrufe";
|
||||
"settings_calls_vibrate_while_ringing_title" = "Vibrieren während ein eingehender Anruf klingelt";
|
||||
"settings_contacts_add_carddav_server_title" = "CardDAV Adressbuch hinzufügen";
|
||||
"settings_contacts_add_ldap_server_title" = "LDAP Server hinzufügen";
|
||||
"settings_contacts_add_carddav_server_title" = "CardDAV-Adressbuch hinzufügen";
|
||||
"settings_contacts_add_ldap_server_title" = "LDAP-Server hinzufügen";
|
||||
"settings_contacts_carddav_name_title" = "Anzeigename";
|
||||
"settings_contacts_carddav_password_title" = "Passwort";
|
||||
"settings_contacts_carddav_server_url_title" = "Server URL";
|
||||
"settings_contacts_carddav_server_url_title" = "Server-URL";
|
||||
"settings_contacts_carddav_sync_error_toast" = "Synchronisierungfehler!";
|
||||
"settings_contacts_carddav_username_title" = "Benutzername";
|
||||
"settings_contacts_edit_carddav_server_title" = "CardDAV Adressbuch bearbeiten";
|
||||
"settings_contacts_edit_ldap_server_title" = "LDAP Server bearbeiten";
|
||||
"settings_contacts_edit_carddav_server_title" = "CardDAV-Adressbuch bearbeiten";
|
||||
"settings_contacts_edit_ldap_server_title" = "LDAP-Server bearbeiten";
|
||||
"settings_contacts_ldap_bind_dn_title" = "DN binden";
|
||||
"settings_contacts_ldap_password_title" = "Passwort";
|
||||
"settings_contacts_ldap_search_base_title" = "Filter";
|
||||
"settings_contacts_ldap_server_url_title" = "Server URL (darf nicht leer sein)";
|
||||
"settings_contacts_ldap_search_base_title" = "Suchbasis (darf nicht leer sein)";
|
||||
"settings_contacts_ldap_server_url_title" = "Server-URL (darf nicht leer sein)";
|
||||
"settings_contacts_ldap_use_tls_title" = "TLS verwenden";
|
||||
"settings_contacts_title" = "Kontakte";
|
||||
"settings_conversations_auto_download_title" = "Dateien automatisch heruntergeladene";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Gespräche beim Schließen der Nachrichtenbenachrichtigung als gelesen markieren";
|
||||
"settings_conversations_auto_download_title" = "Dateien automatisch herunterladen";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Gespräche beim Schließen der Benachrichtigung als gelesen markieren";
|
||||
"settings_conversations_title" = "Gespräche";
|
||||
"settings_meetings_default_layout_title" = "Standardlayout";
|
||||
"settings_meetings_layout_active_speaker_label" = "Aktiver Lautsprecher";
|
||||
|
|
@ -358,8 +358,154 @@
|
|||
"sip_address_display_name" = "Anzeigename";
|
||||
"sip_address_domain" = "Domäne";
|
||||
"start" = "Start";
|
||||
"web_platform_register_email_url" = "TURN aktivieren";
|
||||
"web_platform_register_email_url" = "https://subscribe.linphone.org/register/email";
|
||||
"welcome_page_3_title" = "Open source";
|
||||
"welcome_page_title" = "Willkommen";
|
||||
"conversation_end_to_end_encrypted_event_subtitle" = "Nachrichten in dieser Gespr sind Ende-zu-Ende verschlüsselt. Nur Ihr Gesprächspartner kann sie entschlüsseln.";
|
||||
"conversation_end_to_end_encrypted_event_title" = "Ende-zu-Ende verschlüsselte Gespräch";
|
||||
"assistant_permissions_post_notifications_title" = "**Postbenachrichtigungen:** Um informiert zu werden, wenn Sie eine Nachricht oder einen Anruf erhalten.";
|
||||
"assistant_permissions_access_camera_title" = "**Auf Kamera zugreifen:** Zum Aufnehmen von Videos während Videoanrufen und Konferenzen.";
|
||||
"assistant_permissions_read_contacts_title" = "**Kontakte lesen:** Um Ihre Kontakte anzuzeigen und herauszufinden, wer %@ verwendet.";
|
||||
"account_settings_dialog_invalid_password_message" = "Verbindung fehlgeschlagen, da die Authentifizierung für das Konto fehlt oder ungültig ist.\n%@.\n\nSie können Ihr Passwort erneut eingeben oder Ihre Kontokonfiguration in den Einstellungen überprüfen.";
|
||||
"assistant_account_creation_sms_confirmation_explanation" = "Wir haben einen Bestätigungscode an Ihre Telefonnummer %@ gesendet. Bitte geben Sie unten den Bestätigungscode ein:";
|
||||
"assistant_dialog_confirm_phone_number_message" = "Möchten Sie wirklich die Telefonnummer %@ verwenden?";
|
||||
"assistant_dialog_general_terms_and_privacy_policy_message" = "Indem Sie fortfahren, akzeptieren Sie unsere %@ und %@.";
|
||||
"assistant_forgotten_password" = "Passwort vergessen?";
|
||||
"assistant_invalid_uri_toast" = "Ungültige URI";
|
||||
"assistant_permissions_record_audio_title" = "**Audio aufnehmen:** Damit Ihr Gesprächspartner Sie hören kann und um Sprachnachrichten aufzunehmen.";
|
||||
"assistant_permissions_subtitle" = "Um %@ in vollem Umfang nutzen zu können, müssen Sie uns die folgenden Berechtigungen erteilen:";
|
||||
"history_list_empty_with_filter_history" = "Keine Einträge entsprechen Ihrer Suche";
|
||||
"history_title" = "Anrufliste";
|
||||
"IM_MSG" = "Sie haben eine Nachricht erhalten";
|
||||
"Interoperable" = "Interoperabel";
|
||||
"conversation_event_participant_added" = "%@ ist beigetreten";
|
||||
"drawer_menu_account_connection_status_refreshing" = "Aktualisieren...";
|
||||
"failed_meeting_ics_invitation_not_sent_toast" = "ICS-Besprechungseinladungen an Teilnehmer konnten nicht gesendet werden";
|
||||
"You will change this mode later" = "Sie werden diesen Modus später ändern";
|
||||
"welcome_carousel_skip" = "Überspringen";
|
||||
"welcome_page_2_message" = "Ihre Kommunikation ist dank unserer **Ende-zu-Ende-Verschlüsselung** sicher.";
|
||||
"welcome_page_subtitle" = "in %@";
|
||||
"conversation_event_participant_removed" = "%@ hat verlassen";
|
||||
"username_error" = "Benutzername-Fehler";
|
||||
": %@" = ": %@";
|
||||
"*" = "*";
|
||||
"**%@**" = "**%@**";
|
||||
"#" = "#";
|
||||
"%@" = "%@";
|
||||
"%lld" = "%lld";
|
||||
"%lld %@" = "%1$lld %2$@";
|
||||
"%lld%%" = "%lld%%";
|
||||
"+" = "+";
|
||||
"|" = "|";
|
||||
"❤️" = "❤️";
|
||||
"👍" = "👍";
|
||||
"😂" = "😂";
|
||||
"😢" = "😢";
|
||||
"😮" = "😮";
|
||||
"0" = "0";
|
||||
"1" = "1";
|
||||
"2" = "2";
|
||||
"3" = "3";
|
||||
"4" = "4";
|
||||
"5" = "5";
|
||||
"6" = "6";
|
||||
"7" = "7";
|
||||
"8" = "8";
|
||||
"9" = "9";
|
||||
"assistant_third_party_sip_account_create_linphone_account" = "Ich möchte lieber ein Konto erstellen";
|
||||
"assistant_third_party_sip_account_warning_explanation" = "Für einige Funktionen, wie z. B. Gruppennachrichten, Videokonferenzen usw., ist ein %@-Konto erforderlich.\n\nDiese Funktionen sind ausgeblendet, wenn Sie sich mit einem SIP-Konto eines Drittanbieters registrieren.\n\nUm diese Funktion in einem kommerziellen Projekt zu aktivieren, kontaktieren Sie uns bitte.";
|
||||
"call_action_attended_transfer" = "Begleitete Übertragung";
|
||||
"call_audio_device_type_bluetooth" = "Bluetooth (%@)";
|
||||
"call_can_be_trusted_toast" = "Authentifiziertes Gerät";
|
||||
"call_dialog_zrtp_validate_trust_message" = "Zu Ihrer Sicherheit müssen wir Ihr Endgerät authentifizieren. Bitte tauschen Sie Ihre Codes aus:";
|
||||
"call_dialog_zrtp_validate_trust_warning_message" = "Zu Ihrer Sicherheit müssen wir Ihr Endgerät authentifizieren. Bitte tauschen Sie Ihre Codes aus:";
|
||||
"Ce mode vous permet d’être interopérable avec d’autres services SIP.\nVos communications seront chiffrées de point à point. " = "Dieser Modus ermöglicht die Interoperabilität mit anderen SIP-Diensten.\nIhre Kommunikation wird Punkt-zu-Punkt verschlüsselt. ";
|
||||
"Chiffrement de bout en bout de tous vos échanges, grâce au mode default vos communications sont à l’abri des regards." = "Ende-zu-Ende-Verschlüsselung aller Ihrer Kommunikationen. Dank des Standardmodus sind Ihre Kommunikationen vor neugierigen Blicken geschützt.";
|
||||
"conference_name_error" = "Konferenznamen Fehler";
|
||||
"contact_details_numbers_and_addresses_title" = "Telefonnummern & SIP-Adressen";
|
||||
"contact_dialog_delete_title" = "%@ löschen?";
|
||||
"call_transfer_current_call_title" = "Anruf weiterleiten";
|
||||
"call_zrtp_sas_validation_skip" = "Überspringen";
|
||||
"calls_count_label" = "%@ Anrufe";
|
||||
"contact_video_call_action" = "Videoanruf";
|
||||
"contacts_list_filter_popup_see_linphone_only" = "%@ Kontakte siehen";
|
||||
"conversation_composing_label_multiple" = "%@ schreiben…";
|
||||
"conversation_composing_label_single" = "%@ schreibt…";
|
||||
"conversation_ephemeral_messages_duration_multiple_days" = "%d Tage";
|
||||
"conversation_event_admin_set" = "%@ ist Administrator";
|
||||
"conversation_event_admin_unset" = "$@ ist nicht mehr Administrator";
|
||||
"conversation_event_device_added" = "neues Gerät für %@";
|
||||
"conversation_event_device_removed" = "Gerät für %@ entfernt";
|
||||
"conversation_event_ephemeral_messages_lifetime_changed" = "Kurzlebige Nachrichten Lebensdauer beträgt jetzt %@";
|
||||
"conversation_event_subject_changed" = "neues Betreff: %@";
|
||||
"conversations_files_waiting_to_be_shared_single" = "1 Datei wartet auf Freigabe";
|
||||
"conversations_files_waiting_to_be_shared_multiple" = "%@ Dateien warten auf Freigabe";
|
||||
"conversation_info_participants_list_title" = "Gruppenteilnehmer (%d)";
|
||||
"conversation_message_meeting_cancelled_label" = "Das Besprechung wurde abgesagt!";
|
||||
"conversation_one_to_one_hidden_subject" = "Dummy-Betreff";
|
||||
"conversation_reply_to_message_title" = "Antwort auf: ";
|
||||
"debug_logs_copied_to_clipboard_toast" = "Debug-Protokolle in die Zwischenablage kopiert";
|
||||
"Default" = "Standard";
|
||||
"Default mode" = "Standardmodus";
|
||||
"dialog_close" = "Schließen";
|
||||
"DTLS" = "DTLS";
|
||||
"GC_MSG" = "Sie wurden zu einem Chatroom hinzugefügt";
|
||||
"help_dialog_update_available_message" = "Eine neue Version %@ ist verfügbar. Möchten Sie aktualisieren?";
|
||||
"manage_account_dialog_international_prefix_help_message" = "Wählen Sie Ihr Land aus, um Linphone die Zuordnung Ihrer Kontakte zuzulassen.";
|
||||
"meeting_call_remove_no_participants" = "Zur Zeit kein Teilnehmer…";
|
||||
"meeting_call_remove_participant_confirmation_message" = "Sind Sie sicher, dass Sie %@ entfernen möchten?";
|
||||
"meeting_call_remove_participant_confirmation_title" = "Einen Teilnehmer entfernen";
|
||||
"meeting_exported_as_calendar_event" = "Besprechung zum iPhone-Kalender hinzugefügt";
|
||||
"meeting_failed_to_edit_toast" = "Das Bearbeiten der Besprechung ist fehlgeschlagen";
|
||||
"meeting_schedule_failed_no_subject_or_participant_toast" = "Zum Erstellen eines Meetings ist ein Betreff und mindestens ein Teilnehmer erforderlich";
|
||||
"meeting_waiting_room_joining_subtitle" = "Sie werden in Kürze verbunden sein";
|
||||
"meetings_list_empty" = "Zur Zeit keine Besprechung…";
|
||||
"menu_block_address" = "Die Adresse blockieren";
|
||||
"menu_block_number" = "Die Nummer blockieren";
|
||||
"menu_copy_sip_address" = "SIP-Addresse kopieren";
|
||||
"message_copied_to_clipboard_toast" = "Nachricht in die Zwischenablage kopiert";
|
||||
"message_delivery_info_read_title" = "Gelesen";
|
||||
"message_delivery_info_received_title" = "Empfangen";
|
||||
"message_delivery_info_sent_title" = "Gesendet";
|
||||
"message_meeting_invitation_cancelled_notification" = "Die Besprechung wurde abgesagt";
|
||||
"message_meeting_invitation_notification" = "Sie sind zu einer Besprechung eingeladen";
|
||||
"message_meeting_invitation_updated_notification" = "Besprechung wurde aktualisiert";
|
||||
"message_reactions_info_all_title" = "Reaktionen";
|
||||
"network_reachable_again" = "Netzwerk ist nun wieder erreichbar";
|
||||
"None" = "Kein";
|
||||
"notification_chat_message_reaction_received" = "%@ hat mit %@ auf: %@ reagiert";
|
||||
"notification_chat_message_received_title" = "Nachricht erhalten";
|
||||
"Personnalize your profil mode" = "Ihren Profilmodus personalisieren";
|
||||
"picker_categories" = "Kategorien";
|
||||
"qr_code_validated" = "QR-Code validiert";
|
||||
"selected_participants_count" = "%@ ausgewählte Teilnehmer";
|
||||
"settings_calls_calibrate_echo_canceller_done" = "%@ ms";
|
||||
"settings_contacts_carddav_deleted_toast" = "CardDAV-Konto gelöscht";
|
||||
"settings_contacts_carddav_mandatory_field_not_filled_toast" = "Bitte geben Sie mindestens den Anzeigenamen und die Server-URL ein";
|
||||
"settings_contacts_carddav_realm_title" = "Authentifizierungsbereich";
|
||||
"settings_contacts_carddav_sync_successful_toast" = "Synchronisierung erfolgreich";
|
||||
"settings_contacts_carddav_use_as_default_title" = "Neu erstellte Kontakte hier speichern";
|
||||
"settings_contacts_ldap_bind_user_password_title" = "Benutzerkennwort binden";
|
||||
"settings_contacts_ldap_max_results_title" = "Maximale Ergebnisse";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Request timeout";
|
||||
"settings_contacts_ldap_search_filter_title" = "Filter";
|
||||
"sip_address" = "SIP-Adresse";
|
||||
"SRTP" = "SRTP";
|
||||
"TCP" = "TCP";
|
||||
"Temp Help" = "Temporärer-Hilfe";
|
||||
"text_copied_to_clipboard_toast" = "Text in die Zwischenablage kopiert";
|
||||
"TLS" = "TLS";
|
||||
"UDP" = "UDP";
|
||||
"uri_handler_bad_call_address_failed_toast" = "Anruf nicht möglich, ungültige Adresse";
|
||||
"uri_handler_bad_config_address_failed_toast" = "Konfiguration konnte nicht abgerufen werden, ungültige Adresse";
|
||||
"uri_handler_call_failed_toast" = "Anruf fehlgeschlagen";
|
||||
"uri_handler_config_failed_toast" = "Konfiguration fehlgeschlagen";
|
||||
"ZRTP" = "ZRTP";
|
||||
"welcome_page_1_message" = "Eine **sichere** **Open Source** Kommunikations-App aus Frankreich.";
|
||||
"welcome_page_3_message" = "Eine **kostenlose** Open-Source Anwendung seit **2001**.";
|
||||
"help_about_contribute_translations_title" = "Zur Übersetzung von Linphone beitragen";
|
||||
"help_about_privacy_policy_subtitle" = "Welche Informationen Linphone sammelt und nutzt";
|
||||
"help_about_title" = "Über Linphone";
|
||||
"help_about_user_guide_title" = "Linphone Benutzerhandbuch";
|
||||
"help_about_open_source_licenses_title" = "GNU General Public License v3.0";
|
||||
"help_about_open_source_licenses_subtitle" = "© Belledonne Communications 2010-2024";
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@
|
|||
"account_settings_im_encryption_mandatory_title" = "IM encryption mandatory";
|
||||
"account_settings_lime_server_url_title" = "E2E encryption keys server URL";
|
||||
"account_settings_mwi_uri_title" = "MWI server URI (Message Waiting Indicator)";
|
||||
"account_settings_apply_international_prefix_title" = "Format phone numbers using international prefix";
|
||||
"account_settings_replace_plus_by_00_title" = "Replace + by 00 when formatting phone numbers";
|
||||
"account_settings_nat_policy_title" = "NAT policy settings";
|
||||
"account_settings_outbound_proxy_title" = "Outbound proxy";
|
||||
"account_settings_push_notification_not_available_title" = "Push notifications aren't available!";
|
||||
|
|
@ -93,6 +95,7 @@
|
|||
"assistant_third_party_sip_account_warning_explanation" = "Some features require a %@ account, such as group messaging, video conferences…\n\nThese features are hidden when you register with a third party SIP account.\n\nTo enable it in a commercial project, please contact us.";
|
||||
"assistant_third_party_sip_account_warning_ok" = "I understand";
|
||||
"assistant_web_platform_link" = "subscribe.linphone.org";
|
||||
"authentication_id" = "Authentication ID (if different)";
|
||||
"bottom_navigation_calls_label" = "Calls";
|
||||
"bottom_navigation_contacts_label" = "Contacts";
|
||||
"bottom_navigation_conversations_label" = "Conversations";
|
||||
|
|
@ -187,6 +190,7 @@
|
|||
"contacts_list_favourites_title" = "Favourites";
|
||||
"contacts_list_filter_popup_see_all" = "See all";
|
||||
"contacts_list_filter_popup_see_linphone_only" = "See %@ contacts";
|
||||
"contacts_list_filter_popup_see_sip_only" = "See SIP contacts";
|
||||
"conversation_action_call" = "Call";
|
||||
"conversation_action_configure_ephemeral_messages" = "Configure ephemeral messages";
|
||||
"conversation_action_delete" = "Delete conversation";
|
||||
|
|
@ -200,6 +204,13 @@
|
|||
"conversation_dialog_edit_subject" = "Edit conversation subject";
|
||||
"conversation_dialog_set_subject" = "Set conversation subject";
|
||||
"conversation_dialog_subject_hint" = "Conversation subject";
|
||||
"conversation_editing_message_title" = "Message being edited";
|
||||
"conversation_message_edited_label" = "Edited";
|
||||
"conversation_dialog_delete_chat_message_title" = "Delete this message?";
|
||||
"conversation_dialog_delete_locally_label" = "For me";
|
||||
"conversation_dialog_delete_for_everyone_label" = "For everyone";
|
||||
"conversation_message_content_deleted_label" = "This message has been deleted";
|
||||
"conversation_message_content_deleted_by_us_label" = "You have deleted this message";
|
||||
"conversation_end_to_end_encrypted_bottom_sheet_link" = "https://linphone.org/en/features/#security";
|
||||
"conversation_end_to_end_encrypted_event_title" = "End-to-end encrypted conversation";
|
||||
"conversation_end_to_end_encrypted_event_subtitle" = "Messages in this conversation are e2e encrypted. Only your correspondent can decrypt them.";
|
||||
|
|
@ -247,6 +258,8 @@
|
|||
"conversation_message_meeting_cancelled_label" = "Meeting has been cancelled!";
|
||||
"conversation_message_meeting_updated_label" = "Meeting has been updated";
|
||||
"conversation_one_to_one_hidden_subject" = "Dummy subject";
|
||||
"conversation_participants_list_empty" = "No participants found";
|
||||
"conversation_participants_list_header" = "Participants";
|
||||
"conversation_reply_to_message_title" = "Replying to: ";
|
||||
"conversation_text_field_hint" = "Say something…";
|
||||
"conversations_list_empty" = "No conversation for the moment…";
|
||||
|
|
@ -266,7 +279,7 @@
|
|||
"dialog_deny" = "Deny";
|
||||
"dialog_install" = "Install";
|
||||
"dialog_no" = "No";
|
||||
"dialog_ok" = "OK";
|
||||
"dialog_confirm" = "Confirm";
|
||||
"dialog_yes" = "Yes";
|
||||
"drawer_menu_account_connection_status_cleared" = "Disabled";
|
||||
"drawer_menu_account_connection_status_connected" = "Connected";
|
||||
|
|
@ -390,21 +403,26 @@
|
|||
"menu_delete_selected_item" = "Delete";
|
||||
"menu_forward_chat_message" = "Forward";
|
||||
"menu_invite" = "Invite";
|
||||
"menu_edit_chat_message" = "Edit";
|
||||
"menu_reply_to_chat_message" = "Reply";
|
||||
"menu_resend_chat_message" = "Re-send";
|
||||
"menu_see_existing_contact" = "See contact";
|
||||
"menu_show_imdn" = "Delivery status";
|
||||
"menu_export_selected_item" = "Download";
|
||||
"menu_share_selected_item" = "Share";
|
||||
"message_copied_to_clipboard_toast" = "Message copied into clipboard";
|
||||
"message_delivery_info_error_title" = "Error";
|
||||
"message_delivery_info_read_title" = "Read";
|
||||
"message_delivery_info_received_title" = "Received";
|
||||
"message_delivery_info_sent_title" = "Sent";
|
||||
"message_forwarded_label" = "Forwarded";
|
||||
"message_meeting_invitation_cancelled_notification" = "📅 Meeting has been cancelled";
|
||||
"message_meeting_invitation_notification" = "📅 You are invited to a meeting";
|
||||
"message_meeting_invitation_updated_notification" = "📅 Meeting has been updated";
|
||||
"message_meeting_invitation_cancelled_notification" = "Meeting has been cancelled";
|
||||
"message_meeting_invitation_notification" = "You are invited to a meeting";
|
||||
"message_meeting_invitation_updated_notification" = "Meeting has been updated";
|
||||
"message_reaction_click_to_remove_label" = "Click to remove";
|
||||
"message_reactions_info_all_title" = "Reactions";
|
||||
"mwi_messages_are_waiting_single" = "1 new voice message";
|
||||
"mwi_messages_are_waiting_multiple" = "%@ new voice messages";
|
||||
"network_not_reachable" = "You aren't connected to internet";
|
||||
"network_reachable_again" = "Network is now reachable again";
|
||||
"new_conversation_create_group" = "Create a group conversation";
|
||||
|
|
@ -423,6 +441,10 @@
|
|||
"picker_categories" = "Categories";
|
||||
"qr_code_validated" = "QR code validated";
|
||||
"recordings_title" = "Recordings";
|
||||
"recordings_list_empty" = "No recording for the moment…";
|
||||
"recordings_list_empty" = "No recording for the moment…";
|
||||
"recordings_list_empty" = "No recording for the moment…";
|
||||
"recordings_list_empty" = "No recording for the moment…";
|
||||
"selected_participants_count" = "%@ selected participants";
|
||||
"settings_advanced_accept_early_media_title" = "Accept early media";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Allow outgoing early media";
|
||||
|
|
@ -466,15 +488,22 @@
|
|||
"settings_contacts_carddav_username_title" = "Username";
|
||||
"settings_contacts_edit_carddav_server_title" = "Edit CardDAV address book";
|
||||
"settings_contacts_edit_ldap_server_title" = "Edit LDAP server";
|
||||
"settings_contacts_ldap_enabled_title" = "Enabled";
|
||||
"settings_contacts_ldap_server_url_title" = "Server URL (can't be empty)";
|
||||
"settings_contacts_ldap_bind_dn_title" = "Bind DN";
|
||||
"settings_contacts_ldap_bind_user_password_title" = "Bind user password";
|
||||
"settings_contacts_ldap_max_results_title" = "Maximum results";
|
||||
"settings_contacts_ldap_password_title" = "Password";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Request timeout";
|
||||
"settings_contacts_ldap_use_tls_title" = "Use TLS";
|
||||
"settings_contacts_ldap_search_base_title" = "Search base (can't be empty)";
|
||||
"settings_contacts_ldap_search_filter_title" = "Filter";
|
||||
"settings_contacts_ldap_server_url_title" = "Server URL (can't be empty)";
|
||||
"settings_contacts_ldap_use_tls_title" = "Use TLS";
|
||||
"settings_contacts_ldap_max_results_title" = "Max results";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Timeout (in seconds)";
|
||||
"settings_contacts_ldap_request_delay_title" = "Delay between two queries (in milliseconds)";
|
||||
"settings_contacts_ldap_min_characters_title" = "Min characters to start a query";
|
||||
"settings_contacts_ldap_name_attributes_title" = "Name attributes";
|
||||
"settings_contacts_ldap_sip_attributes_title" = "SIP attributes";
|
||||
"settings_contacts_ldap_sip_domain_title" = "SIP domain";
|
||||
"settings_contacts_ldap_error_toast" = "A error occurred, LDAP server not saved!";
|
||||
"settings_contacts_ldap_empty_server_error_toast" = "Server URL can't be empty";
|
||||
"settings_contacts_title" = "Contacts";
|
||||
"settings_conversations_auto_download_title" = "Auto-download files";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Mark conversation as read when dismissing message notification";
|
||||
|
|
@ -510,6 +539,8 @@
|
|||
"uri_handler_config_success_toast" = "Configuration successfully applied";
|
||||
"username" = "Username";
|
||||
"username_error" = "Username error";
|
||||
"Voicemail" = "Voicemail";
|
||||
"New message" = "New message";
|
||||
"web_platform_forgotten_password_url" = "https://subscribe.linphone.org/";
|
||||
"web_platform_register_email_url" = "https://subscribe.linphone.org/register/email";
|
||||
"website_contact_url" = "https://linphone.org/contact";
|
||||
|
|
|
|||
161
Linphone/Localizable/es.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
"settings_calls_change_ringtone_title" = "Cambiar tono de llamada";
|
||||
"settings_calls_echo_canceller_subtitle" = "Evita que el eco se escuche en el extremo remoto si no hay un cancelador de eco de hardware disponible";
|
||||
"settings_calls_echo_canceller_title" = "Utilizar el cancelador de eco de software";
|
||||
"settings_calls_enable_fec_title" = "Habilitar FEC de vídeo";
|
||||
"settings_calls_enable_video_title" = "Habilitar video";
|
||||
"settings_calls_title" = "Llamadas";
|
||||
"settings_calls_vibrate_while_ringing_title" = "Vibrar mientras suena la llamada entrante";
|
||||
"settings_contacts_add_carddav_server_title" = "Agregar libreta de direcciones CardDAV";
|
||||
"settings_contacts_add_ldap_server_title" = "Agregar servidor LDAP";
|
||||
"settings_contacts_carddav_name_title" = "Nombre para mostrar";
|
||||
"settings_contacts_carddav_password_title" = "Clave";
|
||||
"settings_contacts_carddav_server_url_title" = "URL del servidor";
|
||||
"settings_contacts_carddav_sync_error_toast" = "Error de sincronización!";
|
||||
"settings_contacts_carddav_username_title" = "Nombre de Usuario";
|
||||
"settings_contacts_edit_carddav_server_title" = "Editar libreta de direcciones CardDAV";
|
||||
"settings_contacts_edit_ldap_server_title" = "Editar servidor LDAP";
|
||||
"settings_contacts_ldap_password_title" = "Clave";
|
||||
"settings_contacts_title" = "Contactos";
|
||||
"settings_conversations_auto_download_title" = "Descarga automática de archivos";
|
||||
"dialog_no" = "No";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Marcar la conversación como leída al descartar la notificación del mensaje";
|
||||
"settings_conversations_title" = "Conversaciones";
|
||||
"settings_meetings_default_layout_title" = "Diseño predeterminado";
|
||||
"settings_meetings_layout_active_speaker_label" = "Altavoz activo";
|
||||
"settings_meetings_layout_mosaic_label" = "Mosaico";
|
||||
"settings_meetings_title" = "Reuniones";
|
||||
"settings_network_allow_ipv6" = "Permitir IPv6";
|
||||
"settings_network_title" = "Red";
|
||||
"settings_network_use_wifi_only" = "Use solo redes Wi-fi";
|
||||
"settings_security_enable_vfs_subtitle" = "Advertencia: una vez habilitado, ¡no se puede deshabilitar!";
|
||||
"settings_security_enable_vfs_title" = "Cifrar todo";
|
||||
"settings_security_prevent_screenshots_title" = "Evitar que se grabe la interfaz";
|
||||
"settings_security_title" = "Seguridad";
|
||||
"settings_title" = "Ajustes";
|
||||
"sip_address_copied_to_clipboard_toast" = "Dirección SIP copiada al portapapeles";
|
||||
"sip_address_display_name" = "Nombre para mostrar";
|
||||
"sip_address_domain" = "Dominio";
|
||||
"start" = "Iniciar";
|
||||
"uri_handler_config_success_toast" = "Configuración aplicada satisfactoriamente";
|
||||
"username" = "Nombre de Usuario";
|
||||
"welcome_page_2_title" = "Segura";
|
||||
"welcome_page_3_title" = "código abierto";
|
||||
"welcome_page_title" = "Bienvenido";
|
||||
"account_settings_dialog_invalid_password_hint" = "Clave";
|
||||
"account_settings_title" = "Configuración de la cuenta";
|
||||
"assistant_account_create" = "Crear";
|
||||
"assistant_account_creation_wrong_phone_number" = "Número equivocado?";
|
||||
"assistant_account_login" = "Acceso";
|
||||
"assistant_account_login_forbidden_error" = "Nombre de usuario o contraseña incorrectos";
|
||||
"assistant_account_register" = "Registrar";
|
||||
"assistant_account_register_push_notification_not_received_error" = "Notificación push con token de autenticación no recibida en 5 segundos, inténtelo nuevamente más tarde";
|
||||
"assistant_account_register_unexpected_error" = "Se produjo un error inesperado, inténtelo de nuevo más tarde";
|
||||
"assistant_already_have_an_account" = "Ya tienes una cuenta?";
|
||||
"assistant_create_account_using_email_on_our_web_platform" = "Crea una cuenta con tu correo electrónico en:";
|
||||
"assistant_dialog_confirm_phone_number_title" = "Confirmar número de teléfono";
|
||||
"assistant_dialog_general_terms_and_privacy_policy_title" = "Términos generales y política de privacidad";
|
||||
"assistant_dialog_general_terms_label" = "términos generales";
|
||||
"assistant_dialog_privacy_policy_label" = "política de privacidad";
|
||||
"assistant_login_third_party_sip_account" = "Utilice una cuenta SIP de terceros";
|
||||
"assistant_no_account_yet" = "Aún no tienes cuenta?";
|
||||
"assistant_permissions_grant_all_of_them" = "Ok";
|
||||
"assistant_permissions_skip_permissions" = "Hazlo más tarde";
|
||||
"assistant_permissions_title" = "Conceder permisos";
|
||||
"assistant_qr_code_invalid_toast" = "Código QR no válido!";
|
||||
"assistant_scan_qr_code" = "Escanear código QR";
|
||||
"assistant_sip_account_transport_protocol" = "Transporte";
|
||||
"assistant_third_party_sip_account_warning_ok" = "Yo entiendo";
|
||||
"authentication_id" = "ID de autenticación (si es diferente)";
|
||||
"bottom_navigation_calls_label" = "Llamadas";
|
||||
"bottom_navigation_contacts_label" = "Contactos";
|
||||
"bottom_navigation_conversations_label" = "Conversaciones";
|
||||
"bottom_navigation_meetings_label" = "Reuniones";
|
||||
"call_stats_media_encryption_title" = "Cifrado de medios";
|
||||
"conference_layout_grid" = "Mosaico";
|
||||
"contact_call_action" = "Llamar";
|
||||
"contact_details_delete" = "Eliminar";
|
||||
"conversation_action_call" = "Llamar";
|
||||
"conversation_action_mark_as_read" = "Marcar como leído";
|
||||
"conversation_ephemeral_messages_duration_disabled" = "Deshabilitado";
|
||||
"dialog_accept" = "Aceptar";
|
||||
"dialog_call" = "Llamar";
|
||||
"dialog_cancel" = "Cancelar";
|
||||
"dialog_continue" = "Continuar";
|
||||
"dialog_deny" = "Denegar";
|
||||
"dialog_install" = "Instalar";
|
||||
"dialog_ok" = "Ok";
|
||||
"dialog_yes" = "Si";
|
||||
"drawer_menu_account_connection_status_cleared" = "Deshabilitado";
|
||||
"drawer_menu_account_connection_status_connected" = "Conectado";
|
||||
"drawer_menu_account_connection_status_failed" = "Error";
|
||||
"drawer_menu_account_connection_status_progress" = "Conectando…";
|
||||
"drawer_menu_add_account" = "Agregar una cuenta";
|
||||
"drawer_menu_manage_account" = "Administrar el perfil";
|
||||
"drawer_menu_no_account_configured_yet" = "Cuenta no configurada aun";
|
||||
"Error" = "Error";
|
||||
"help_about_advanced_title" = "Avanzado";
|
||||
"help_about_check_for_update" = "Comprobar actualización";
|
||||
"help_about_privacy_policy_title" = "Política de privacidad";
|
||||
"help_about_user_guide_subtitle" = "Aprenda a dominar todas las funciones de la aplicación, paso a paso.";
|
||||
"help_about_version_title" = "Versión";
|
||||
"help_dialog_update_available_title" = "Actualización disponible";
|
||||
"help_error_checking_version_toast_message" = "Se produjo un error al buscar actualizaciones";
|
||||
"help_quit_title" = "Salir de la aplicación";
|
||||
"help_title" = "Ayuda";
|
||||
"help_troubleshooting_app_version_title" = "Versión de la aplicación";
|
||||
"help_troubleshooting_clean_logs" = "Limpiar registros";
|
||||
"help_troubleshooting_clear_native_friends_in_database" = "Borrar contactos importados de la libreta de direcciones nativa";
|
||||
"help_troubleshooting_debug_logs_cleaned_toast_message" = "Se han limpiado los registros de depuración";
|
||||
"help_troubleshooting_debug_logs_upload_error_toast_message" = "No se pudieron cargar los registros de depuración";
|
||||
"help_troubleshooting_firebase_project_title" = "ID del proyecto de Firebase";
|
||||
"help_troubleshooting_print_logs_in_logcat" = "Imprimir registros en logcat";
|
||||
"help_troubleshooting_sdk_version_title" = "Versión SDK";
|
||||
"help_troubleshooting_share_logs" = "Compartir registros";
|
||||
"help_troubleshooting_share_logs_dialog_title" = "Compartir enlaces de registros de depuración usando…";
|
||||
"help_troubleshooting_show_config_file" = "Mostrar configuracion";
|
||||
"help_troubleshooting_title" = "Solución de problemas";
|
||||
"help_version_up_to_date_toast_message" = "Su versión esta actualizada";
|
||||
"Interoperable mode" = "Modo interoperable";
|
||||
"manage_account_add_picture" = "Agregar una imagen";
|
||||
"manage_account_delete" = "Cerrar sesión";
|
||||
"manage_account_details_title" = "Detalles";
|
||||
"manage_account_device_remove" = "Remover";
|
||||
"manage_account_devices_title" = "Dispositivos";
|
||||
"manage_account_edit_picture" = "Editar imagen";
|
||||
"manage_account_international_prefix" = "Prefijo internacional";
|
||||
"manage_account_no_device" = "No se encontró ningún dispositivo…";
|
||||
"manage_account_remove_picture" = "Remover imagen";
|
||||
"manage_account_settings" = "Configuración de la cuenta";
|
||||
"manage_account_status_cleared_summary" = "La cuenta ha sido desactivada, no recibirás ninguna llamada ni mensaje.";
|
||||
"manage_account_status_connected_summary" = "Esta cuenta está en línea, cualquiera puede llamarte.";
|
||||
"manage_account_status_failed_summary" = "Error al conectar la cuenta, comprueba tu configuración.";
|
||||
"manage_account_status_progress_summary" = "La cuenta se está conectando al servidor, por favor espere…";
|
||||
"manage_account_title" = "Administrar cuenta";
|
||||
"meeting_waiting_room_cancel" = "Cancelar";
|
||||
"menu_delete_selected_item" = "Eliminar";
|
||||
"menu_reply_to_chat_message" = "Responder";
|
||||
"message_delivery_info_error_title" = "Error";
|
||||
"next" = "Siguiente";
|
||||
"notification_missed_call_title" = "Llamada perdida";
|
||||
"or" = "o";
|
||||
"password" = "Clave";
|
||||
"phone_number" = "Número de teléfono";
|
||||
"settings_advanced_accept_early_media_title" = "Acepta los primeros medios de comunicación";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Permitir la salida temprana de medios";
|
||||
"settings_advanced_audio_codecs_title" = "Códecs de audio";
|
||||
"settings_advanced_audio_devices_title" = "Dispositivos de audio";
|
||||
"settings_advanced_device_id" = "Identificación de Dispositivo";
|
||||
"settings_advanced_device_id_hint" = "Solo caracteres alfanuméricos";
|
||||
"settings_advanced_download_apply_remote_provisioning" = "Descargar y aplicar";
|
||||
"settings_advanced_input_audio_device_title" = "Dispositivo de audio de entrada predeterminado";
|
||||
"settings_advanced_media_encryption_mandatory_title" = "Cifrado obligatorio de los medios de comunicación";
|
||||
"settings_advanced_output_audio_device_title" = "Dispositivo de salida de audio predeterminado";
|
||||
"settings_advanced_remote_provisioning_url" = "URL de aprovisionamiento remoto";
|
||||
"settings_advanced_title" = "Configuración avanzada";
|
||||
"settings_advanced_upload_server_url" = "URL del servidor para compartir archivos";
|
||||
"settings_advanced_video_codecs_title" = "Códecs de video";
|
||||
"settings_calls_auto_record_title" = "Iniciar automáticamente la grabación de llamadas";
|
||||
"settings_calls_calibrate_echo_canceller_done_no_echo" = "sin eco";
|
||||
"settings_calls_calibrate_echo_canceller_failed" = "fallido";
|
||||
"settings_calls_calibrate_echo_canceller_in_progress" = "en progreso";
|
||||
"settings_calls_calibrate_echo_canceller_title" = "Calibrar el cancelador de eco";
|
||||
16
Linphone/Localizable/eu.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"next" = "Hurrengoa";
|
||||
"account_settings_dialog_invalid_password_hint" = "Pasahitza";
|
||||
"dialog_accept" = "Onartu";
|
||||
"dialog_deny" = "Ukatu";
|
||||
"or" = "edo";
|
||||
"password" = "Pasahitza";
|
||||
"phone_number" = "Telefono zenbakia";
|
||||
"settings_advanced_device_id" = "Gailuaren IDa";
|
||||
"settings_contacts_carddav_name_title" = "Erakusteko izena";
|
||||
"settings_contacts_carddav_password_title" = "Pasahitza";
|
||||
"settings_contacts_carddav_username_title" = "Erabiltzaile izena";
|
||||
"settings_contacts_ldap_password_title" = "Pasahitza";
|
||||
"sip_address_display_name" = "Erakusteko izena";
|
||||
"sip_address_domain" = "Domeinua";
|
||||
"start" = "Hasi";
|
||||
"username" = "Erabiltzaile izena";
|
||||
31
Linphone/Localizable/fi.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"account_settings_audio_video_conference_factory_uri_title" = "Audio/video kokous, tehdas URI";
|
||||
"account_settings_dialog_invalid_password_title" = "Todennus tarvitaan";
|
||||
": %@" = ": %@";
|
||||
"[linphone.org/contact](https://linphone.org/contact)" = "[linphone.org/contact](https://linphone.org/contact)";
|
||||
"*" = "*";
|
||||
"**%@**" = "**%@**";
|
||||
"#" = "#";
|
||||
"%@" = "%@";
|
||||
"%lld" = "%lld";
|
||||
"%lld %@" = "%1$lld %2$@";
|
||||
"+" = "+";
|
||||
"|" = "|";
|
||||
"❤️" = "❤️";
|
||||
"👍" = "👍";
|
||||
"%lld%%" = "%lld%%";
|
||||
"😂" = "😂";
|
||||
"😢" = "😢";
|
||||
"😮" = "😮";
|
||||
"0" = "0";
|
||||
"1" = "1";
|
||||
"2" = "2";
|
||||
"3" = "3";
|
||||
"4" = "4";
|
||||
"5" = "5";
|
||||
"6" = "6";
|
||||
"7" = "7";
|
||||
"8" = "8";
|
||||
"9" = "9";
|
||||
"account_settings_avpf_title" = "AVPF";
|
||||
"ZRTP" = "ZRTP";
|
||||
"[https://sip.linphone.org](https://sip.linphone.org)" = "[https://sip.linphone.org](https://sip.linphone.org)";
|
||||
|
|
@ -47,6 +47,8 @@
|
|||
"account_settings_im_encryption_mandatory_title" = "Chiffrement obligatoire des conversations";
|
||||
"account_settings_lime_server_url_title" = "URL du serveur d'échange de clés de chiffrement";
|
||||
"account_settings_mwi_uri_title" = "URI du serveur MWI (Message Waiting Indicator)";
|
||||
"account_settings_apply_international_prefix_title" = "Formater les numéros en utilisant l'indicatif international";
|
||||
"account_settings_replace_plus_by_00_title" = "Remplacer + par 00 lors du formatage des numéros de téléphone";
|
||||
"account_settings_nat_policy_title" = "Paramètres de politique NAT";
|
||||
"account_settings_outbound_proxy_title" = "Serveur mandataire sortant";
|
||||
"account_settings_push_notification_not_available_title" = "Notifications push non disponibles";
|
||||
|
|
@ -93,6 +95,7 @@
|
|||
"assistant_third_party_sip_account_warning_explanation" = "Certaines fonctionnalités telles que les conversations de groupe, les vidéo-conférences, etc… nécessitent un compte %@.\n\nCes fonctionnalités seront masquées si vous utilisez un compte SIP tiers.\n\nPour les activer dans un projet commercial, merci de nous contacter.";
|
||||
"assistant_third_party_sip_account_warning_ok" = "J’ai compris";
|
||||
"assistant_web_platform_link" = "subscribe.linphone.org";
|
||||
"authentication_id" = "Identifiant de connexion (si différent)";
|
||||
"bottom_navigation_calls_label" = "Appels";
|
||||
"bottom_navigation_contacts_label" = "Contacts";
|
||||
"bottom_navigation_conversations_label" = "Conversations";
|
||||
|
|
@ -187,6 +190,7 @@
|
|||
"contacts_list_favourites_title" = "Favoris";
|
||||
"contacts_list_filter_popup_see_all" = "Tous les contacts";
|
||||
"contacts_list_filter_popup_see_linphone_only" = "Contacts %@";
|
||||
"contacts_list_filter_popup_see_sip_only" = "Contacts SIP";
|
||||
"conversation_action_call" = "Appeler";
|
||||
"conversation_action_configure_ephemeral_messages" = "Configurer les messages éphémères";
|
||||
"conversation_action_delete" = "Supprimer la conversation";
|
||||
|
|
@ -200,6 +204,13 @@
|
|||
"conversation_dialog_edit_subject" = "Renommer la conversation";
|
||||
"conversation_dialog_set_subject" = "Nommer la conversation";
|
||||
"conversation_dialog_subject_hint" = "Nom de la conversation";
|
||||
"conversation_editing_message_title" = "Modification du message";
|
||||
"conversation_message_edited_label" = "Modifié";
|
||||
"conversation_dialog_delete_chat_message_title" = "Supprimer le message ?";
|
||||
"conversation_dialog_delete_locally_label" = "Pour moi";
|
||||
"conversation_dialog_delete_for_everyone_label" = "Pour tout le monde";
|
||||
"conversation_message_content_deleted_label" = "Le message a été supprimé";
|
||||
"conversation_message_content_deleted_by_us_label" = "Vous avez supprimé le message";
|
||||
"conversation_end_to_end_encrypted_bottom_sheet_link" = "https://linphone.org/en/features/#security";
|
||||
"conversation_end_to_end_encrypted_event_title" = "Conversation chiffrée de bout en bout";
|
||||
"conversation_end_to_end_encrypted_event_subtitle" = "Les messages de cette conversation sont chiffrés de bout en bout. Seul votre correspondant peut les déchiffrer.";
|
||||
|
|
@ -247,6 +258,8 @@
|
|||
"conversation_message_meeting_cancelled_label" = "La réunion a été annulée";
|
||||
"conversation_message_meeting_updated_label" = "La réunion a été mise à jour";
|
||||
"conversation_one_to_one_hidden_subject" = "Dummy subject";
|
||||
"conversation_participants_list_empty" = "Aucun participant trouvé";
|
||||
"conversation_participants_list_header" = "Participants";
|
||||
"conversation_reply_to_message_title" = "En réponse à : ";
|
||||
"conversation_text_field_hint" = "Dites quelque chose…";
|
||||
"conversations_list_empty" = "Aucune conversation pour le moment…";
|
||||
|
|
@ -266,7 +279,7 @@
|
|||
"dialog_deny" = "Refuser";
|
||||
"dialog_install" = "Installer";
|
||||
"dialog_no" = "Non";
|
||||
"dialog_ok" = "OK";
|
||||
"dialog_confirm" = "Confirmer";
|
||||
"dialog_yes" = "Oui";
|
||||
"drawer_menu_account_connection_status_cleared" = "Désactivé";
|
||||
"drawer_menu_account_connection_status_connected" = "Connecté";
|
||||
|
|
@ -337,7 +350,7 @@
|
|||
"manage_account_international_prefix" = "Indicatif international";
|
||||
"manage_account_no_device" = "Aucun appareil n'a été trouvé…";
|
||||
"manage_account_remove_picture" = "Supprimer";
|
||||
"manage_account_settings" = "Mon compte";
|
||||
"manage_account_settings" = "Paramètres";
|
||||
"manage_account_status_cleared_summary" = "Compte désactivé, vous ne recevrez ni appel ni message.";
|
||||
"manage_account_status_connected_summary" = "Vous êtes en ligne, on peut vous joindre.";
|
||||
"manage_account_status_failed_summary" = "Erreur de connexion, vérifiez vos paramètres.";
|
||||
|
|
@ -390,21 +403,26 @@
|
|||
"menu_delete_selected_item" = "Supprimer";
|
||||
"menu_forward_chat_message" = "Transférer";
|
||||
"menu_invite" = "Inviter";
|
||||
"menu_edit_chat_message" = "Modifier";
|
||||
"menu_reply_to_chat_message" = "Répondre";
|
||||
"menu_resend_chat_message" = "Ré-envoyer";
|
||||
"menu_see_existing_contact" = "Voir le contact";
|
||||
"menu_show_imdn" = "Info de réception";
|
||||
"menu_export_selected_item" = "Télécharger";
|
||||
"menu_share_selected_item" = "Partager";
|
||||
"message_copied_to_clipboard_toast" = "Message copié dans le presse-papier";
|
||||
"message_delivery_info_error_title" = "En erreur";
|
||||
"message_delivery_info_read_title" = "Lu";
|
||||
"message_delivery_info_received_title" = "Reçu";
|
||||
"message_delivery_info_sent_title" = "Envoyé";
|
||||
"message_forwarded_label" = "Transféré";
|
||||
"message_meeting_invitation_cancelled_notification" = "📅 Réunion annulée";
|
||||
"message_meeting_invitation_notification" = "📅 Invitation à une réunion";
|
||||
"message_meeting_invitation_updated_notification" = "📅 Réunion mise à jour";
|
||||
"message_meeting_invitation_cancelled_notification" = "Réunion annulée";
|
||||
"message_meeting_invitation_notification" = "Invitation à une réunion";
|
||||
"message_meeting_invitation_updated_notification" = "Réunion mise à jour";
|
||||
"message_reaction_click_to_remove_label" = "Cliquez pour supprimer";
|
||||
"message_reactions_info_all_title" = "Réactions";
|
||||
"mwi_messages_are_waiting_single" = "1 message vocal en attente";
|
||||
"mwi_messages_are_waiting_multiple" = "%@ messages vocaux en attente";
|
||||
"network_not_reachable" = "Vous n’êtes pas connecté à internet";
|
||||
"network_reachable_again" = "Vous êtes à nouveau connecté à internet";
|
||||
"new_conversation_create_group" = "Créer une conversation de groupe";
|
||||
|
|
@ -423,6 +441,7 @@
|
|||
"picker_categories" = "Catégories";
|
||||
"qr_code_validated" = "QR code validé";
|
||||
"recordings_title" = "Enregistrements";
|
||||
"recordings_list_empty" = "Aucun appel enregistré…";
|
||||
"selected_participants_count" = "%@ participants selectionnés";
|
||||
"settings_advanced_accept_early_media_title" = "Accepter l'early media";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Autoriser l'early media pour les appels sortants";
|
||||
|
|
@ -466,15 +485,22 @@
|
|||
"settings_contacts_carddav_username_title" = "Nom d'utilisateur";
|
||||
"settings_contacts_edit_carddav_server_title" = "Editer le carnet d'adresse CardDAV";
|
||||
"settings_contacts_edit_ldap_server_title" = "Editer le serveur LDAP";
|
||||
"settings_contacts_ldap_enabled_title" = "Activé";
|
||||
"settings_contacts_ldap_server_url_title" = "URL du serveur (ne peut être vide)";
|
||||
"settings_contacts_ldap_bind_dn_title" = "Bind DN";
|
||||
"settings_contacts_ldap_bind_user_password_title" = "Mot de passe de l'utilisateur Bind";
|
||||
"settings_contacts_ldap_max_results_title" = "Nombre de résultats maximum";
|
||||
"settings_contacts_ldap_password_title" = "Mot de passe";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Délai d'attente de la requête";
|
||||
"settings_contacts_ldap_use_tls_title" = "Utiliser TLS";
|
||||
"settings_contacts_ldap_search_base_title" = "Base de recherche (ne peut être vide)";
|
||||
"settings_contacts_ldap_search_filter_title" = "Filtre";
|
||||
"settings_contacts_ldap_server_url_title" = "URL du serveur (ne peut être vide)";
|
||||
"settings_contacts_ldap_use_tls_title" = "Utiliser TLS";
|
||||
"settings_contacts_ldap_max_results_title" = "Nombre de résultats maximum";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Durée maximum (en secondes)";
|
||||
"settings_contacts_ldap_request_delay_title" = "Délai entre 2 requêtes (en millisecondes)";
|
||||
"settings_contacts_ldap_min_characters_title" = "Nombre minimum de caractères pour lancer la requête";
|
||||
"settings_contacts_ldap_name_attributes_title" = "Attributs de nom";
|
||||
"settings_contacts_ldap_sip_attributes_title" = "Attributs SIP";
|
||||
"settings_contacts_ldap_sip_domain_title" = "Domaine SIP";
|
||||
"settings_contacts_ldap_error_toast" = "Une erreur s'est produite, la configuration LDAP n'a pas été sauvegardée !";
|
||||
"settings_contacts_ldap_empty_server_error_toast" = "L'URL du serveur ne peut être vide";
|
||||
"settings_contacts_title" = "Contacts";
|
||||
"settings_conversations_auto_download_title" = "Télécharger automatiquement les fichiers";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Marquer la conversation comme lue lorsqu'une notification de message est supprimée";
|
||||
|
|
@ -510,6 +536,8 @@
|
|||
"uri_handler_config_success_toast" = "Configuration appliquée avec succè";
|
||||
"username" = "Nom d’utilisateur";
|
||||
"username_error" = "Erreur dans le nom d'utilisateur";
|
||||
"Voicemail" = "Messagerie vocale";
|
||||
"New message" = "Nouveau message";
|
||||
"web_platform_forgotten_password_url" = "https://subscribe.linphone.org/";
|
||||
"web_platform_register_email_url" = "https://subscribe.linphone.org/register/email";
|
||||
"website_contact_url" = "https://linphone.org/contact";
|
||||
|
|
|
|||
86
Linphone/Localizable/hu.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"account_settings_dialog_invalid_password_hint" = "Jelszó";
|
||||
"assistant_account_create" = "Létrehoz";
|
||||
"assistant_account_creation_wrong_phone_number" = "Rossz telefonszám?";
|
||||
"assistant_account_login" = "Bejelentkezés";
|
||||
"assistant_account_login_forbidden_error" = "Hibás felhasználónév vagy jelszó";
|
||||
"assistant_account_register" = "Regisztráció";
|
||||
"assistant_account_register_push_notification_not_received_error" = "A push értesítés az azonosító tokennel nem érkezett meg 5mp-en belül, próbálja meg később";
|
||||
"assistant_account_register_unexpected_error" = "Váratlan hiba történt, próbálja meg később";
|
||||
"assistant_already_have_an_account" = "Már van fiókja?";
|
||||
"assistant_create_account_using_email_on_our_web_platform" = "Hozzon létre egy fiókot az email–címével:";
|
||||
"assistant_dialog_confirm_phone_number_title" = "Erősítse meg a telefonszámot";
|
||||
"assistant_dialog_general_terms_and_privacy_policy_title" = "Általános feltételek és biztonsági szabályok";
|
||||
"assistant_dialog_general_terms_label" = "általános feltételek";
|
||||
"assistant_dialog_privacy_policy_label" = "biztonsági szabályok";
|
||||
"assistant_login_third_party_sip_account" = "Harmadik féltől származó SIP fiók használata";
|
||||
"assistant_no_account_yet" = "Nincs még fiókja?";
|
||||
"assistant_permissions_grant_all_of_them" = "OK";
|
||||
"assistant_permissions_skip_permissions" = "Később";
|
||||
"assistant_permissions_title" = "Engedélyek biztosítása";
|
||||
"assistant_qr_code_invalid_toast" = "Érvénytelen QR–kód!";
|
||||
"assistant_scan_qr_code" = "QR–kód beolvasása";
|
||||
"assistant_sip_account_transport_protocol" = "SIP átvitel";
|
||||
"assistant_third_party_sip_account_warning_ok" = "Elfogadom";
|
||||
"authentication_id" = "Azonosító ID (ha különbözik)";
|
||||
"bottom_navigation_calls_label" = "Hívások";
|
||||
"bottom_navigation_contacts_label" = "Névjegyek";
|
||||
"bottom_navigation_conversations_label" = "Üzenetek";
|
||||
"bottom_navigation_meetings_label" = "Meetingek";
|
||||
"contact_call_action" = "Hívás";
|
||||
"contact_details_delete" = "Törlés";
|
||||
"conversation_action_call" = "Hívás";
|
||||
"conversation_action_mark_as_read" = "Jelölés olvasottnak";
|
||||
"conversation_ephemeral_messages_duration_disabled" = "Letiltva";
|
||||
"dialog_accept" = "Elfogad";
|
||||
"dialog_call" = "Hívás";
|
||||
"dialog_cancel" = "Mégse";
|
||||
"dialog_continue" = "Folytatás";
|
||||
"dialog_deny" = "Elutasít";
|
||||
"dialog_install" = "Telepítés";
|
||||
"dialog_no" = "Nem";
|
||||
"dialog_ok" = "OK";
|
||||
"dialog_yes" = "Igen";
|
||||
"drawer_menu_account_connection_status_cleared" = "Letiltva";
|
||||
"drawer_menu_account_connection_status_connected" = "Csatlakoztatva";
|
||||
"drawer_menu_account_connection_status_failed" = "Hiba";
|
||||
"drawer_menu_account_connection_status_progress" = "Csatlakozás…";
|
||||
"drawer_menu_add_account" = "Fiók hozzáadása";
|
||||
"drawer_menu_manage_account" = "Profil kezelése";
|
||||
"drawer_menu_no_account_configured_yet" = "Nincs még fiók konfigurálva";
|
||||
"Error" = "Hiba";
|
||||
"help_about_advanced_title" = "Haladó";
|
||||
"help_about_version_title" = "Verzió";
|
||||
"help_title" = "Segítség";
|
||||
"help_troubleshooting_clear_native_friends_in_database" = "Importált névjegyek törlése a telefon névjegyzékéből";
|
||||
"help_troubleshooting_show_config_file" = "Mutassa a konfigurációt";
|
||||
"manage_account_device_remove" = "Eltávolít";
|
||||
"meeting_waiting_room_cancel" = "Mégse";
|
||||
"menu_delete_selected_item" = "Törlés";
|
||||
"menu_reply_to_chat_message" = "Válasz";
|
||||
"message_delivery_info_error_title" = "Hiba";
|
||||
"next" = "Következő";
|
||||
"notification_missed_call_title" = "Nem fogadott hívás";
|
||||
"or" = "vagy";
|
||||
"password" = "Jelszó";
|
||||
"phone_number" = "Telefonszám";
|
||||
"settings_advanced_device_id" = "Eszköz ID";
|
||||
"settings_calls_title" = "Hívások";
|
||||
"settings_contacts_carddav_name_title" = "Megjelenített név";
|
||||
"settings_contacts_carddav_password_title" = "Jelszó";
|
||||
"settings_contacts_carddav_username_title" = "Felhasználónév";
|
||||
"settings_contacts_ldap_password_title" = "Jelszó";
|
||||
"settings_contacts_title" = "Névjegyek";
|
||||
"settings_conversations_title" = "Üzenetek";
|
||||
"settings_meetings_title" = "Meetingek";
|
||||
"settings_security_enable_vfs_title" = "Titkosítás";
|
||||
"settings_security_title" = "Biztonság";
|
||||
"settings_title" = "Beállítások";
|
||||
"sip_address_copied_to_clipboard_toast" = "SIP cím a vágólapra másolva";
|
||||
"sip_address_display_name" = "Megjelenített név";
|
||||
"sip_address_domain" = "Domain cím";
|
||||
"start" = "Indítás";
|
||||
"uri_handler_config_success_toast" = "A konfiguráció sikeresen alkalmazva";
|
||||
"username" = "Felhasználónév";
|
||||
"welcome_page_2_title" = "Biztonságos";
|
||||
"welcome_page_3_title" = "Nyílt forráskódú";
|
||||
"welcome_page_title" = "Üdvözöljük";
|
||||
1
Linphone/Localizable/mk.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
363
Linphone/Localizable/nl.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
"dialog_yes" = "Ja";
|
||||
"Error" = "Fout";
|
||||
"menu_copy_chat_message" = "Kopiëren";
|
||||
"account_settings_audio_video_conference_factory_uri_title" = "URI van de Audio/video conference factory";
|
||||
"account_settings_avpf_title" = "AVPF";
|
||||
"account_settings_bundle_mode_title" = "Bundelmodus";
|
||||
"account_settings_ccmp_server_url_title" = "URL van de CCMP-server";
|
||||
"account_settings_conference_factory_uri_title" = "Conference factory URI";
|
||||
"account_settings_cpim_in_basic_conversations_title" = "Gebruik CPIM in 'basis' gesprekken";
|
||||
"account_settings_dialog_invalid_password_title" = "Authenticatie vereist";
|
||||
"account_settings_dialog_invalid_password_hint" = "Wachtwoord";
|
||||
"account_settings_enable_ice_title" = "Schakel ICE in";
|
||||
"account_settings_enable_turn_title" = "Schakel TURN in";
|
||||
"account_settings_expire_title" = "Vervalt (in seconden)";
|
||||
"account_settings_im_encryption_mandatory_title" = "IM-versleuteling verplicht";
|
||||
"account_settings_lime_server_url_title" = "URL van de E2E-versleuteling sleutel server";
|
||||
"account_settings_mwi_uri_title" = "MWI-server URI (Message Waiting Indicator)";
|
||||
"account_settings_nat_policy_title" = "NAT-beleid instellingen";
|
||||
"account_settings_outbound_proxy_title" = "Uitgaande proxy";
|
||||
"account_settings_push_notification_not_available_title" = "Pushmeldingen zijn niet beschikbaar!";
|
||||
"account_settings_push_notification_title" = "Sta pushmeldingen toe";
|
||||
"account_settings_sip_proxy_url_title" = "URL van de SIP-proxyserver";
|
||||
"account_settings_stun_server_url_title" = "URL van de STUN/TURN-server";
|
||||
"account_settings_title" = "Accountinstellingen";
|
||||
"account_settings_turn_password_title" = "TURN-wachtwoord";
|
||||
"account_settings_turn_username_title" = "TURN-gebruikersnaam";
|
||||
"account_settings_update_password_title" = "Wachtwoord bijwerken";
|
||||
"account_settings_voicemail_uri_title" = "Voicemail-URI";
|
||||
"assistant_account_create" = "Maken";
|
||||
"assistant_account_creation_wrong_phone_number" = "Verkeerd nummer?";
|
||||
"assistant_account_login" = "Login";
|
||||
"assistant_account_login_forbidden_error" = "Verkeerde gebruikersnaam of wachtwoord";
|
||||
"assistant_account_register" = "Registreren";
|
||||
"assistant_account_register_push_notification_not_received_error" = "Pushmelding met autorisatietoken niet binnen 5 seconden ontvangen, probeer het later opnieuw";
|
||||
"assistant_account_register_unexpected_error" = "Er is een onverwachte fout opgetreden, probeer het later opnieuw";
|
||||
"assistant_already_have_an_account" = "Heeft u al een account?";
|
||||
"assistant_create_account_using_email_on_our_web_platform" = "Maak een account aan met uw e-mailadres op:";
|
||||
"assistant_dialog_confirm_phone_number_title" = "Bevestig telefoonnummer";
|
||||
"assistant_dialog_general_terms_and_privacy_policy_title" = "Algemene voorwaarden & privacybeleid";
|
||||
"assistant_dialog_general_terms_label" = "algemene voorwaarden";
|
||||
"assistant_dialog_privacy_policy_label" = "privacy beleid";
|
||||
"assistant_login_third_party_sip_account" = "Gebruik een SIP-account van derden";
|
||||
"assistant_no_account_yet" = "Nog geen account?";
|
||||
"assistant_permissions_grant_all_of_them" = "OK";
|
||||
"assistant_permissions_skip_permissions" = "Doe het later";
|
||||
"assistant_permissions_title" = "Verleen machtigingen";
|
||||
"assistant_qr_code_invalid_toast" = "Ongeldige QR-code!";
|
||||
"assistant_scan_qr_code" = "Scan de QR-code";
|
||||
"assistant_sip_account_transport_protocol" = "Vervoer";
|
||||
"assistant_third_party_sip_account_warning_ok" = "Ik begrijp";
|
||||
"authentication_id" = "Authenticatie-ID (indien anders)";
|
||||
"bottom_navigation_calls_label" = "Oproepen";
|
||||
"bottom_navigation_contacts_label" = "Contacten";
|
||||
"bottom_navigation_conversations_label" = "Gesprekken";
|
||||
"bottom_navigation_meetings_label" = "Vergaderingen";
|
||||
"call_action_blind_transfer" = "Doorverbinden";
|
||||
"call_action_change_layout" = "Indeling";
|
||||
"call_action_go_to_calls_list" = "Oproeplijst";
|
||||
"call_action_hang_up" = "Oproep beëindigen";
|
||||
"call_action_pause_call" = "Pauzeren";
|
||||
"call_action_record_call" = "Opnemen";
|
||||
"call_action_resume_call" = "Hervatten";
|
||||
"call_action_show_dialer" = "Toetsenbord";
|
||||
"call_action_show_messages" = "Berichten";
|
||||
"call_action_start_new_call" = "Nieuwe oproep";
|
||||
"call_audio_device_type_earpiece" = "Oortelefoon";
|
||||
"call_audio_device_type_headphones" = "Koptelefoon";
|
||||
"call_audio_device_type_speaker" = "Spreker";
|
||||
"call_audio_incoming" = "Inkomende oproep";
|
||||
"call_dialog_zrtp_security_alert_message" = "De vertrouwelijkheid van deze oproep kan in gevaar zijn!";
|
||||
"call_dialog_zrtp_security_alert_title" = "Beveiligingswaarschuwing";
|
||||
"call_dialog_zrtp_security_alert_try_again" = "Probeer het opnieuw";
|
||||
"call_dialog_zrtp_validate_trust_letters_do_not_match" = "Niets komt overeen";
|
||||
"call_dialog_zrtp_validate_trust_local_code_label" = "Uw code:";
|
||||
"call_dialog_zrtp_validate_trust_remote_code_label" = "Code van gesprekspartner:";
|
||||
"call_dialog_zrtp_validate_trust_title" = "Valideer het apparaat";
|
||||
"call_do_zrtp_sas_validation_again" = "Valideer ZRTP SAS opnieuw";
|
||||
"call_history_deleted_toast" = "Gespreksgeschiedenis is verwijderd";
|
||||
"call_not_encrypted" = "Oproep is niet versleuteld";
|
||||
"call_outgoing" = "Uitgaande oproep";
|
||||
"call_srtp_point_to_point_encrypted" = "Punt-naar-punt versleuteld door SRTP";
|
||||
"call_state_connected" = "Actief";
|
||||
"call_state_paused" = "Gepauzeerd";
|
||||
"call_state_paused_by_remote" = "Gepauzeerd door externe partij";
|
||||
"call_state_resuming" = "Hervatten…";
|
||||
"call_stats_audio_title" = "Audio";
|
||||
"call_stats_media_encryption_title" = "Media-versleuteling";
|
||||
"call_stats_video_title" = "Video";
|
||||
"call_transfer_failed_toast" = "Doorverbinden van oproep mislukt!";
|
||||
"call_transfer_in_progress_toast" = "Oproep wordt doorverbonden";
|
||||
"call_transfer_successful_toast" = "Oproep is succesvol doorverbonden";
|
||||
"call_waiting_for_encryption_info" = "Wachten op versleuteling…";
|
||||
"call_zrtp_end_to_end_encrypted" = "Eind-tot-eind versleuteld door ZRTP";
|
||||
"call_zrtp_sas_validation_required" = "Validatie vereist";
|
||||
"calls_list_dialog_merge_into_conference_label" = "Groepsgesprek aanmaken";
|
||||
"calls_list_dialog_merge_into_conference_title" = "Alle oproepen samenvoegen in een groepsgesprek ?";
|
||||
"calls_list_title" = "Oproeplijst";
|
||||
"conference_action_screen_sharing" = "Scherm delen";
|
||||
"conference_action_show_participants" = "Deelnemers";
|
||||
"conference_call_empty" = "Wachten op andere deelnemers…";
|
||||
"conference_failed_to_create_group_call_toast" = "Groepsoproep aanmaken mislukt!";
|
||||
"conference_layout_active_speaker" = "Spreker";
|
||||
"conference_layout_audio_only" = "Alleen audio";
|
||||
"conference_layout_grid" = "Mozaïek";
|
||||
"conference_participant_joining_text" = "Deelnemen…";
|
||||
"conference_participant_paused_text" = "Gepauzeerd";
|
||||
"conference_share_link_title" = "Uitnodiging delen";
|
||||
"connection_error_for_non_default_account" = "Account(s) verbindingsfout";
|
||||
"contact_call_action" = "Telefoongesprek";
|
||||
"contact_details_actions_title" = "Andere acties";
|
||||
"contact_details_add_to_favourites" = "Toevoegen aan favorieten";
|
||||
"contact_details_delete" = "Wissen";
|
||||
"contact_details_edit" = "Bewerken";
|
||||
"contact_details_remove_from_favourites" = "Verwijderen uit favorieten";
|
||||
"contact_details_share" = "Delen";
|
||||
"contact_dialog_delete_message" = "Dit contact wordt definitief verwijderd.";
|
||||
"contact_dialog_pick_phone_number_or_sip_address_title" = "Kies een nummer of een SIP-adres";
|
||||
"contact_edit_title" = "Bewerk contact";
|
||||
"contact_editor_company" = "Bedrijf";
|
||||
"contact_editor_dialog_abort_confirmation_message" = "Alle wijzigingen gaan verloren";
|
||||
"contact_editor_dialog_abort_confirmation_title" = "Wijzigingen niet opslaan?";
|
||||
"contact_editor_first_name" = "Voornaam";
|
||||
"contact_editor_job_title" = "Functietitel";
|
||||
"contact_editor_last_name" = "Achternaam";
|
||||
"contact_message_action" = "Bericht";
|
||||
"contact_new_title" = "Nieuw contact";
|
||||
"contacts_list_all_contacts_title" = "Alle contacten";
|
||||
"contacts_list_empty" = "Geen contact op dit moment…";
|
||||
"contacts_list_favourites_title" = "Favorieten";
|
||||
"contacts_list_filter_popup_see_all" = "Zie alles";
|
||||
"conversation_action_call" = "Telefoongesprek";
|
||||
"conversation_action_configure_ephemeral_messages" = "Configureer kortstondige berichten";
|
||||
"conversation_action_delete" = "Gesprek verwijderen";
|
||||
"conversation_action_leave_group" = "Groep verlaten";
|
||||
"conversation_action_mark_as_read" = "Markeer als gelezen";
|
||||
"conversation_action_mute" = "Dempen";
|
||||
"conversation_action_unmute" = "Dempen uitschakelen";
|
||||
"conversation_add_participants_title" = "Deelnemers toevoegen";
|
||||
"conversation_dialog_edit_subject" = "Gespreksonderwerp bewerken";
|
||||
"conversation_dialog_set_subject" = "Gespreksonderwerp instellen";
|
||||
"conversation_dialog_subject_hint" = "Gespreksonderwerp";
|
||||
"conversation_end_to_end_encrypted_event_title" = "Eind-tot-eind versleuteld gesprek";
|
||||
"conversation_end_to_end_encrypted_event_subtitle" = "Berichten in dit gesprek zijn eind-tot-eind versleuteld. Alleen je gesprekspartner kan ze decoderen.";
|
||||
"conversation_ephemeral_messages_duration_disabled" = "Uitgeschakeld";
|
||||
"conversation_ephemeral_messages_duration_one_day" = "1 dag";
|
||||
"conversation_ephemeral_messages_duration_one_hour" = "1 uur";
|
||||
"conversation_ephemeral_messages_duration_one_minute" = "1 minuut";
|
||||
"conversation_ephemeral_messages_duration_one_week" = "1 week";
|
||||
"conversation_ephemeral_messages_duration_three_days" = "3 dagen";
|
||||
"conversation_ephemeral_messages_subtitle" = "Nieuwe berichten worden automatisch verwijderd zodra ze door iedereen zijn gelezen.\nKies een duur:";
|
||||
"conversation_ephemeral_messages_title" = "Kortstondige berichten";
|
||||
"conversation_event_conference_created" = "Je bent bij de groep gekomen";
|
||||
"conversation_event_conference_destroyed" = "Je hebt de groep verlaten";
|
||||
"conversation_event_ephemeral_messages_disabled" = "Tijdelijke berichten zijn uitgeschakeld";
|
||||
"conversation_event_ephemeral_messages_enabled" = "Tijdelijke berichten zijn ingeschakeld";
|
||||
"conversation_failed_to_create_toast" = "Gesprek aanmaken mislukt!";
|
||||
"conversation_forward_message_title" = "Bericht doorsturen naar…";
|
||||
"conversation_info_add_participants_label" = "Deelnemers toevoegen";
|
||||
"conversation_info_admin_menu_remove_participant" = "Verwijderen uit de groep";
|
||||
"conversation_info_admin_menu_set_participant_admin" = "Beheerrechten toekennen";
|
||||
"conversation_info_admin_menu_unset_participant_admin" = "Beheerrechten intrekken";
|
||||
"conversation_info_confirm_start_group_call_dialog_message" = "Alle deelnemers ontvangen een oproep.";
|
||||
"conversation_info_confirm_start_group_call_dialog_title" = "Groepsoproep starten?";
|
||||
"conversation_info_delete_history_action" = "Geschiedenis verwijderen";
|
||||
"conversation_info_menu_add_to_contacts" = "Toevoegen aan contacten";
|
||||
"conversation_info_menu_go_to_contact" = "Bekijk contactprofiel";
|
||||
"conversation_info_participant_is_admin_label" = "Beheerder";
|
||||
"conversation_invalid_participant_due_to_security_mode_toast" = "Gesprek kan niet worden aangemaakt met een deelnemer die zich niet op hetzelfde domein bevindt vanwege beveiligingsbeperkingen!";
|
||||
"conversation_menu_configure_ephemeral_messages" = "Kortstondige berichten";
|
||||
"conversation_menu_go_to_info" = "Gesprek informatie";
|
||||
"conversation_message_forward_cancelled_toast" = "Doorsturen van bericht is geannuleerd";
|
||||
"conversation_message_forwarded_toast" = "Bericht is doorgestuurd";
|
||||
"conversation_message_meeting_updated_label" = "Vergadering is bijgewerkt";
|
||||
"conversation_text_field_hint" = "Zeg iets…";
|
||||
"conversations_list_empty" = "Geen gesprek op dit moment…";
|
||||
"conversation_take_picture_label" = "Foto maken";
|
||||
"conversation_pick_file_from_gallery_label" = "Galerij openen";
|
||||
"conversation_pick_any_file_label" = "Bestand kiezen";
|
||||
"conversation_file_cant_be_opened_error_toast" = "Bestand kan niet worden geopend!";
|
||||
"default_account_disabled" = "Geselecteerd account is momenteel uitgeschakeld";
|
||||
"dialog_accept" = "Accepteren";
|
||||
"dialog_call" = "Telefoongesprek";
|
||||
"dialog_cancel" = "Annuleren";
|
||||
"dialog_continue" = "Doorgaan";
|
||||
"dialog_deny" = "Ontkennen";
|
||||
"dialog_install" = "Installeren";
|
||||
"dialog_no" = "Nee";
|
||||
"dialog_ok" = "OK";
|
||||
"drawer_menu_account_connection_status_cleared" = "Uitgeschakeld";
|
||||
"drawer_menu_account_connection_status_connected" = "Verbonden";
|
||||
"drawer_menu_account_connection_status_failed" = "Fout";
|
||||
"drawer_menu_account_connection_status_progress" = "Verbinden…";
|
||||
"drawer_menu_add_account" = "Voeg een account toe";
|
||||
"drawer_menu_manage_account" = "Beheer het profiel";
|
||||
"drawer_menu_no_account_configured_yet" = "Nog geen account geconfigureerd";
|
||||
"generic_address_picker_suggestions_list_title" = "Suggesties";
|
||||
"help_about_advanced_title" = "Geavanceerd";
|
||||
"help_about_check_for_update" = "Controleer op updates";
|
||||
"help_about_privacy_policy_title" = "Privacy beleid";
|
||||
"help_about_version_title" = "Versie";
|
||||
"help_dialog_update_available_title" = "Update beschikbaar";
|
||||
"help_error_checking_version_toast_message" = "Er is een fout opgetreden tijdens het controleren op updates";
|
||||
"help_quit_title" = "Afsluiten";
|
||||
"help_title" = "Help";
|
||||
"help_troubleshooting_app_version_title" = "App-versie";
|
||||
"help_troubleshooting_clean_logs" = "Logboeken opschonen";
|
||||
"help_troubleshooting_clear_native_friends_in_database" = "Geïmporteerde contacten uit het lokale adresboek verwijderen";
|
||||
"help_troubleshooting_debug_logs_cleaned_toast_message" = "Debuglogs zijn opgeschoond";
|
||||
"help_troubleshooting_debug_logs_upload_error_toast_message" = "Uploaden van debuglogs mislukt";
|
||||
"help_troubleshooting_firebase_project_title" = "Firebase-project-ID";
|
||||
"help_troubleshooting_print_logs_in_logcat" = "Logboeken afdrukken in logcat";
|
||||
"help_troubleshooting_sdk_version_title" = "SDK-versie";
|
||||
"help_troubleshooting_share_logs" = "Logs delen";
|
||||
"help_troubleshooting_share_logs_dialog_title" = "Deel link naar foutopsporingslogs via…";
|
||||
"help_troubleshooting_show_config_file" = "Configuratie weergeven";
|
||||
"help_troubleshooting_title" = "Probleemoplossing";
|
||||
"help_version_up_to_date_toast_message" = "Uw versie is up-to-date";
|
||||
"history_call_start_create_group_call" = "Maak een groepsgesprek";
|
||||
"history_call_start_search_bar_filter_hint" = "Zoek contact of gespreksgeschiedenis";
|
||||
"history_call_start_title" = "Nieuwe oproep";
|
||||
"history_dialog_delete_all_call_logs_message" = "Alle oproepen worden uit de geschiedenis verwijderd";
|
||||
"history_dialog_delete_all_call_logs_title" = "Weet je zeker dat je alle gespreksgeschiedenis wilt verwijderen?";
|
||||
"history_group_call_start_dialog_set_subject" = "Instellen onderwerp groepsoproep";
|
||||
"history_group_call_start_dialog_subject_hint" = "Onderwerp groepsoproep";
|
||||
"history_list_empty_history" = "Geen oproep op dit moment…";
|
||||
"Interoperable mode" = "Interoperabele modus";
|
||||
"list_filter_no_result_found" = "Geen resultaat gevonden…";
|
||||
"manage_account_add_picture" = "Voeg een foto toe";
|
||||
"manage_account_delete" = "Uitloggen";
|
||||
"manage_account_details_title" = "Details";
|
||||
"manage_account_device_last_connection" = "Laatste verbinding:";
|
||||
"manage_account_device_remove" = "Weghalen";
|
||||
"manage_account_devices_title" = "Apparaten";
|
||||
"manage_account_dialog_remove_account_message" = "Als je je account permanent wilt verwijderen, ga naar: https://sip.linphone.org";
|
||||
"manage_account_dialog_remove_account_title" = "Uitloggen uit je account?";
|
||||
"manage_account_edit_picture" = "Bewerk foto";
|
||||
"manage_account_international_prefix" = "Internationale prefix";
|
||||
"manage_account_no_device" = "Geen apparaat gevonden…";
|
||||
"manage_account_remove_picture" = "Verwijder foto";
|
||||
"manage_account_settings" = "Accountinstellingen";
|
||||
"manage_account_status_cleared_summary" = "Account is uitgeschakeld, je ontvangt geen oproepen of berichten.";
|
||||
"manage_account_status_connected_summary" = "Dit account is online, iedereen kan je bellen.";
|
||||
"manage_account_status_failed_summary" = "Verbinding met account mislukt, controleer je instellingen.";
|
||||
"manage_account_status_progress_summary" = "Account is verbinding aan het maken met de server, even geduld aub…";
|
||||
"manage_account_title" = "Account beheren";
|
||||
"meeting_failed_to_schedule_toast" = "Vergadering inplannen mislukt!";
|
||||
"meeting_failed_to_send_invites_toast" = "Het verzenden van alle uitnodigingen voor de vergadering is mislukt!";
|
||||
"meeting_failed_to_send_part_of_invites_toast" = "Het verzenden van uitnodigingen naar sommige deelnemers van de vergadering is mislukt!";
|
||||
"meeting_info_cancelled_toast" = "Vergadering is geannuleerd";
|
||||
"meeting_info_created_toast" = "Vergadering is aangemaakt";
|
||||
"meeting_info_delete" = "Vergadering verwijderen";
|
||||
"meeting_info_deleted_toast" = "Vergadering is verwijderd";
|
||||
"meeting_info_export_as_calendar_event" = "Agenda-item aanmaken";
|
||||
"meeting_info_join_title" = "Sluit nu aan in de vergadering";
|
||||
"meeting_info_organizer_label" = "Organisator";
|
||||
"meeting_info_updated_toast" = "Vergadering is bijgewerkt";
|
||||
"meeting_schedule_add_participants_title" = "Deelnemers toevoegen";
|
||||
"meeting_schedule_cancel_dialog_message" = "Wil je de vergadering annuleren en een melding naar alle deelnemers sturen?";
|
||||
"meeting_schedule_cancel_dialog_title" = "Vergadering annuleren?";
|
||||
"meeting_schedule_description_hint" = "Beschrijving toevoegen";
|
||||
"meeting_schedule_description_title" = "Beschrijving";
|
||||
"meeting_schedule_edit_title" = "Vergadering bewerken";
|
||||
"meeting_schedule_meeting_label" = "Vergadering";
|
||||
"meeting_schedule_pick_end_time_title" = "Kies de eindtijd";
|
||||
"meeting_schedule_pick_start_date_title" = "Kies de startdatum";
|
||||
"meeting_schedule_pick_start_time_title" = "Kies de starttijd";
|
||||
"meeting_schedule_send_invitations_title" = "Stuur uitnodiging naar deelnemers";
|
||||
"meeting_schedule_subject_hint" = "Titel toevoegen…";
|
||||
"meeting_schedule_timezone_title" = "Tijdzone";
|
||||
"meeting_schedule_title" = "Nieuwe vergadering";
|
||||
"meeting_waiting_room_cancel" = "Annuleren";
|
||||
"meeting_waiting_room_join" = "Deelnemen";
|
||||
"meeting_waiting_room_joining_title" = "Verbinding wordt tot stand gebracht";
|
||||
"meetings_list_no_meeting_for_today" = "Er is geen vergadering gepland voor vandaag";
|
||||
"menu_add_address_to_contacts" = "Toevoegen aan contacten";
|
||||
"menu_copy_phone_number" = "Telefoonnummer kopiëren";
|
||||
"menu_delete_history" = "Geschiedenis verwijderen";
|
||||
"menu_delete_selected_item" = "Wissen";
|
||||
"menu_forward_chat_message" = "Doorsturen";
|
||||
"menu_invite" = "Uitnodigen";
|
||||
"menu_reply_to_chat_message" = "Antwoord";
|
||||
"menu_resend_chat_message" = "Opnieuw verzenden";
|
||||
"menu_see_existing_contact" = "Bekijk contact";
|
||||
"menu_show_imdn" = "Bezorgstatus";
|
||||
"message_delivery_info_error_title" = "Fout";
|
||||
"message_forwarded_label" = "Doorgestuurd";
|
||||
"message_reaction_click_to_remove_label" = "Klik om te verwijderen";
|
||||
"network_not_reachable" = "Je bent niet verbonden met internet";
|
||||
"new_conversation_create_group" = "Maak een groepsgesprek";
|
||||
"new_conversation_search_bar_filter_hint" = "Zoek contact";
|
||||
"new_conversation_title" = "Nieuw gesprek";
|
||||
"next" = "Volgende";
|
||||
"notification_missed_call_title" = "Gemiste oproep";
|
||||
"operation_in_progress_overlay" = "Bezig met bewerking, even geduld alstublieft";
|
||||
"or" = "of";
|
||||
"password" = "Wachtwoord";
|
||||
"phone_number" = "Telefoonnummer";
|
||||
"recordings_title" = "Opnames";
|
||||
"settings_advanced_accept_early_media_title" = "Accepteer vroege media";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Sta uitgaande vroege media toe";
|
||||
"settings_advanced_audio_codecs_title" = "Audiocodecs";
|
||||
"settings_advanced_audio_devices_title" = "Audioapparaten";
|
||||
"settings_advanced_device_id" = "Apparaat-ID";
|
||||
"settings_advanced_device_id_hint" = "Alleen alfanumerieke tekens";
|
||||
"settings_advanced_download_apply_remote_provisioning" = "Downloaden en toepassen";
|
||||
"settings_advanced_input_audio_device_title" = "Standaard invoer audioapparaat";
|
||||
"settings_advanced_media_encryption_mandatory_title" = "Media-versleuteling verplicht";
|
||||
"settings_advanced_output_audio_device_title" = "Standaard uitvoer audioapparaat";
|
||||
"settings_advanced_remote_provisioning_url" = "URL voor externe provisioning";
|
||||
"settings_advanced_title" = "Geavanceerde instellingen";
|
||||
"settings_advanced_upload_server_url" = "URL van de bestanddelingsserver";
|
||||
"settings_advanced_video_codecs_title" = "Video-codecs";
|
||||
"settings_calls_adaptive_rate_control_title" = "Adaptieve bitrate-regeling";
|
||||
"settings_calls_auto_record_title" = "Oproepen automatisch opnemen";
|
||||
"settings_calls_calibrate_echo_canceller_done_no_echo" = "Geen echo";
|
||||
"settings_calls_calibrate_echo_canceller_failed" = "Mislukt";
|
||||
"settings_calls_calibrate_echo_canceller_in_progress" = "In behandeling";
|
||||
"settings_calls_calibrate_echo_canceller_title" = "Echo-onderdrukking kalibreren";
|
||||
"settings_calls_change_ringtone_title" = "Beltoon wijzigen";
|
||||
"settings_calls_echo_canceller_subtitle" = "Voorkomt echo aan de andere kant van de lijn als er geen hardwarematige echo-onderdrukking beschikbaar is";
|
||||
"settings_calls_echo_canceller_title" = "Softwarematige echo-onderdrukking gebruiken";
|
||||
"settings_calls_enable_fec_title" = "Video-FEC inschakelen";
|
||||
"settings_calls_enable_video_title" = "Video inschakelen";
|
||||
"settings_calls_title" = "Oproepen";
|
||||
"settings_calls_vibrate_while_ringing_title" = "Trillen tijdens inkomend gesprek";
|
||||
"settings_contacts_add_carddav_server_title" = "Voeg CardDAV-adresboek toe";
|
||||
"settings_contacts_add_ldap_server_title" = "LDAP-server toevoegen";
|
||||
"settings_contacts_carddav_name_title" = "Weergavenaam";
|
||||
"settings_contacts_carddav_password_title" = "Wachtwoord";
|
||||
"settings_contacts_carddav_server_url_title" = "Server-URL";
|
||||
"settings_contacts_carddav_sync_error_toast" = "Synchronisatiefout!";
|
||||
"settings_contacts_carddav_username_title" = "Gebruikersnaam";
|
||||
"settings_contacts_edit_carddav_server_title" = "Bewerk CardDAV-adresboek";
|
||||
"settings_contacts_edit_ldap_server_title" = "Bewerk LDAP-server";
|
||||
"settings_contacts_ldap_bind_dn_title" = "Bind-DN";
|
||||
"settings_contacts_ldap_password_title" = "Wachtwoord";
|
||||
"settings_contacts_ldap_server_url_title" = "Server-URL (mag niet leeg zijn)";
|
||||
"settings_contacts_ldap_use_tls_title" = "TLS gebruiken";
|
||||
"settings_contacts_title" = "Contacten";
|
||||
"settings_conversations_auto_download_title" = "Bestanden automatisch downloaden";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Gesprek als gelezen markeren bij het sluiten van berichtmelding";
|
||||
"settings_conversations_title" = "Gesprekken";
|
||||
"settings_meetings_default_layout_title" = "Standaardindeling";
|
||||
"settings_meetings_layout_active_speaker_label" = "Actieve spreker";
|
||||
"settings_meetings_layout_mosaic_label" = "Mozaïek";
|
||||
"settings_meetings_title" = "Vergaderingen";
|
||||
"settings_network_allow_ipv6" = "Sta IPv6 toe";
|
||||
"settings_network_title" = "Netwerk";
|
||||
"settings_network_use_wifi_only" = "Gebruik alleen Wi-Fi-netwerken";
|
||||
"settings_security_enable_vfs_subtitle" = "Waarschuwing: eenmaal ingeschakeld kan dit niet meer worden uitgeschakeld!";
|
||||
"settings_security_enable_vfs_title" = "Versleutel alles";
|
||||
"settings_security_prevent_screenshots_title" = "Voorkom dat de interface wordt opgenomen";
|
||||
"settings_security_title" = "Beveiliging";
|
||||
"settings_title" = "Instellingen";
|
||||
"sip_address_copied_to_clipboard_toast" = "SIP-adres gekopieerd naar klembord";
|
||||
"sip_address_display_name" = "Weergavenaam";
|
||||
"sip_address_domain" = "Domein";
|
||||
"start" = "Begin";
|
||||
"uri_handler_config_success_toast" = "Configuratie succesvol toegepast";
|
||||
"username" = "Gebruikersnaam";
|
||||
"welcome_page_2_title" = "Beveiligd";
|
||||
"welcome_page_3_title" = "Open-source";
|
||||
"welcome_page_title" = "Welkom";
|
||||
525
Linphone/Localizable/pt-BR.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
"assistant_account_create" = "Criar";
|
||||
"assistant_account_creation_wrong_phone_number" = "Número errado?";
|
||||
"assistant_account_login" = "Login";
|
||||
"assistant_account_login_forbidden_error" = "Nome de usuário ou senha incorretos";
|
||||
"assistant_account_register" = "Registrar";
|
||||
"assistant_account_register_push_notification_not_received_error" = "Notificação push com token de autenticação não recebida em 5 segundos, por favor, tente novamente mais tarde";
|
||||
"assistant_account_register_unexpected_error" = "Ocorreu um erro inesperado, por favor, tente novamente mais tarde";
|
||||
"conversation_ephemeral_messages_duration_disabled" = "Desativado";
|
||||
"conversation_invalid_participant_due_to_security_mode_toast" = "Não é possível criar conversa com um participante que não está no mesmo domínio devido a restrições de segurança!";
|
||||
"conversation_menu_configure_ephemeral_messages" = "Mensagens efêmeras";
|
||||
"conversation_menu_go_to_info" = "Informações da conversa";
|
||||
"conversation_message_forward_cancelled_toast" = "Encaminhamento de mensagem cancelado";
|
||||
"conversations_list_empty" = "Nenhuma conversa no momento…";
|
||||
"default_account_disabled" = "A conta selecionada está atualmente desativada";
|
||||
"dialog_accept" = "Aceitar";
|
||||
"dialog_call" = "Chamar";
|
||||
"dialog_cancel" = "Cancelar";
|
||||
"dialog_continue" = "Continuar";
|
||||
"dialog_deny" = "Recusar";
|
||||
"dialog_install" = "Instalar";
|
||||
"Error" = "Erro";
|
||||
"help_troubleshooting_clean_logs" = "Limpar logs";
|
||||
"help_troubleshooting_clear_native_friends_in_database" = "Limpar contatos importados da agenda nativa";
|
||||
"help_troubleshooting_debug_logs_cleaned_toast_message" = "Logs de depuração foram limpos";
|
||||
"help_troubleshooting_debug_logs_upload_error_toast_message" = "Falha ao enviar logs de depuração";
|
||||
"help_troubleshooting_firebase_project_title" = "ID do projeto Firebase";
|
||||
"help_troubleshooting_print_logs_in_logcat" = "Imprimir logs no logcat";
|
||||
"help_troubleshooting_sdk_version_title" = "Versão do SDK";
|
||||
"help_troubleshooting_share_logs" = "Compartilhar logs";
|
||||
"help_troubleshooting_share_logs_dialog_title" = "Compartilhar link de logs de depuração usando…";
|
||||
"help_troubleshooting_show_config_file" = "Mostrar configuração";
|
||||
"help_troubleshooting_title" = "Solução de problemas";
|
||||
"help_version_up_to_date_toast_message" = "Sua versão está atualizada";
|
||||
"history_call_start_create_group_call" = "Criar uma chamada em grupo";
|
||||
"history_call_start_search_bar_filter_hint" = "Pesquisar contato ou histórico de chamadas";
|
||||
"history_call_start_title" = "Nova chamada";
|
||||
"history_dialog_delete_all_call_logs_message" = "Todas as chamadas serão removidas do histórico";
|
||||
"history_dialog_delete_all_call_logs_title" = "Você realmente quer excluir todo o histórico de chamadas?";
|
||||
"history_group_call_start_dialog_set_subject" = "Definir assunto da chamada em grupo";
|
||||
"history_group_call_start_dialog_subject_hint" = "Assunto da chamada em grupo";
|
||||
"history_list_empty_history" = "Nenhuma chamada no momento…";
|
||||
"Interoperable mode" = "Modo interoperável";
|
||||
"list_filter_no_result_found" = "Nenhum resultado encontrado…";
|
||||
"manage_account_no_device" = "Nenhum dispositivo encontrado…";
|
||||
"manage_account_remove_picture" = "Remover foto";
|
||||
"manage_account_settings" = "Configurações da conta";
|
||||
"manage_account_status_cleared_summary" = "A conta foi desativada, você não receberá nenhuma chamada ou mensagem.";
|
||||
"manage_account_status_connected_summary" = "Esta conta está online, todos podem ligar para você.";
|
||||
"manage_account_status_failed_summary" = "Falha na conexão da conta, verifique suas configurações.";
|
||||
"meeting_failed_to_send_invites_toast" = "Falha ao enviar todos os convites para a reunião!";
|
||||
"meeting_failed_to_send_part_of_invites_toast" = "Falha ao enviar convites para alguns participantes da reunião!";
|
||||
"meeting_info_cancelled_toast" = "A reunião foi cancelada";
|
||||
"meeting_info_created_toast" = "A reunião foi criada";
|
||||
"meeting_info_delete" = "Excluir reunião";
|
||||
"meeting_info_organizer_label" = "Organizador";
|
||||
"meeting_info_updated_toast" = "A reunião foi atualizada";
|
||||
"meeting_schedule_add_participants_title" = "Adicionar participantes";
|
||||
"meeting_schedule_cancel_dialog_message" = "Você quer cancelar a reunião e enviar uma notificação para todos os participantes?";
|
||||
"meeting_schedule_cancel_dialog_title" = "Cancelar a reunião?";
|
||||
"meeting_schedule_description_hint" = "Adicionar descrição";
|
||||
"meeting_schedule_description_title" = "Descrição";
|
||||
"meeting_waiting_room_join" = "Entrar";
|
||||
"meeting_waiting_room_joining_title" = "Conexão em andamento";
|
||||
"meetings_list_no_meeting_for_today" = "Nenhuma reunião agendada para hoje";
|
||||
"menu_add_address_to_contacts" = "Adicionar aos contatos";
|
||||
"menu_copy_chat_message" = "Copiar";
|
||||
"menu_copy_phone_number" = "Copiar número de telefone";
|
||||
"menu_delete_history" = "Excluir histórico";
|
||||
"menu_delete_selected_item" = "Excluir";
|
||||
"menu_forward_chat_message" = "Encaminhar";
|
||||
"menu_invite" = "Convidar";
|
||||
"menu_reply_to_chat_message" = "Responder";
|
||||
"password" = "Senha";
|
||||
"settings_advanced_device_id_hint" = "Apenas caracteres alfanuméricos";
|
||||
"settings_advanced_download_apply_remote_provisioning" = "Baixar e aplicar";
|
||||
"settings_advanced_input_audio_device_title" = "Dispositivo de entrada de áudio padrão";
|
||||
"settings_contacts_carddav_name_title" = "Nome de exibição";
|
||||
"settings_contacts_carddav_password_title" = "Senha";
|
||||
"settings_contacts_carddav_server_url_title" = "URL do servidor";
|
||||
"settings_contacts_carddav_sync_error_toast" = "Erro na sincronização!";
|
||||
"settings_contacts_carddav_username_title" = "Nome de usuário";
|
||||
"settings_contacts_edit_carddav_server_title" = "Editar agenda CardDAV";
|
||||
"settings_contacts_edit_ldap_server_title" = "Editar servidor LDAP";
|
||||
"settings_contacts_ldap_bind_dn_title" = "Bind DN";
|
||||
"settings_contacts_ldap_password_title" = "Senha";
|
||||
"settings_meetings_layout_mosaic_label" = "Mosaico";
|
||||
"welcome_page_2_title" = "Seguro";
|
||||
"welcome_page_title" = "Bem vindo";
|
||||
"account_settings_audio_video_conference_factory_uri_title" = "URI da fábrica de conferências de áudio/vídeo";
|
||||
"account_settings_avpf_title" = "AVPF";
|
||||
"account_settings_bundle_mode_title" = "Modo bundle";
|
||||
"account_settings_ccmp_server_url_title" = "URL do servidor CCMP";
|
||||
"account_settings_conference_factory_uri_title" = "URI da fábrica de conferências";
|
||||
"account_settings_cpim_in_basic_conversations_title" = "Usar CPIM em conversas básicas";
|
||||
"account_settings_dialog_invalid_password_title" = "Autenticação necessária";
|
||||
"account_settings_dialog_invalid_password_hint" = "Senha";
|
||||
"account_settings_enable_ice_title" = "Ativar ICE";
|
||||
"account_settings_enable_turn_title" = "Ativar TURN";
|
||||
"account_settings_expire_title" = "Expira (em segundos)";
|
||||
"account_settings_im_encryption_mandatory_title" = "Criptografia de IM obrigatória";
|
||||
"account_settings_lime_server_url_title" = "URL do servidor de chaves de criptografia E2E";
|
||||
"account_settings_mwi_uri_title" = "URI do servidor MWI (Indicador de Mensagem em Espera)";
|
||||
"account_settings_nat_policy_title" = "Configurações da política NAT";
|
||||
"account_settings_outbound_proxy_title" = "Proxy de saída";
|
||||
"account_settings_push_notification_not_available_title" = "As notificações push não estão disponíveis!";
|
||||
"account_settings_push_notification_title" = "Permitir notificações push";
|
||||
"account_settings_sip_proxy_url_title" = "URL do servidor proxy SIP";
|
||||
"account_settings_stun_server_url_title" = "URL do servidor STUN/TURN";
|
||||
"account_settings_title" = "Configurações da conta";
|
||||
"account_settings_turn_password_title" = "Senha TURN";
|
||||
"account_settings_turn_username_title" = "Nome de usuário TURN";
|
||||
"account_settings_update_password_title" = "Atualizar senha";
|
||||
"account_settings_voicemail_uri_title" = "URI do correio de voz";
|
||||
"assistant_already_have_an_account" = "Já tem uma conta?";
|
||||
"assistant_create_account_using_email_on_our_web_platform" = "Crie uma conta com seu e-mail em:";
|
||||
"assistant_dialog_confirm_phone_number_title" = "Confirmar número de telefone";
|
||||
"assistant_dialog_general_terms_and_privacy_policy_title" = "Termos gerais e política de privacidade";
|
||||
"assistant_dialog_general_terms_label" = "termos gerais";
|
||||
"assistant_dialog_privacy_policy_label" = "política de privacidade";
|
||||
"assistant_login_third_party_sip_account" = "Usar uma conta SIP de terceiros";
|
||||
"assistant_no_account_yet" = "Ainda não tem uma conta?";
|
||||
"assistant_permissions_grant_all_of_them" = "OK";
|
||||
"assistant_permissions_skip_permissions" = "Fazer isso mais tarde";
|
||||
"assistant_permissions_title" = "Conceder permissões";
|
||||
"assistant_qr_code_invalid_toast" = "QR code inválido!";
|
||||
"assistant_scan_qr_code" = "Escanear QR code";
|
||||
"assistant_sip_account_transport_protocol" = "Transporte";
|
||||
"assistant_third_party_sip_account_warning_ok" = "Eu entendo";
|
||||
"authentication_id" = "ID de autenticação (se diferente)";
|
||||
"bottom_navigation_calls_label" = "Chamadas";
|
||||
"bottom_navigation_contacts_label" = "Contatos";
|
||||
"bottom_navigation_conversations_label" = "Conversas";
|
||||
"bottom_navigation_meetings_label" = "Reuniões";
|
||||
"call_action_blind_transfer" = "Transferir";
|
||||
"call_action_change_layout" = "Layout";
|
||||
"call_action_go_to_calls_list" = "Lista de chamadas";
|
||||
"call_action_hang_up" = "Desligar";
|
||||
"call_action_pause_call" = "Pausar";
|
||||
"call_action_record_call" = "Gravar";
|
||||
"call_action_resume_call" = "Retomar";
|
||||
"call_action_show_dialer" = "Teclado";
|
||||
"call_action_show_messages" = "Mensagens";
|
||||
"call_action_start_new_call" = "Nova chamada";
|
||||
"call_audio_device_type_earpiece" = "Fone de ouvido (auricular)";
|
||||
"call_audio_device_type_headphones" = "Fones de ouvido";
|
||||
"call_audio_device_type_speaker" = "Alto-falante";
|
||||
"call_audio_incoming" = "Chamada recebida";
|
||||
"call_dialog_zrtp_security_alert_message" = "A confidencialidade desta chamada pode estar comprometida!";
|
||||
"call_dialog_zrtp_security_alert_title" = "Alerta de segurança";
|
||||
"call_dialog_zrtp_security_alert_try_again" = "Tente novamente";
|
||||
"call_dialog_zrtp_validate_trust_letters_do_not_match" = "Nada corresponde";
|
||||
"call_dialog_zrtp_validate_trust_local_code_label" = "Seu código:";
|
||||
"call_dialog_zrtp_validate_trust_remote_code_label" = "Código do correspondente:";
|
||||
"call_dialog_zrtp_validate_trust_title" = "Valide o dispositivo";
|
||||
"call_do_zrtp_sas_validation_again" = "Validar ZRTP SAS novamente";
|
||||
"call_history_deleted_toast" = "O histórico foi excluído";
|
||||
"call_not_encrypted" = "A chamada não está criptografada";
|
||||
"call_outgoing" = "Chamada efetuada";
|
||||
"call_srtp_point_to_point_encrypted" = "Criptografada ponto a ponto por SRTP";
|
||||
"call_state_connected" = "Ativa";
|
||||
"call_state_paused" = "Pausada";
|
||||
"call_state_paused_by_remote" = "Pausada pelo remoto";
|
||||
"call_state_resuming" = "Retomando…";
|
||||
"call_stats_audio_title" = "Áudio";
|
||||
"call_stats_media_encryption_title" = "Criptografia de mídia";
|
||||
"call_stats_video_title" = "Vídeo";
|
||||
"call_transfer_failed_toast" = "Falha na transferência da chamada!";
|
||||
"call_transfer_in_progress_toast" = "A chamada está sendo transferida";
|
||||
"call_transfer_successful_toast" = "A chamada foi transferida com sucesso";
|
||||
"call_waiting_for_encryption_info" = "Aguardando criptografia…";
|
||||
"call_zrtp_end_to_end_encrypted" = "Criptografada de ponta a ponta por ZRTP";
|
||||
"call_zrtp_sas_validation_required" = "Validação necessária";
|
||||
"calls_list_dialog_merge_into_conference_label" = "Criar conferência";
|
||||
"calls_list_dialog_merge_into_conference_title" = "Mesclar todas as chamadas em conferência?";
|
||||
"conference_layout_audio_only" = "Somente áudio";
|
||||
"conference_layout_grid" = "Mosaico";
|
||||
"conference_participant_joining_text" = "Entrando…";
|
||||
"conference_participant_paused_text" = "Pausado";
|
||||
"calls_list_title" = "Lista de chamadas";
|
||||
"conference_action_screen_sharing" = "Compartilhar tela";
|
||||
"conference_action_show_participants" = "Participantes";
|
||||
"conference_call_empty" = "Aguardando outros participantes…";
|
||||
"conference_failed_to_create_group_call_toast" = "Falha ao criar chamada em grupo!";
|
||||
"conference_layout_active_speaker" = "Alto-falante";
|
||||
"conference_share_link_title" = "Compartilhar convite";
|
||||
"connection_error_for_non_default_account" = "Erro de conexão da(s) conta(s)";
|
||||
"contact_call_action" = "Chamar";
|
||||
"contact_details_actions_title" = "Outras ações";
|
||||
"contact_details_add_to_favourites" = "Adicionar aos favoritos";
|
||||
"contact_details_delete" = "Excluir";
|
||||
"contact_details_edit" = "Editar";
|
||||
"contact_details_remove_from_favourites" = "Remover dos favoritos";
|
||||
"contact_details_share" = "Compartilhar";
|
||||
"contact_dialog_delete_message" = "Este contato será removido definitivamente.";
|
||||
"contact_dialog_pick_phone_number_or_sip_address_title" = "Escolha um número ou um endereço SIP";
|
||||
"contact_edit_title" = "Editar contato";
|
||||
"contact_editor_company" = "Empresa";
|
||||
"contact_editor_dialog_abort_confirmation_message" = "Todas as alterações serão perdidas";
|
||||
"contact_editor_dialog_abort_confirmation_title" = "Não salvar alterações?";
|
||||
"contact_editor_first_name" = "Nome";
|
||||
"contact_editor_job_title" = "Cargo";
|
||||
"contact_editor_last_name" = "Sobrenome";
|
||||
"contact_message_action" = "Mensagem";
|
||||
"contact_new_title" = "Novo contato";
|
||||
"contacts_list_all_contacts_title" = "Todos os contatos";
|
||||
"contacts_list_empty" = "Nenhum contato no momento…";
|
||||
"contacts_list_favourites_title" = "Favoritos";
|
||||
"contacts_list_filter_popup_see_all" = "Ver todos";
|
||||
"conversation_action_call" = "Chamar";
|
||||
"conversation_action_configure_ephemeral_messages" = "Configurar mensagens efêmeras";
|
||||
"conversation_action_delete" = "Excluir conversa";
|
||||
"conversation_action_leave_group" = "Sair do grupo";
|
||||
"conversation_action_mark_as_read" = "Marcar como lida";
|
||||
"conversation_action_mute" = "Silenciar";
|
||||
"conversation_action_unmute" = "Reativar som";
|
||||
"conversation_add_participants_title" = "Adicionar participantes";
|
||||
"conversation_dialog_edit_subject" = "Editar assunto da conversa";
|
||||
"conversation_dialog_set_subject" = "Definir assunto da conversa";
|
||||
"conversation_dialog_subject_hint" = "Assunto da conversa";
|
||||
"conversation_end_to_end_encrypted_event_title" = "Conversa com criptografia de ponta a ponta";
|
||||
"conversation_end_to_end_encrypted_event_subtitle" = "As mensagens nesta conversa são criptografadas de ponta a ponta. Apenas seu correspondente pode descriptografá-las.";
|
||||
"conversation_ephemeral_messages_duration_one_day" = "1 dia";
|
||||
"conversation_ephemeral_messages_duration_one_hour" = "1 hora";
|
||||
"conversation_ephemeral_messages_duration_one_minute" = "1 minuto";
|
||||
"conversation_ephemeral_messages_duration_one_week" = "1 semana";
|
||||
"conversation_ephemeral_messages_duration_three_days" = "3 dias";
|
||||
"conversation_ephemeral_messages_subtitle" = "Novas mensagens serão excluídas automaticamente assim que lidas por todos.\nEscolha uma duração:";
|
||||
"conversation_ephemeral_messages_title" = "Mensagens efêmeras";
|
||||
"conversation_event_conference_created" = "Você entrou no grupo";
|
||||
"conversation_event_conference_destroyed" = "Você saiu do grupo";
|
||||
"conversation_event_ephemeral_messages_disabled" = "Mensagens efêmeras foram desativadas";
|
||||
"conversation_event_ephemeral_messages_enabled" = "Mensagens efêmeras foram ativadas";
|
||||
"conversation_failed_to_create_toast" = "Falha ao criar conversa!";
|
||||
"conversation_forward_message_title" = "Encaminhar mensagem para…";
|
||||
"conversation_info_add_participants_label" = "Adicionar participantes";
|
||||
"conversation_info_admin_menu_remove_participant" = "Remover do grupo";
|
||||
"conversation_info_admin_menu_set_participant_admin" = "Dar direitos de admin";
|
||||
"conversation_info_admin_menu_unset_participant_admin" = "Remover direitos de admin";
|
||||
"conversation_info_confirm_start_group_call_dialog_message" = "Todos os participantes receberão uma chamada.";
|
||||
"conversation_info_confirm_start_group_call_dialog_title" = "Iniciar uma chamada em grupo?";
|
||||
"conversation_info_delete_history_action" = "Excluir histórico";
|
||||
"conversation_info_menu_add_to_contacts" = "Adicionar aos contatos";
|
||||
"conversation_message_forwarded_toast" = "A mensagem foi encaminhada";
|
||||
"conversation_info_menu_go_to_contact" = "Ver perfil do contato";
|
||||
"conversation_info_participant_is_admin_label" = "Admin";
|
||||
"conversation_message_meeting_updated_label" = "A reunião foi atualizada";
|
||||
"conversation_text_field_hint" = "Diga algo…";
|
||||
"conversation_take_picture_label" = "Tirar foto";
|
||||
"conversation_pick_file_from_gallery_label" = "Abrir galeria";
|
||||
"conversation_pick_any_file_label" = "Escolher arquivo";
|
||||
"conversation_file_cant_be_opened_error_toast" = "O arquivo não pode ser aberto!";
|
||||
"dialog_no" = "Não";
|
||||
"dialog_ok" = "OK";
|
||||
"dialog_yes" = "Sim";
|
||||
"drawer_menu_account_connection_status_cleared" = "Desativado";
|
||||
"drawer_menu_account_connection_status_connected" = "Conectado";
|
||||
"drawer_menu_account_connection_status_failed" = "Erro";
|
||||
"drawer_menu_account_connection_status_progress" = "Conectando…";
|
||||
"drawer_menu_add_account" = "Adicionar uma conta";
|
||||
"drawer_menu_manage_account" = "Gerenciar o perfil";
|
||||
"drawer_menu_no_account_configured_yet" = "Nenhuma conta configurada ainda";
|
||||
"generic_address_picker_suggestions_list_title" = "Sugestões";
|
||||
"help_about_advanced_title" = "Avançado";
|
||||
"help_about_check_for_update" = "Verificar atualização";
|
||||
"help_about_privacy_policy_title" = "Política de privacidade";
|
||||
"help_about_user_guide_subtitle" = "Aprenda a dominar todos os recursos do aplicativo, passo a passo.";
|
||||
"help_about_version_title" = "Versão";
|
||||
"help_dialog_update_available_title" = "Atualização disponível";
|
||||
"help_error_checking_version_toast_message" = "Ocorreu um erro ao verificar por atualizações";
|
||||
"help_quit_title" = "Sair do aplicativo";
|
||||
"help_title" = "Ajuda";
|
||||
"help_troubleshooting_app_version_title" = "Versão do aplicativo";
|
||||
"manage_account_add_picture" = "Adicionar uma foto";
|
||||
"manage_account_delete" = "Sair";
|
||||
"manage_account_details_title" = "Detalhes";
|
||||
"manage_account_device_last_connection" = "Última conexão:";
|
||||
"manage_account_device_remove" = "Remover";
|
||||
"manage_account_devices_title" = "Dispositivos";
|
||||
"manage_account_dialog_remove_account_message" = "Se você deseja excluir sua conta permanentemente, acesse: https://sip.linphone.org";
|
||||
"manage_account_dialog_remove_account_title" = "Sair da sua conta?";
|
||||
"manage_account_edit_picture" = "Editar foto";
|
||||
"manage_account_international_prefix" = "Prefixo internacional";
|
||||
"manage_account_status_progress_summary" = "A conta está se conectando ao servidor, por favor, aguarde…";
|
||||
"manage_account_title" = "Gerenciar conta";
|
||||
"meeting_failed_to_schedule_toast" = "Falha ao agendar reunião!";
|
||||
"meeting_info_deleted_toast" = "A reunião foi excluída";
|
||||
"meeting_info_export_as_calendar_event" = "Criar evento na agenda";
|
||||
"meeting_info_join_title" = "Entrar na reunião agora";
|
||||
"meeting_schedule_edit_title" = "Editar reunião";
|
||||
"meeting_schedule_meeting_label" = "Reunião";
|
||||
"meeting_schedule_pick_end_time_title" = "Escolha a hora de término";
|
||||
"meeting_schedule_pick_start_date_title" = "Escolha a data de início";
|
||||
"meeting_schedule_pick_start_time_title" = "Escolha a hora de início";
|
||||
"meeting_schedule_send_invitations_title" = "Enviar convite aos participantes";
|
||||
"meeting_schedule_subject_hint" = "Adicionar título…";
|
||||
"meeting_schedule_timezone_title" = "Fuso horário";
|
||||
"meeting_schedule_title" = "Nova reunião";
|
||||
"meeting_waiting_room_cancel" = "Cancelar";
|
||||
"menu_resend_chat_message" = "Reenviar";
|
||||
"or" = "ou";
|
||||
"phone_number" = "Número de telefone";
|
||||
"menu_see_existing_contact" = "Ver contato";
|
||||
"menu_show_imdn" = "Status de entrega";
|
||||
"message_delivery_info_error_title" = "Erro";
|
||||
"message_forwarded_label" = "Encaminhada";
|
||||
"message_reaction_click_to_remove_label" = "Clique para remover";
|
||||
"network_not_reachable" = "Você não está conectado à internet";
|
||||
"new_conversation_create_group" = "Criar uma conversa em grupo";
|
||||
"new_conversation_search_bar_filter_hint" = "Pesquisar contato";
|
||||
"new_conversation_title" = "Nova conversa";
|
||||
"next" = "Próximo";
|
||||
"notification_missed_call_title" = "Chamada perdida";
|
||||
"operation_in_progress_overlay" = "Operação em andamento, por favor, aguarde";
|
||||
"recordings_title" = "Gravações";
|
||||
"settings_advanced_accept_early_media_title" = "Aceitar mídia antecipada";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Permitir mídia antecipada de saída";
|
||||
"settings_advanced_audio_codecs_title" = "Codecs de áudio";
|
||||
"settings_advanced_audio_devices_title" = "Dispositivos de áudio";
|
||||
"settings_advanced_device_id" = "ID do dispositivo";
|
||||
"settings_advanced_media_encryption_mandatory_title" = "Criptografia de mídia obrigatória";
|
||||
"settings_advanced_output_audio_device_title" = "Dispositivo de saída de áudio padrão";
|
||||
"settings_advanced_remote_provisioning_url" = "URL de provisionamento remoto";
|
||||
"settings_advanced_title" = "Configurações avançadas";
|
||||
"settings_advanced_upload_server_url" = "URL do servidor de compartilhamento de arquivos";
|
||||
"settings_advanced_video_codecs_title" = "Codecs de vídeo";
|
||||
"settings_calls_adaptive_rate_control_title" = "Controle adaptativo de taxa";
|
||||
"settings_calls_auto_record_title" = "Iniciar gravação de chamadas automaticamente";
|
||||
"settings_calls_calibrate_echo_canceller_done_no_echo" = "sem eco";
|
||||
"settings_calls_calibrate_echo_canceller_failed" = "falhou";
|
||||
"settings_calls_calibrate_echo_canceller_in_progress" = "em andamento";
|
||||
"settings_calls_calibrate_echo_canceller_title" = "Calibrar cancelador de eco";
|
||||
"settings_calls_change_ringtone_title" = "Alterar toque";
|
||||
"settings_calls_echo_canceller_subtitle" = "Impede que o eco seja ouvido pela outra parte se não houver um cancelador de eco de hardware disponível";
|
||||
"settings_calls_echo_canceller_title" = "Usar cancelador de eco por software";
|
||||
"settings_calls_enable_fec_title" = "Ativar FEC de vídeo";
|
||||
"settings_calls_enable_video_title" = "Ativar vídeo";
|
||||
"settings_calls_title" = "Chamadas";
|
||||
"settings_calls_vibrate_while_ringing_title" = "Vibrar enquanto a chamada recebida está tocando";
|
||||
"settings_contacts_add_carddav_server_title" = "Adicionar agenda CardDAV";
|
||||
"settings_contacts_add_ldap_server_title" = "Adicionar servidor LDAP";
|
||||
"settings_contacts_ldap_search_base_title" = "Base de busca (não pode ficar em branco)";
|
||||
"settings_contacts_ldap_search_filter_title" = "Filtro";
|
||||
"settings_contacts_ldap_server_url_title" = "URL do servidor (não pode ficar em branco)";
|
||||
"settings_contacts_ldap_use_tls_title" = "Usar TLS";
|
||||
"settings_contacts_title" = "Contatos";
|
||||
"settings_conversations_auto_download_title" = "Baixar arquivos automaticamente";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Marcar conversa como lida ao dispensar notificação de mensagem";
|
||||
"settings_conversations_title" = "Conversas";
|
||||
"settings_meetings_default_layout_title" = "Layout padrão";
|
||||
"settings_meetings_layout_active_speaker_label" = "Orador ativo";
|
||||
"settings_meetings_title" = "Reuniões";
|
||||
"settings_network_allow_ipv6" = "Permitir IPv6";
|
||||
"settings_network_title" = "Rede";
|
||||
"settings_network_use_wifi_only" = "Usar apenas redes Wi-Fi";
|
||||
"settings_security_enable_vfs_subtitle" = "Aviso: uma vez ativado, não pode ser desativado!";
|
||||
"settings_security_enable_vfs_title" = "Criptografar tudo";
|
||||
"settings_security_prevent_screenshots_title" = "Impedir que a interface seja gravada";
|
||||
"settings_security_title" = "Segurança";
|
||||
"settings_title" = "Configurações";
|
||||
"sip_address_copied_to_clipboard_toast" = "Endereço SIP copiado para a área de transferência";
|
||||
"sip_address_display_name" = "Nome de exibição";
|
||||
"sip_address_domain" = "Domínio";
|
||||
"start" = "Início";
|
||||
"uri_handler_config_success_toast" = "Configuração aplicada com sucesso";
|
||||
"username" = "Nome de usuário";
|
||||
"welcome_page_3_title" = "Código aberto";
|
||||
"[linphone.org/contact](https://linphone.org/contact)" = "[linphone.org/contact](https://linphone.org/contact)";
|
||||
"*" = "*";
|
||||
"**%@**" = "**%@**";
|
||||
"#" = "#";
|
||||
"%@" = "%@";
|
||||
"%lld" = "%lld";
|
||||
"%lld %@" = "%1$lld %2$@";
|
||||
"%lld%%" = "%lld%%";
|
||||
"+" = "+";
|
||||
"|" = "|";
|
||||
"❤️" = "❤️";
|
||||
"👍" = "👍";
|
||||
"😂" = "😂";
|
||||
"😢" = "😢";
|
||||
"😮" = "😮";
|
||||
"0" = "0";
|
||||
"1" = "1";
|
||||
"2" = "2";
|
||||
"3" = "3";
|
||||
"4" = "4";
|
||||
"5" = "5";
|
||||
"6" = "6";
|
||||
"7" = "7";
|
||||
"8" = "8";
|
||||
"9" = "9";
|
||||
"account_settings_dialog_invalid_password_message" = "A conexão falhou porque a autenticação está ausente ou é inválida para a conta \n%@.\n\nVocê pode fornecer a senha novamente ou verificar a configuração da sua conta nas configurações.";
|
||||
"assistant_account_creation_sms_confirmation_explanation" = "Enviamos um código de verificação para o seu número de telefone %@. Por favor, insira o código de verificação abaixo:";
|
||||
"assistant_dialog_confirm_phone_number_message" = "Tem certeza de que deseja usar o número de telefone %@?";
|
||||
"assistant_permissions_subtitle" = "Para aproveitar ao máximo o %@, precisamos que você nos conceda as seguintes permissões:";
|
||||
"assistant_dialog_general_terms_and_privacy_policy_message" = "Ao continuar, você aceita nossos %@ e %@.";
|
||||
"assistant_forgotten_password" = "Esqueceu a senha?";
|
||||
"assistant_invalid_uri_toast" = "URI inválido";
|
||||
"assistant_permissions_access_camera_title" = "**Acessar a câmera:** Para capturar vídeo durante videochamadas e conferências.";
|
||||
"assistant_permissions_post_notifications_title" = "**Publicar notificações:** Para ser informado quando você receber uma mensagem ou uma chamada.";
|
||||
"assistant_permissions_read_contacts_title" = "**Ler contatos:** Para exibir seus contatos e encontrar quem está usando o %@.";
|
||||
"assistant_third_party_sip_account_create_linphone_account" = "Prefiro criar uma conta";
|
||||
"assistant_web_platform_link" = "subscribe.linphone.org";
|
||||
"call_action_attended_transfer" = "Transferência assistida";
|
||||
"call_audio_device_type_bluetooth" = "Bluetooth (%@)";
|
||||
"assistant_permissions_record_audio_title" = "**Gravar áudio:** Para que seu correspondente possa ouvi-lo e para gravar mensagens de voz.";
|
||||
"assistant_third_party_sip_account_warning_explanation" = "Alguns recursos exigem uma conta %@, como mensagens em grupo, videoconferências…\n\nEsses recursos ficam ocultos quando você se registra com uma conta SIP de terceiros.\n\nPara habilitá-los em um projeto comercial, entre em contato conosco.";
|
||||
"call_can_be_trusted_toast" = "Dispositivo autenticado";
|
||||
"call_transfer_current_call_title" = "Transferir chamada";
|
||||
"call_zrtp_sas_validation_skip" = "Pular";
|
||||
"calls_count_label" = "%@ chamadas";
|
||||
"Ce mode vous permet d’être interopérable avec d’autres services SIP.\nVos communications seront chiffrées de point à point. " = "Este modo permite que você seja interoperável com outros serviços SIP.\nSuas comunicações serão criptografadas de ponto a ponto. ";
|
||||
"Chiffrement de bout en bout de tous vos échanges, grâce au mode default vos communications sont à l’abri des regards." = "Criptografia de ponta a ponta de todas as suas trocas, graças ao modo padrão, suas comunicações ficam protegidas de olhares indiscretos.";
|
||||
"call_dialog_zrtp_validate_trust_warning_message" = "Para sua segurança, precisamos reautenticar o dispositivo do seu correspondente. Por favor, troquem novamente seus códigos:";
|
||||
"call_dialog_zrtp_validate_trust_message" = "Para sua segurança, precisamos autenticar o dispositivo do seu correspondente. Por favor, troquem seus códigos:";
|
||||
"conference_name_error" = "Erro no nome da conferência";
|
||||
"contact_dialog_delete_title" = "Excluir %@?";
|
||||
"contact_video_call_action" = "Chamada de vídeo";
|
||||
"contacts_list_filter_popup_see_linphone_only" = "Ver contatos %@";
|
||||
"conversation_composing_label_multiple" = "%@ estão digitando…";
|
||||
"conversation_composing_label_single" = "%@ está digitando…";
|
||||
"conversation_end_to_end_encrypted_bottom_sheet_link" = "https://linphone.org/en/features/#security";
|
||||
"contact_details_numbers_and_addresses_title" = "Números de telefone e endereços SIP";
|
||||
"conversation_ephemeral_messages_duration_multiple_days" = "%d dias";
|
||||
"conversation_event_admin_set" = "%@ é admin";
|
||||
"conversation_event_admin_unset" = "%@ não é mais admin";
|
||||
"conversation_event_device_removed" = "Dispositivo de %@ removido";
|
||||
"conversation_event_device_added" = "Novo dispositivo para %@";
|
||||
"conversation_event_ephemeral_messages_lifetime_changed" = "A duração efêmera agora é %@";
|
||||
"conversation_event_participant_added" = "%@ entrou";
|
||||
"conversation_info_participants_list_title" = "Membros do grupo (%d)";
|
||||
"conversation_message_meeting_cancelled_label" = "A reunião foi cancelada!";
|
||||
"conversation_one_to_one_hidden_subject" = "Dummy subject";
|
||||
"conversation_reply_to_message_title" = "Respondendo a: ";
|
||||
"debug_logs_copied_to_clipboard_toast" = "Logs de depuração copiados para a área de transferência";
|
||||
"Default" = "Padrão";
|
||||
"Default mode" = "Modo padrão";
|
||||
"dialog_close" = "Fechar";
|
||||
"help_about_title" = "Sobre o Linphone";
|
||||
"drawer_menu_account_connection_status_refreshing" = "Atualizando ...";
|
||||
"DTLS" = "DTLS";
|
||||
"failed_meeting_ics_invitation_not_sent_toast" = "Não foi possível enviar convites ICS da reunião para nenhum participante";
|
||||
"GC_MSG" = "Você foi adicionado a uma sala de chat";
|
||||
"help_about_contribute_translations_title" = "Contribua na tradução do Linphone";
|
||||
"help_about_open_source_licenses_subtitle" = "© Belledonne Communications 2010-2024";
|
||||
"help_about_open_source_licenses_title" = "GNU General Public License v3.0";
|
||||
"help_about_privacy_policy_subtitle" = "Quais informações o Linphone coleta e usa";
|
||||
"help_about_user_guide_title" = "Guia do usuário Linphone";
|
||||
"help_dialog_update_available_message" = "Uma nova versão %@ está disponível. Você quer atualizar?";
|
||||
"history_list_empty_with_filter_history" = "Nenhum registro corresponde à sua pesquisa";
|
||||
"history_title" = "Histórico de chamadas";
|
||||
"Interoperable" = "Interoperável";
|
||||
"IM_MSG" = "Você recebeu uma mensagem";
|
||||
"manage_account_dialog_international_prefix_help_message" = "Escolha seu país para permitir que o Linphone corresponda aos seus contatos.";
|
||||
"meeting_call_remove_no_participants" = "Nenhum participante no momento…";
|
||||
"meeting_call_remove_participant_confirmation_message" = "Tem certeza de que deseja remover %@ ?";
|
||||
"meeting_call_remove_participant_confirmation_title" = "Remover um participante";
|
||||
"meeting_exported_as_calendar_event" = "Reunião adicionada à agenda do iPhone";
|
||||
"meeting_failed_to_edit_toast" = "Falha ao editar reunião";
|
||||
"meeting_schedule_failed_no_subject_or_participant_toast" = "Um assunto e pelo menos um participante são necessários para criar uma reunião";
|
||||
"meeting_waiting_room_joining_subtitle" = "Você entrará em breve";
|
||||
"meetings_list_empty" = "Nenhuma reunião no momento…";
|
||||
"menu_block_address" = "Bloquear o endereço";
|
||||
"menu_block_number" = "Bloquear o número";
|
||||
"menu_copy_sip_address" = "Copiar endereço SIP";
|
||||
"message_copied_to_clipboard_toast" = "Mensagem copiada para a área de transferência";
|
||||
"message_delivery_info_read_title" = "Lida";
|
||||
"message_delivery_info_received_title" = "Recebida";
|
||||
"message_delivery_info_sent_title" = "Enviada";
|
||||
"message_meeting_invitation_cancelled_notification" = "📅 A reunião foi cancelada";
|
||||
"message_meeting_invitation_notification" = "📅 Você foi convidado para uma reunião";
|
||||
"message_meeting_invitation_updated_notification" = "📅 A reunião foi atualizada";
|
||||
"message_reactions_info_all_title" = "Reações";
|
||||
"network_reachable_again" = "A rede está acessível novamente";
|
||||
"None" = "Nenhum";
|
||||
"notification_chat_message_reaction_received" = "%@ reagiu com %@ para: %@";
|
||||
"Personnalize your profil mode" = "Personalize o modo do seu perfil";
|
||||
"picker_categories" = "Categorias";
|
||||
"selected_participants_count" = "%@ participantes selecionados";
|
||||
"qr_code_validated" = "QR code validado";
|
||||
"settings_calls_calibrate_echo_canceller_done" = "%@ ms";
|
||||
"settings_contacts_carddav_deleted_toast" = "Conta CardDAV excluída";
|
||||
"settings_contacts_carddav_mandatory_field_not_filled_toast" = "Por favor, preencha pelo menos o nome de exibição e a URL do servidor";
|
||||
"settings_contacts_carddav_sync_successful_toast" = "Sincronização bem-sucedida";
|
||||
"settings_contacts_carddav_use_as_default_title" = "Armazenar contatos recém-criados aqui";
|
||||
"settings_contacts_carddav_realm_title" = "Domínio de autenticação";
|
||||
"settings_contacts_ldap_bind_user_password_title" = "Senha do usuário de autenticação";
|
||||
"settings_contacts_ldap_max_results_title" = "Máximo de resultados";
|
||||
"settings_contacts_ldap_request_timeout_title" = "Tempo limite da solicitação";
|
||||
"sip_address" = "Endereço SIP";
|
||||
"sip.linphone.org" = "sip.linphone.org";
|
||||
"SRTP" = "SRTP";
|
||||
"TCP" = "TCP";
|
||||
"Temp Help" = "Ajuda Temp";
|
||||
"text_copied_to_clipboard_toast" = "Texto copiado para a área de transferência";
|
||||
"username_error" = "Erro no nome de usuário";
|
||||
"web_platform_forgotten_password_url" = "https://subscribe.linphone.org/";
|
||||
"website_contact_url" = "https://linphone.org/contact";
|
||||
"website_download_url" = "https://linphone.org/linphone-softphone";
|
||||
"website_user_guide_url" = "https://linphone.org/en/docs/";
|
||||
"welcome_page_subtitle" = "ao %@";
|
||||
"ZRTP" = "ZRTP";
|
||||
"conversation_event_participant_removed" = "%@ saiu";
|
||||
"conversation_event_subject_changed" = "Novo assunto: %@";
|
||||
"conversations_files_waiting_to_be_shared_single" = "1 arquivo esperando para ser compartilhado";
|
||||
"conversations_files_waiting_to_be_shared_multiple" = "%@ arquivos esperando para ser compartilhados";
|
||||
"notification_chat_message_received_title" = "Mensagem recebida";
|
||||
"TLS" = "TLS";
|
||||
"UDP" = "UDP";
|
||||
"uri_handler_bad_call_address_failed_toast" = "Não é possível ligar, endereço inválido";
|
||||
"uri_handler_bad_config_address_failed_toast" = "Não é possível recuperar a configuração, endereço inválido";
|
||||
"uri_handler_call_failed_toast" = "Falha na chamada";
|
||||
"uri_handler_config_failed_toast" = "Falha na configuração";
|
||||
"web_platform_register_email_url" = "https://subscribe.linphone.org/register/email";
|
||||
"website_open_source_licences_usage_url" = "https://wiki.linphone.org/xwiki/wiki/public/view/Linphone/Third%20party%20components%20/";
|
||||
"website_privacy_policy_url" = "https://linphone.org/en/privacy-policy";
|
||||
"website_terms_and_conditions_url" = "https://www.linphone.org/en/terms-of-use";
|
||||
"website_translate_weblate_url" = "https://weblate.linphone.org/";
|
||||
"[https://sip.linphone.org](https://sip.linphone.org)" = "[https://sip.linphone.org](https://sip.linphone.org)";
|
||||
": %@" = ": %@";
|
||||
"welcome_carousel_skip" = "Pular";
|
||||
"welcome_page_1_message" = "Um aplicativo de comunicação **seguro**, de **código aberto** e **francês**.";
|
||||
"welcome_page_2_message" = "Suas comunicações estão seguras graças à nossa **criptografia de ponta a ponta**.";
|
||||
"welcome_page_3_message" = "Um aplicativo **gratuito** e de código aberto desde **2001**.";
|
||||
"You will change this mode later" = "Você mudará este modo mais tarde";
|
||||
8
Linphone/Localizable/pt.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
"[linphone.org/contact](https://linphone.org/contact)" = "[linphone.org/contact](https://linphone.org/contact)";
|
||||
"*" = "*";
|
||||
"**%@**" = "**%@**";
|
||||
"#" = "#";
|
||||
"%@" = "%@";
|
||||
"%lld" = "%lld";
|
||||
"[https://sip.linphone.org](https://sip.linphone.org)" = "[https://sip.linphone.org](https://sip.linphone.org)";
|
||||
": %@" = ": %@";
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"dialog_deny" = "Отказать";
|
||||
"dialog_install" = "Установить";
|
||||
"dialog_no" = "Нет";
|
||||
"dialog_ok" = "ОК";
|
||||
"dialog_confirm" = "ОК";
|
||||
"dialog_yes" = "Да";
|
||||
"drawer_menu_account_connection_status_cleared" = "Отключить";
|
||||
"drawer_menu_account_connection_status_connected" = "Подключен";
|
||||
|
|
@ -362,3 +362,4 @@
|
|||
"username" = "Имя пользователя";
|
||||
"conversation_end_to_end_encrypted_event_title" = "Сквозное шифрование беседы";
|
||||
"conversation_end_to_end_encrypted_event_subtitle" = "Сообщения в этой беседе зашифрованы end-to-end шифрованием. Расшифровать их может только ваш собеседник.";
|
||||
"authentication_id" = "Идентификатор аутентификации (если отличается)";
|
||||
|
|
|
|||
382
Linphone/Localizable/sk.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
"account_settings_title" = "Nastavenia účtu";
|
||||
"account_settings_push_notification_not_available_title" = "Push notifikácie nie sú dostupné!";
|
||||
"account_settings_im_encryption_mandatory_title" = "Šifrovanie správ je povinné";
|
||||
"account_settings_sip_proxy_url_title" = "URL adresa SIP proxy servera";
|
||||
"account_settings_outbound_proxy_title" = "Proxy server pre odchádzajúcu komunikáciu";
|
||||
"account_settings_nat_policy_title" = "Nastavenia zásad NAT";
|
||||
"account_settings_enable_ice_title" = "Povoliť ICE";
|
||||
"account_settings_enable_turn_title" = "Povoliť TURN";
|
||||
"account_settings_turn_username_title" = "TURN používateľské meno";
|
||||
"account_settings_turn_password_title" = "TURN heslo";
|
||||
"account_settings_avpf_title" = "AVPF (Profil audio-vizuálu so spätnou väzbou)";
|
||||
"account_settings_expire_title" = "Platnosť (v sekundách)";
|
||||
"account_settings_audio_video_conference_factory_uri_title" = "URI adresa pre audio/video hovory";
|
||||
"account_settings_ccmp_server_url_title" = "RL adresa servera CCMP (Cisco CallManager Provisioning)";
|
||||
"account_settings_lime_server_url_title" = "URL adresa servera pre kľúče koncového šifrovania";
|
||||
"account_settings_bundle_mode_title" = "Režim zoskupenia";
|
||||
"account_settings_mwi_uri_title" = "URI adresa servera MWI (Message Waiting Indicator)";
|
||||
"account_settings_dialog_invalid_password_title" = "Vyžaduje sa overenie";
|
||||
"bottom_navigation_contacts_label" = "Kontakty";
|
||||
"bottom_navigation_calls_label" = "Hovory";
|
||||
"account_settings_voicemail_uri_title" = "URI adresa hlasovej schránky";
|
||||
"account_settings_update_password_title" = "Aktualizovať heslo";
|
||||
"bottom_navigation_meetings_label" = "Schôdzky";
|
||||
"contacts_list_empty" = "Momentálne žiadny kontakt…";
|
||||
"contacts_list_favourites_title" = "Obľúbené";
|
||||
"contacts_list_all_contacts_title" = "Všetky kontakty";
|
||||
"drawer_menu_manage_account" = "Spravovať profil";
|
||||
"drawer_menu_account_connection_status_connected" = "Pripojené";
|
||||
"drawer_menu_account_connection_status_cleared" = "Zakázané";
|
||||
"drawer_menu_account_connection_status_progress" = "Pripájanie…";
|
||||
"drawer_menu_account_connection_status_failed" = "Chyba";
|
||||
"drawer_menu_no_account_configured_yet" = "Žiadny účet zatiaľ nie je nastavený";
|
||||
"drawer_menu_add_account" = "Pridať účet";
|
||||
"help_about_check_for_update" = "Kontrola aktualizácie";
|
||||
"help_about_advanced_title" = "Pokročilé";
|
||||
"help_about_privacy_policy_title" = "Zásady ochrany súkromia";
|
||||
"help_about_version_title" = "Verzia";
|
||||
"help_version_up_to_date_toast_message" = "Vaša verzia je aktuálna";
|
||||
"help_error_checking_version_toast_message" = "Počas kontroly aktualizácie nastala chyba";
|
||||
"help_dialog_update_available_title" = "Je dostupná nová aktualizácia";
|
||||
"help_quit_title" = "Ukončiť aplikáciu";
|
||||
"help_troubleshooting_title" = "Riešenie problémov";
|
||||
"help_troubleshooting_clean_logs" = "Vyčistiť záznamy";
|
||||
"help_troubleshooting_print_logs_in_logcat" = "Zapisovať záznamy do logcatu";
|
||||
"help_troubleshooting_share_logs" = "Zdieľať záznamy";
|
||||
"help_troubleshooting_app_version_title" = "Verzia aplikácie";
|
||||
"help_troubleshooting_sdk_version_title" = "Verzia SDK";
|
||||
"help_troubleshooting_share_logs_dialog_title" = "Zdieľať odkaz na ladiace záznamy pomocou…";
|
||||
"help_troubleshooting_debug_logs_cleaned_toast_message" = "Ladiace záznamy boli vyčistené";
|
||||
"help_troubleshooting_debug_logs_upload_error_toast_message" = "Chyba pri nahrávaní ladiacich záznamov";
|
||||
"help_troubleshooting_show_config_file" = "Zobraziť konfiguráciu";
|
||||
"history_call_start_title" = "Nový hovor";
|
||||
"history_call_start_search_bar_filter_hint" = "Hľadať kontakt alebo históriu hovoru";
|
||||
"history_call_start_create_group_call" = "Vytvoriť skupinový hovor";
|
||||
"history_group_call_start_dialog_set_subject" = "Nastaviť predmet skupinového hovoru";
|
||||
"history_group_call_start_dialog_subject_hint" = "Predmet skupinového hovoru";
|
||||
"history_dialog_delete_all_call_logs_title" = "Naozaj chcete zmazať celú históriu hovorov?";
|
||||
"history_dialog_delete_all_call_logs_message" = "Z histórie budú odstránené všetky hovory";
|
||||
"manage_account_details_title" = "Podrobnosti";
|
||||
"manage_account_devices_title" = "Zariadenia";
|
||||
"manage_account_edit_picture" = "Upraviť obrázok";
|
||||
"manage_account_remove_picture" = "Odstrániť obrázok";
|
||||
"manage_account_status_connected_summary" = "Tento účet je aktívny, každý Vám môže volať.";
|
||||
"manage_account_international_prefix" = "Medzinárodný prefix";
|
||||
"manage_account_settings" = "Nastavenia účtu";
|
||||
"manage_account_delete" = "Odhlásiť sa";
|
||||
"manage_account_device_last_connection" = "Posledné pripojenie:";
|
||||
"manage_account_dialog_remove_account_title" = "Odhlásiť sa z Vášho účtu?";
|
||||
"manage_account_dialog_remove_account_message" = "Pokiaľ si želáte nenávratne zmazať svoj účet, navštívte: https://sip.linphone.org";
|
||||
"history_list_empty_history" = "Momentálne žiadny hovor…";
|
||||
"manage_account_title" = "Spravovať účet";
|
||||
"manage_account_status_failed_summary" = "Pripojenie účtu zlyhalo, skontrolujte nastavenia.";
|
||||
"settings_advanced_title" = "Pokročilé nastavenia";
|
||||
"settings_advanced_device_id_hint" = "Iba alfanumerické znaky";
|
||||
"settings_advanced_upload_server_url" = "URL adresa servera pre zdieľanie súborov";
|
||||
"settings_advanced_media_encryption_mandatory_title" = "Povinné šifrovanie médií";
|
||||
"settings_advanced_accept_early_media_title" = "Prijímať zvuk pred spojením hovoru (early media)";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Prenášať zvuk pri odchádzajúcom hovore (early media)";
|
||||
"settings_advanced_remote_provisioning_url" = "URL pre vzdialenú správu";
|
||||
"settings_advanced_download_apply_remote_provisioning" = "Stiahnuť a použiť";
|
||||
"settings_advanced_audio_devices_title" = "Zvukové zariadenia";
|
||||
"settings_advanced_input_audio_device_title" = "Predvolené vstupné zvukové zariadenie";
|
||||
"settings_advanced_output_audio_device_title" = "Predvolené výstupné zvukové zariadenie";
|
||||
"settings_advanced_audio_codecs_title" = "Zvukové kodeky";
|
||||
"settings_calls_calibrate_echo_canceller_in_progress" = "prebieha";
|
||||
"settings_calls_calibrate_echo_canceller_done_no_echo" = "bez ozveny";
|
||||
"settings_calls_calibrate_echo_canceller_failed" = "zlyhalo";
|
||||
"settings_calls_adaptive_rate_control_title" = "Adaptívny kontrola rýchlosti";
|
||||
"settings_calls_change_ringtone_title" = "Zmeniť vyzváňací tón";
|
||||
"settings_advanced_video_codecs_title" = "Video kodeky";
|
||||
"settings_calls_enable_video_title" = "Povoliť video";
|
||||
"settings_calls_enable_fec_title" = "Povoliť FEC pre video";
|
||||
"settings_calls_vibrate_while_ringing_title" = "Vibrovať počas prichádzajúceho hovoru";
|
||||
"settings_contacts_add_ldap_server_title" = "Pridať LDAP server";
|
||||
"settings_contacts_add_carddav_server_title" = "Pridať adresár CardDAV";
|
||||
"settings_contacts_carddav_server_url_title" = "URL adresa servera";
|
||||
"settings_contacts_carddav_sync_error_toast" = "Synchronizácia zlyhala!";
|
||||
"settings_contacts_edit_ldap_server_title" = "Upraviť LDAP server";
|
||||
"settings_contacts_edit_carddav_server_title" = "Upraviť adresár CardDAV";
|
||||
"settings_contacts_ldap_bind_dn_title" = "Bind DN (pripájací identifikátor)";
|
||||
"settings_title" = "Nastavenia";
|
||||
"settings_security_title" = "Zabezpečenie";
|
||||
"settings_security_enable_vfs_title" = "Šifrovať všetko";
|
||||
"settings_security_enable_vfs_subtitle" = "Varovanie: po zapnutí sa už nedá zrušiť!";
|
||||
"settings_security_prevent_screenshots_title" = "Zabrániť nahrávaniu rozhrania aplikácie";
|
||||
"settings_conversations_auto_download_title" = "Automaticky sťahovať súbory";
|
||||
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Označiť konverzáciu ako prečítanú pri zavretí oznámenia o správe";
|
||||
"settings_contacts_title" = "Kontakty";
|
||||
"settings_contacts_ldap_use_tls_title" = "Použiť TLS";
|
||||
"settings_contacts_ldap_server_url_title" = "URL adresa servera (nesmie byť prázdne)";
|
||||
"settings_meetings_title" = "Schôdzky";
|
||||
"settings_meetings_default_layout_title" = "Predvolené rozloženie";
|
||||
"settings_meetings_layout_active_speaker_label" = "Aktívny hovoriaci";
|
||||
"settings_meetings_layout_mosaic_label" = "Mozaika";
|
||||
"settings_network_title" = "Sieť";
|
||||
"settings_network_use_wifi_only" = "Používať iba siete Wi-Fi";
|
||||
"settings_network_allow_ipv6" = "Povoliť IPv6";
|
||||
"settings_conversations_title" = "Konverzácie";
|
||||
"help_troubleshooting_clear_native_friends_in_database" = "Vymazať importované kontakty zo systémového adresára";
|
||||
"manage_account_status_cleared_summary" = "Účet bol zakázaný, nebudete môcť prijímať hovory ani správy.";
|
||||
"account_settings_push_notification_title" = "Povoliť push notifikácie";
|
||||
"bottom_navigation_conversations_label" = "Konverzácie";
|
||||
"account_settings_stun_server_url_title" = "URL adresa servera STUN/TURN";
|
||||
"settings_calls_echo_canceller_title" = "Použiť softvérové potlačenie ozveny";
|
||||
"help_title" = "Pomoc";
|
||||
"settings_calls_auto_record_title" = "Automaticky spustiť nahrávanie hovorov";
|
||||
"help_about_user_guide_subtitle" = "Naučte sa krok za krokom ovládať všetky funkcie aplikácie.";
|
||||
"help_troubleshooting_firebase_project_title" = "ID Firebase project";
|
||||
"manage_account_status_progress_summary" = "Účet sa pripája k serveru, prosím, čakajte…";
|
||||
"settings_calls_title" = "Hovory";
|
||||
"settings_calls_echo_canceller_subtitle" = "Zabraňuje, aby ozvenu bolo počuť na vzdialenej strane, ak nie je k dispozícii hardvérové potlačenie ozveny";
|
||||
"settings_calls_calibrate_echo_canceller_title" = "Kalibrovať potlačenie ozveny";
|
||||
"manage_account_add_picture" = "Pridať obrázok";
|
||||
"account_settings_cpim_in_basic_conversations_title" = "Použiť CPIM v \"základných\" konverzáciách";
|
||||
"account_settings_conference_factory_uri_title" = "URI adresa pre vytváranie konferencií";
|
||||
"manage_account_device_remove" = "Odstrániť";
|
||||
"welcome_page_2_title" = "Zabezpečená";
|
||||
"welcome_page_3_title" = "Otvorená";
|
||||
"welcome_page_title" = "Vitajte";
|
||||
"account_settings_dialog_invalid_password_hint" = "Heslo";
|
||||
"assistant_account_create" = "Vytvoriť";
|
||||
"assistant_account_creation_wrong_phone_number" = "Nesprávne číslo?";
|
||||
"assistant_account_login" = "Prihlásenie";
|
||||
"assistant_account_login_forbidden_error" = "Nesprávne používateľské meno alebo heslo";
|
||||
"assistant_account_register" = "Registrácia";
|
||||
"assistant_account_register_push_notification_not_received_error" = "Push notifikácia s autentifikačným tokenom nebola prijatá behom 5 sekúnd, skúste to, prosím, neskôr znovu";
|
||||
"assistant_account_register_unexpected_error" = "Nastala neočakávaná chyba, skúste to, prosím, neskôr znovu";
|
||||
"assistant_already_have_an_account" = "Máte už účet?";
|
||||
"assistant_create_account_using_email_on_our_web_platform" = "Vytvorte účet pomocou svojej e-mailovej adresy na:";
|
||||
"assistant_dialog_confirm_phone_number_title" = "Potvrdiť telefónne číslo";
|
||||
"assistant_dialog_general_terms_and_privacy_policy_title" = "Všeobecné podmienky a zásady ochrany súkromia";
|
||||
"assistant_dialog_general_terms_label" = "všeobecné podmienky";
|
||||
"assistant_dialog_privacy_policy_label" = "zásady ochrany súkromia";
|
||||
"assistant_login_third_party_sip_account" = "Použiť SIP účet tretej strany";
|
||||
"assistant_no_account_yet" = "Nemáte ešte účet?";
|
||||
"assistant_permissions_grant_all_of_them" = "OK";
|
||||
"assistant_permissions_skip_permissions" = "Vykonať neskôr";
|
||||
"assistant_permissions_title" = "Udeliť oprávnenia";
|
||||
"assistant_qr_code_invalid_toast" = "Neplatný QR kód!";
|
||||
"assistant_scan_qr_code" = "Naskenovať QR kód";
|
||||
"assistant_sip_account_transport_protocol" = "Prenos";
|
||||
"assistant_third_party_sip_account_warning_ok" = "Rozumiem";
|
||||
"contact_call_action" = "Volať";
|
||||
"contact_details_delete" = "Vymazať";
|
||||
"conversation_action_call" = "Volať";
|
||||
"conversation_action_mark_as_read" = "Označiť ako prečítané";
|
||||
"dialog_accept" = "Prijať";
|
||||
"dialog_call" = "Volať";
|
||||
"dialog_cancel" = "Zrušiť";
|
||||
"dialog_continue" = "Pokračovať";
|
||||
"dialog_deny" = "Odmietnuť";
|
||||
"dialog_install" = "Inštalovať";
|
||||
"dialog_no" = "Nie";
|
||||
"dialog_confirm" = "Confirm";
|
||||
"dialog_yes" = "Áno";
|
||||
"meeting_waiting_room_cancel" = "Zrušiť";
|
||||
"menu_delete_selected_item" = "Vymazať";
|
||||
"menu_reply_to_chat_message" = "Odpovedať";
|
||||
"next" = "Ďalej";
|
||||
"notification_missed_call_title" = "Zmeškaný hovor";
|
||||
"or" = "alebo";
|
||||
"password" = "Heslo";
|
||||
"phone_number" = "Telefónne číslo";
|
||||
"settings_advanced_device_id" = "ID zariadenia";
|
||||
"settings_contacts_carddav_name_title" = "Zobrazované meno";
|
||||
"settings_contacts_carddav_password_title" = "Heslo";
|
||||
"settings_contacts_carddav_username_title" = "Používateľské meno";
|
||||
"settings_contacts_ldap_password_title" = "Heslo";
|
||||
"sip_address_copied_to_clipboard_toast" = "SIP adresa skopírovaná do schránky";
|
||||
"sip_address_display_name" = "Zobrazované meno";
|
||||
"sip_address_domain" = "Doména";
|
||||
"start" = "Začať";
|
||||
"uri_handler_config_success_toast" = "Konfigurácia bola úspešne nastavená";
|
||||
"username" = "Používateľské meno";
|
||||
"contacts_list_filter_popup_see_all" = "Zobraziť všetko";
|
||||
"contact_new_title" = "Nový kontakt";
|
||||
"contact_edit_title" = "Upraviť kontakt";
|
||||
"contact_editor_first_name" = "Krstné meno";
|
||||
"contact_editor_last_name" = "Priezvisko";
|
||||
"contact_editor_company" = "Spoločnosť";
|
||||
"contact_editor_job_title" = "Pracovná pozícia";
|
||||
"contact_editor_dialog_abort_confirmation_title" = "Neuložiť zmeny?";
|
||||
"contact_editor_dialog_abort_confirmation_message" = "Všetky zmeny budú stratené";
|
||||
"contact_details_actions_title" = "Ďalšie akcie";
|
||||
"contact_details_edit" = "Upraviť";
|
||||
"contact_details_add_to_favourites" = "Pridať do obľúbených";
|
||||
"contact_details_remove_from_favourites" = "Odstrániť z obľúbených";
|
||||
"contact_details_share" = "Zdieľať";
|
||||
"contact_dialog_delete_message" = "Tento kontakt bude definitívne odstránený.";
|
||||
"contact_dialog_pick_phone_number_or_sip_address_title" = "Vyberte číslo alebo SIP adresu";
|
||||
"contact_message_action" = "Správa";
|
||||
"contact_video_call_action" = "Videohovor";
|
||||
"conversation_action_mute" = "Stlmiť";
|
||||
"conversation_action_unmute" = "Zrušiť stlmenie";
|
||||
"conversation_action_delete" = "Vymazať konverzáciu";
|
||||
"conversation_action_leave_group" = "Opustiť skupinu";
|
||||
"conversation_ephemeral_messages_title" = "Dočasné (miznúce) správy";
|
||||
"conversations_list_empty" = "Momentálne žiadna konverzácia…";
|
||||
"conversation_action_configure_ephemeral_messages" = "Nastavenie dočasných (miznúcich) správ";
|
||||
"conference_layout_grid" = "Mozaika";
|
||||
"conversation_ephemeral_messages_duration_disabled" = "Zakázané";
|
||||
"conversation_menu_configure_ephemeral_messages" = "Dočasné (miznúce) správy";
|
||||
"Error" = "Chyba";
|
||||
"Interoperable mode" = "Režim vzájomnej kompatibility";
|
||||
"manage_account_no_device" = "Zariadenie sa nenašlo…";
|
||||
"message_delivery_info_error_title" = "Chyba";
|
||||
"call_action_start_new_call" = "Nový hovor";
|
||||
"call_stats_media_encryption_title" = "Šifrovanie médií";
|
||||
"settings_contacts_ldap_search_base_title" = "Počiatočný bod hľadania (nesmie byť prázdne)";
|
||||
"conversation_ephemeral_messages_subtitle" = "Nové správy sa automaticky odstránia, keď si ich všetci prečítajú.\nVyberte dobu trvania:";
|
||||
"conversation_ephemeral_messages_duration_one_minute" = "1 minúta";
|
||||
"conversation_ephemeral_messages_duration_one_hour" = "1 hodina";
|
||||
"conversation_ephemeral_messages_duration_one_day" = "1 deň";
|
||||
"conversation_ephemeral_messages_duration_three_days" = "3 dni";
|
||||
"conversation_ephemeral_messages_duration_one_week" = "1 týždeň";
|
||||
"conversation_add_participants_title" = "Pridať účastníkov";
|
||||
"conversation_end_to_end_encrypted_event_title" = "Koncové šifrovanie konverzácie";
|
||||
"conversation_end_to_end_encrypted_event_subtitle" = "Správy v tejto konverzácii sú koncovo šifrované. Dešifrovať ich môže len váš partner v konverzácii.";
|
||||
"conversation_dialog_set_subject" = "Nastaviť predmet konverzácie";
|
||||
"conversation_dialog_edit_subject" = "Upraviť predmet konverzácie";
|
||||
"conversation_dialog_subject_hint" = "Predmet konverzácie";
|
||||
"conversation_event_ephemeral_messages_enabled" = "Dočasné (miznúce) správy boli povolené";
|
||||
"conversation_event_ephemeral_messages_disabled" = "Dočasné (miznúce) správy boli zakázané";
|
||||
"conversation_event_conference_created" = "Pridali ste sa ku skupine";
|
||||
"conversation_event_conference_destroyed" = "Opustili ste skupinu";
|
||||
"conversation_info_add_participants_label" = "Pridať účastníkov";
|
||||
"conversation_info_delete_history_action" = "Vymazať históriu";
|
||||
"conversation_info_admin_menu_remove_participant" = "Odstrániť zo skupiny";
|
||||
"conversation_info_admin_menu_set_participant_admin" = "Udeliť práva administrátora";
|
||||
"conversation_info_admin_menu_unset_participant_admin" = "Odobrať práva administrátora";
|
||||
"conversation_info_menu_go_to_contact" = "Zobraziť profil kontaktu";
|
||||
"conversation_info_menu_add_to_contacts" = "Pridať do kontaktov";
|
||||
"conversation_info_confirm_start_group_call_dialog_message" = "Bude volané všetkým účastníkom.";
|
||||
"conversation_text_field_hint" = "Napíšte niečo…";
|
||||
"conversation_menu_go_to_info" = "Informácia o konverzácii";
|
||||
"conversation_invalid_participant_due_to_security_mode_toast" = "Vytvorenie konverzácie s účastníkom z inej domény nie je z bezpečnostných dôvodov povolené!";
|
||||
"conversation_take_picture_label" = "Odfotiť";
|
||||
"conversation_pick_file_from_gallery_label" = "Otvoriť galériu";
|
||||
"conversation_pick_any_file_label" = "Vybrať súbor";
|
||||
"conversation_file_cant_be_opened_error_toast" = "Súbor sa nedá otvoriť!";
|
||||
"new_conversation_title" = "Nová konverzácia";
|
||||
"new_conversation_search_bar_filter_hint" = "Hľadať kontakt";
|
||||
"new_conversation_create_group" = "Vytvoriť skupinovú konverzáciu";
|
||||
"conversation_info_participant_is_admin_label" = "Administrátor";
|
||||
"conversation_info_confirm_start_group_call_dialog_title" = "Zahájiť skupinový hovor?";
|
||||
"conversation_failed_to_create_toast" = "Vytvorenie konverzácie zlyhalo!";
|
||||
"call_action_go_to_calls_list" = "Zoznam hovorov";
|
||||
"call_action_change_layout" = "Rozloženie";
|
||||
"call_outgoing" = "Odchádzajúci hovor";
|
||||
"call_audio_incoming" = "Prichádzajúci hovor";
|
||||
"call_action_show_messages" = "Správy";
|
||||
"call_action_pause_call" = "Pozastaviť";
|
||||
"call_action_resume_call" = "Obnoviť";
|
||||
"call_action_record_call" = "Nahrať";
|
||||
"call_action_hang_up" = "Zavesiť";
|
||||
"call_do_zrtp_sas_validation_again" = "Znovu overiť ZRTP SAS";
|
||||
"call_not_encrypted" = "Hovor nie je šifrovaný";
|
||||
"call_dialog_zrtp_validate_trust_local_code_label" = "Váš kód:";
|
||||
"call_dialog_zrtp_validate_trust_remote_code_label" = "Kód protistrany:";
|
||||
"call_dialog_zrtp_validate_trust_letters_do_not_match" = "Žiadna zhoda";
|
||||
"call_dialog_zrtp_security_alert_title" = "Bezpečnostné upozornenie";
|
||||
"call_dialog_zrtp_security_alert_try_again" = "Skúsiť znovu";
|
||||
"call_dialog_zrtp_security_alert_message" = "Dôvernosť tohto hovoru môže byť ohrozená!";
|
||||
"call_audio_device_type_earpiece" = "Slúchadlo";
|
||||
"call_audio_device_type_speaker" = "Reproduktor";
|
||||
"call_state_connected" = "Aktívny";
|
||||
"call_state_paused" = "Pozastavené";
|
||||
"call_state_paused_by_remote" = "Pozastavené vzdialenou stranou";
|
||||
"call_state_resuming" = "Obnovujem…";
|
||||
"call_stats_audio_title" = "Zvuk";
|
||||
"call_zrtp_sas_validation_required" = "Vyžadované overenie";
|
||||
"calls_list_title" = "Zoznam hovorov";
|
||||
"calls_list_dialog_merge_into_conference_title" = "Zlúčiť všetky hovory do konferencie?";
|
||||
"calls_list_dialog_merge_into_conference_label" = "Vytvoriť konferenciu";
|
||||
"conversation_forward_message_title" = "Preposlať správu…";
|
||||
"conversation_message_forwarded_toast" = "Správa bola preposlaná";
|
||||
"conversation_message_forward_cancelled_toast" = "Preposlanie správy bolo zrušené";
|
||||
"meeting_schedule_meeting_label" = "Schôdzka";
|
||||
"meeting_schedule_pick_start_date_title" = "Zvoľte dátum začiatku";
|
||||
"meeting_schedule_pick_start_time_title" = "Zvoľte čas začiatku";
|
||||
"meeting_schedule_pick_end_time_title" = "Zvoľte čas konca";
|
||||
"meeting_schedule_description_hint" = "Pridať popis";
|
||||
"meeting_schedule_add_participants_title" = "Pridať účastníkov";
|
||||
"meeting_info_join_title" = "Pripojiť sa k schôdzke teraz";
|
||||
"meeting_info_organizer_label" = "Organizátor";
|
||||
"meeting_info_export_as_calendar_event" = "Vytvoriť udalosť v kalendári";
|
||||
"meeting_info_deleted_toast" = "Schôdzka bola vymazaná";
|
||||
"meeting_schedule_description_title" = "Popis";
|
||||
"meeting_schedule_edit_title" = "Upraviť schôdzku";
|
||||
"meeting_schedule_cancel_dialog_title" = "Zrušiť schôdzku?";
|
||||
"meeting_schedule_cancel_dialog_message" = "Želáte si zrušiť schôdzku a odoslať oznámenie všetkým účastníkom?";
|
||||
"meeting_info_created_toast" = "Schôdzka bola vytvorená";
|
||||
"meeting_info_updated_toast" = "Schôdzka bola aktualizovaná";
|
||||
"meeting_failed_to_schedule_toast" = "Naplánovanie schôdzky zlyhalo!";
|
||||
"meeting_failed_to_send_invites_toast" = "Odoslanie všetkých pozvánok na schôdzku zlyhalo!";
|
||||
"meeting_schedule_title" = "Nová schôdzka";
|
||||
"meeting_schedule_timezone_title" = "Časová zóna";
|
||||
"meeting_waiting_room_join" = "Pripojiť sa";
|
||||
"meeting_waiting_room_joining_title" = "Prebieha pripojenie";
|
||||
"message_reaction_click_to_remove_label" = "Kliknutím odstránite";
|
||||
"message_forwarded_label" = "Preposlané";
|
||||
"call_waiting_for_encryption_info" = "Čaká sa na šifrovanie…";
|
||||
"call_zrtp_end_to_end_encrypted" = "Koncovo šifrované pomocou ZRTP";
|
||||
"call_dialog_zrtp_validate_trust_title" = "Overiť zariadenie";
|
||||
"meeting_schedule_send_invitations_title" = "Odoslať pozvánku účastníkom";
|
||||
"call_audio_device_type_headphones" = "Slúchadlá";
|
||||
"meeting_schedule_subject_hint" = "Pridať názov…";
|
||||
"call_action_show_dialer" = "Číselník (numerická klávesnica)";
|
||||
"call_srtp_point_to_point_encrypted" = "Koncové šifrovanie pomocou SRTP";
|
||||
"meetings_list_no_meeting_for_today" = "Nadnes nie je naplánovaná žiadna schôdzka";
|
||||
"meeting_info_cancelled_toast" = "Schôdzka bola zrušená";
|
||||
"meeting_failed_to_send_part_of_invites_toast" = "Odoslanie pozvánok na schôdzku niektorým účastníkom zlyhalo!";
|
||||
"account_settings_dialog_invalid_password_message" = "Pripojenie sa nepodarilo, pretože chýba alebo je neplatné overenie účtu\n%@.\n\nPokúste sa znovu zadať heslo alebo skontrolovať konfiguráciu účtu v nastaveniach.";
|
||||
": %@" = ": %@";
|
||||
"[https://sip.linphone.org](https://sip.linphone.org)" = "[https://sip.linphone.org](https://sip.linphone.org)";
|
||||
"[linphone.org/contact](https://linphone.org/contact)" = "[linphone.org/contact](https://linphone.org/contact)";
|
||||
"*" = "*";
|
||||
"assistant_dialog_general_terms_and_privacy_policy_message" = "Pokračovaním akceptujete naše %1$s a %2$s.";
|
||||
"assistant_third_party_sip_account_create_linphone_account" = "Preferujem vytvorenie účtu";
|
||||
"assistant_permissions_subtitle" = "Pre plné využívanie %@ potrebujeme udelenie nasledovných oprávnení:";
|
||||
"assistant_account_creation_sms_confirmation_explanation" = "Poslali sme Vám overovací kód na Vaše telefónne číslo %@. Zadajte nižšie, prosím, verifikačný kód:";
|
||||
"assistant_forgotten_password" = "Zabudnuté heslo?";
|
||||
"assistant_invalid_uri_toast" = "Neplatná URI adresa";
|
||||
"call_history_deleted_toast" = "História bola vymazaná";
|
||||
"call_stats_video_title" = "Video (obraz)";
|
||||
"call_transfer_in_progress_toast" = "Hovor je presmerovaný";
|
||||
"call_transfer_failed_toast" = "Presmerovanie hovoru zlyhalo!";
|
||||
"call_transfer_successful_toast" = "Hovor bol úspešne presmerovaný";
|
||||
"conference_share_link_title" = "Zdieľať pozvánku";
|
||||
"conference_call_empty" = "Čakanie na ďalších účastníkov…";
|
||||
"conference_action_screen_sharing" = "Zdieľať obrazovku";
|
||||
"conference_action_show_participants" = "Účastníci";
|
||||
"conference_failed_to_create_group_call_toast" = "Vytvorenie skupinového hovoru zlyhalo!";
|
||||
"conference_participant_joining_text" = "Pripojovanie…";
|
||||
"conference_participant_paused_text" = "Pozastavené";
|
||||
"conference_layout_active_speaker" = "Hovoriaci (rečník)";
|
||||
"conference_layout_audio_only" = "Iba zvuk";
|
||||
"connection_error_for_non_default_account" = "Chyba pripojenia účtu(-ov)";
|
||||
"default_account_disabled" = "Zvolený účet je momentálne zakázaný";
|
||||
"generic_address_picker_suggestions_list_title" = "Návrhy";
|
||||
"help_about_title" = "O aplikácii Linphone";
|
||||
"help_about_contribute_translations_title" = "Prispieť do prekladu Linphone";
|
||||
"help_about_open_source_licenses_subtitle" = "© Belledonne Communications 2010-2024";
|
||||
"help_about_open_source_licenses_title" = "GNU General Public License v3.0";
|
||||
"list_filter_no_result_found" = "Žiadne výsledky…";
|
||||
"menu_add_address_to_contacts" = "Pridať do kontaktov";
|
||||
"menu_see_existing_contact" = "Zobraziť kontakt";
|
||||
"menu_copy_phone_number" = "Kopírovať telefónne číslo";
|
||||
"menu_delete_history" = "Vymazať históriu";
|
||||
"menu_invite" = "Pozvať";
|
||||
"menu_resend_chat_message" = "Znovu odoslať";
|
||||
"menu_show_imdn" = "Stav doručenia";
|
||||
"menu_forward_chat_message" = "Preposlať";
|
||||
"menu_copy_chat_message" = "Kopírovať";
|
||||
"network_not_reachable" = "Nie ste pripojený k internetu";
|
||||
"operation_in_progress_overlay" = "Prebieha operácia, prosím, čakajte";
|
||||
"recordings_title" = "Nahrávky";
|
||||
"settings_contacts_ldap_max_results_title" = "Maximálny počet výsledkov";
|
||||
"welcome_page_subtitle" = "v %@";
|
||||
"call_action_blind_transfer" = "Presmerovať";
|
||||
"conversation_message_meeting_updated_label" = "Schôdzka bola aktualizovaná";
|
||||
"meeting_info_delete" = "Vymazať schôdzku";
|
||||
|
|
@ -377,7 +377,7 @@
|
|||
"conversation_ephemeral_messages_duration_one_week" = "1 тиждень";
|
||||
"conversation_ephemeral_messages_duration_three_days" = "3 доби";
|
||||
"conversation_ephemeral_messages_duration_one_day" = "1 доба";
|
||||
"dialog_ok" = "ОК";
|
||||
"dialog_confirm" = "ОК";
|
||||
"welcome_page_title" = "Ласкаво просимо";
|
||||
": %@" = ": %@";
|
||||
"[https://sip.linphone.org](https://sip.linphone.org)" = "[https://sip.linphone.org](https://sip.linphone.org)";
|
||||
|
|
@ -456,9 +456,9 @@
|
|||
"message_delivery_info_read_title" = "Читати";
|
||||
"message_delivery_info_received_title" = "Отримано";
|
||||
"message_delivery_info_sent_title" = "Відправлено";
|
||||
"message_meeting_invitation_cancelled_notification" = "📅 Нараду скасовано";
|
||||
"message_meeting_invitation_notification" = "📅 Вас запрошено на нараду";
|
||||
"message_meeting_invitation_updated_notification" = "📅 Нараду оновлено";
|
||||
"message_meeting_invitation_cancelled_notification" = "Нараду скасовано";
|
||||
"message_meeting_invitation_notification" = "Вас запрошено на нараду";
|
||||
"message_meeting_invitation_updated_notification" = "Нараду оновлено";
|
||||
"message_reactions_info_all_title" = "Реакції";
|
||||
"network_reachable_again" = "Мережа знову доступна";
|
||||
"None" = "Жоден";
|
||||
|
|
@ -522,3 +522,4 @@
|
|||
"conversations_files_waiting_to_be_shared_single" = "1 файл очікує на спільний доступ";
|
||||
"conversations_files_waiting_to_be_shared_multiple" = "%@ файлів, що очікують на спільний доступ";
|
||||
"conversation_ephemeral_messages_duration_multiple_days" = "%d днів";
|
||||
"authentication_id" = "Ідентифікатор автентифікації (якщо відрізняється)";
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@
|
|||
"dialog_deny" = "拒绝";
|
||||
"dialog_install" = "安装";
|
||||
"dialog_no" = "否";
|
||||
"dialog_ok" = "OK";
|
||||
"dialog_confirm" = "Confirm";
|
||||
"dialog_yes" = "是";
|
||||
"drawer_menu_account_connection_status_cleared" = "禁用";
|
||||
"drawer_menu_account_connection_status_connected" = "已连接";
|
||||
|
|
@ -362,3 +362,5 @@
|
|||
"menu_show_imdn" = "送达状态";
|
||||
"conversation_end_to_end_encrypted_event_title" = "端到端加密聊天";
|
||||
"conversation_end_to_end_encrypted_event_subtitle" = "此聊天中的消息是端对端e2e加密的。只有您的对话方才能解密它们。";
|
||||
"authentication_id" = "认证ID";
|
||||
"settings_contacts_ldap_search_filter_title" = "搜索base(不能为空) 过滤";
|
||||
|
|
|
|||
|
|
@ -30,10 +30,7 @@
|
|||
<entry name="protocols" overwrite="true">stun,ice</entry>
|
||||
</section>
|
||||
<section name="sip">
|
||||
<entry name="media_encryption" overwrite="true">zrtp</entry>
|
||||
<entry name="media_encryption" overwrite="true">srtp</entry>
|
||||
<entry name="media_encryption_mandatory">1</entry>
|
||||
</section>
|
||||
<section name="net">
|
||||
<entry name="friendlist_subscription_enabled" overwrite="true">1</entry>
|
||||
</section>
|
||||
</config>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<entry name="conference_factory_uri" overwrite="true"></entry>
|
||||
<entry name="audio_video_conference_factory_uri" overwrite="true"></entry>
|
||||
<entry name="push_notification_allowed" overwrite="true">0</entry>
|
||||
<entry name="remote_push_notification_allowed" overwrite="true">0</entry>
|
||||
<entry name="cpim_in_basic_chat_rooms_enabled" overwrite="true">0</entry>
|
||||
<entry name="rtp_bundle" overwrite="true">0</entry>
|
||||
<entry name="lime_server_url" overwrite="true"></entry>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ media_encryption=none
|
|||
update_presence_model_timestamp_before_publish_expires_refresh=1
|
||||
use_rfc2833=1
|
||||
use_info=1
|
||||
rls_uri=sips:rls@sip.linphone.org
|
||||
|
||||
[net]
|
||||
#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit"
|
||||
|
|
|
|||
|
|
@ -22,20 +22,17 @@ zrtp_key_agreements_suites=MS_ZRTP_KEY_AGREEMENT_K255_MLK512,MS_ZRTP_KEY_AGREEME
|
|||
chat_messages_aggregation_delay=1000
|
||||
chat_messages_aggregation=1
|
||||
update_presence_model_timestamp_before_publish_expires_refresh=1
|
||||
rls_uri=sips:rls@sip.linphone.org
|
||||
|
||||
[sound]
|
||||
#remove this property for any application that is not Linphone public version itself
|
||||
ec_calibrator_cool_tones=1
|
||||
disable_ringing=1
|
||||
disable_ringing=0
|
||||
|
||||
[audio]
|
||||
|
||||
[video]
|
||||
auto_resize_preview_to_keep_ratio=1
|
||||
max_conference_size=vga
|
||||
automatically_accept=1
|
||||
automatically_initiate=0
|
||||
|
||||
[misc]
|
||||
enable_basic_to_client_group_chat_room_migration=0
|
||||
|
|
@ -49,6 +46,7 @@ record_aware=1
|
|||
|
||||
[account_creator]
|
||||
url=https://subscribe.linphone.org/api/
|
||||
backend=1
|
||||
|
||||
[lime]
|
||||
lime_update_threshold=86400
|
||||
|
|
|
|||
|
|
@ -20,20 +20,19 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SplashScreen: View {
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { _ in
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Image("linphone")
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
ZStack {
|
||||
Color.white
|
||||
.ignoresSafeArea()
|
||||
|
||||
Image("linphone")
|
||||
.resizable()
|
||||
.renderingMode(.template)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 240, height: 128)
|
||||
.foregroundColor(ThemeManager.shared.currentTheme.main500)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.ignoresSafeArea(.all)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,17 +195,13 @@ class TelecomManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
private func makeRecordFilePath() -> String {
|
||||
var filePath = "recording_"
|
||||
let now = Date()
|
||||
let dateFormat = DateFormatter()
|
||||
dateFormat.dateFormat = "E-d-MMM-yyyy-HH-mm-ss"
|
||||
let date = dateFormat.string(from: now)
|
||||
filePath = filePath.appending("\(date).mkv")
|
||||
private func makeRecordFilePath(address: String) -> String {
|
||||
var filePath = "call_recording_sip_" + address.dropFirst(4) + "_on_"
|
||||
|
||||
let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
|
||||
let writablePath = paths[0]
|
||||
return writablePath.appending("/\(filePath)")
|
||||
filePath = filePath.appending("\(Int(Date().timeIntervalSince1970)).mkv")
|
||||
|
||||
let writablePath = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Recordings/\(filePath)")
|
||||
return writablePath.path
|
||||
}
|
||||
|
||||
func doCall(core: Core, addr: Address, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws {
|
||||
|
|
@ -237,7 +233,7 @@ class TelecomManager: ObservableObject {
|
|||
// Log.directLog(BCTBX_LOG_DEBUG, text: "record file path: \(writablePath)")
|
||||
// lcallParams.recordFile = writablePath
|
||||
|
||||
lcallParams.recordFile = makeRecordFilePath()
|
||||
lcallParams.recordFile = makeRecordFilePath(address: addr.asStringUriOnly())
|
||||
|
||||
if isSas {
|
||||
lcallParams.mediaEncryption = .ZRTP
|
||||
|
|
@ -292,7 +288,7 @@ class TelecomManager: ObservableObject {
|
|||
func acceptCall(core: Core, call: Call, hasVideo: Bool) {
|
||||
do {
|
||||
let callParams = try core.createCallParams(call: call)
|
||||
callParams.recordFile = makeRecordFilePath()
|
||||
callParams.recordFile = makeRecordFilePath(address: call.remoteAddress?.asStringUriOnly() ?? "")
|
||||
callParams.videoEnabled = hasVideo
|
||||
/*if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) {
|
||||
let low_bandwidth = (AppManager.network() == .network_2g)
|
||||
|
|
@ -342,11 +338,13 @@ class TelecomManager: ObservableObject {
|
|||
}
|
||||
|
||||
func terminateCall(call: Call) {
|
||||
do {
|
||||
try call.terminate()
|
||||
Log.info("Call terminated")
|
||||
} catch {
|
||||
Log.error("Failed to terminate call failed because \(error)")
|
||||
CoreContext.shared.doOnCoreQueue { _ in
|
||||
do {
|
||||
try call.terminate()
|
||||
Log.info("Call terminated")
|
||||
} catch {
|
||||
Log.error("Failed to terminate call failed because \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -435,6 +433,7 @@ class TelecomManager: ObservableObject {
|
|||
func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) {
|
||||
let callLog = call.callLog
|
||||
let callId = callLog?.callId ?? ""
|
||||
|
||||
if !callInProgress && participantsInvited {
|
||||
if let remoteAddress = call.remoteAddress {
|
||||
let uuid = UUID()
|
||||
|
|
@ -623,6 +622,11 @@ class TelecomManager: ObservableObject {
|
|||
|
||||
Log.info("CallKit: outgoing call started connecting with uuid \(uuid!) and callId \(callId)")
|
||||
providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!)
|
||||
} else if callId != "" && cstate == .OutgoingInit {
|
||||
if let uuidTmp = providerDelegate.uuids["\(callId)"] {
|
||||
providerDelegate.uuids.removeValue(forKey: callId)
|
||||
providerDelegate.uuids.updateValue(uuidTmp, forKey: "")
|
||||
}
|
||||
} else {
|
||||
referedToCall = callId
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,14 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
struct LoginFragment: View {
|
||||
|
||||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
|
||||
@StateObject private var accountLoginViewModel = AccountLoginViewModel()
|
||||
@StateObject private var keyboard = KeyboardResponder()
|
||||
|
||||
@State private var isSecured: Bool = true
|
||||
|
||||
|
|
@ -37,6 +39,8 @@ struct LoginFragment: View {
|
|||
@State private var isLinkSIPActive = false
|
||||
@State private var isLinkREGActive = false
|
||||
|
||||
@State var isShowHelpFragment = false
|
||||
|
||||
var isShowBack = false
|
||||
|
||||
var onBackPressed: (() -> Void)?
|
||||
|
|
@ -66,25 +70,33 @@ struct LoginFragment: View {
|
|||
let contentPopup3 = Text(.init(splitMsg[1]))
|
||||
let contentPopup4 = Text(.init(privacyPolicy)).underline()
|
||||
let contentPopup5 = Text(.init(splitMsg[2]))
|
||||
PopupView(isShowPopup: $isShowPopup,
|
||||
title: Text("assistant_dialog_general_terms_and_privacy_policy_title"),
|
||||
content: contentPopup1 + contentPopup2 + contentPopup3 + contentPopup4 + contentPopup5,
|
||||
titleFirstButton: Text("dialog_deny"),
|
||||
actionFirstButton: {self.isShowPopup.toggle()},
|
||||
titleSecondButton: Text("dialog_accept"),
|
||||
actionSecondButton: {acceptGeneralTerms()})
|
||||
PopupView(
|
||||
isShowPopup: $isShowPopup,
|
||||
title: Text("assistant_dialog_general_terms_and_privacy_policy_title"),
|
||||
content: contentPopup1 + contentPopup2 + contentPopup3 + contentPopup4 + contentPopup5,
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_accept"),
|
||||
actionSecondButton: { acceptGeneralTerms() },
|
||||
titleThirdButton: Text("dialog_deny"),
|
||||
actionThirdButton: { self.isShowPopup.toggle() }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.onTapGesture {
|
||||
self.isShowPopup.toggle()
|
||||
}
|
||||
} else { // backup just in case
|
||||
PopupView(isShowPopup: $isShowPopup,
|
||||
title: Text("assistant_dialog_general_terms_and_privacy_policy_title"),
|
||||
content: Text(.init(String(format: String(localized: "assistant_dialog_general_terms_and_privacy_policy_message"), generalTerms, privacyPolicy))),
|
||||
titleFirstButton: Text("dialog_deny"),
|
||||
actionFirstButton: {self.isShowPopup.toggle()},
|
||||
titleSecondButton: Text("dialog_accept"),
|
||||
actionSecondButton: {acceptGeneralTerms()})
|
||||
PopupView(
|
||||
isShowPopup: $isShowPopup,
|
||||
title: Text("assistant_dialog_general_terms_and_privacy_policy_title"),
|
||||
content: Text(.init(String(format: String(localized: "assistant_dialog_general_terms_and_privacy_policy_message"), generalTerms, privacyPolicy))),
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_accept"),
|
||||
actionSecondButton: { acceptGeneralTerms() },
|
||||
titleThirdButton: Text("dialog_deny"),
|
||||
actionThirdButton: { self.isShowPopup.toggle() }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.onTapGesture {
|
||||
self.isShowPopup.toggle()
|
||||
|
|
@ -93,6 +105,14 @@ struct LoginFragment: View {
|
|||
}
|
||||
}
|
||||
|
||||
if isShowHelpFragment {
|
||||
HelpFragment(
|
||||
isShowHelpFragment: $isShowHelpFragment
|
||||
)
|
||||
.transition(.move(edge: .trailing))
|
||||
.zIndex(3)
|
||||
}
|
||||
|
||||
if coreContext.loggingInProgress {
|
||||
PopupLoadingView()
|
||||
.background(.black.opacity(0.65))
|
||||
|
|
@ -129,6 +149,26 @@ struct LoginFragment: View {
|
|||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
isShowHelpFragment = true
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("question")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("help_title")
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.default_text_style_orange_600(styleSize: 15)
|
||||
.frame(height: 35)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
|
||||
Text("assistant_account_login")
|
||||
|
|
@ -313,7 +353,7 @@ struct LoginFragment: View {
|
|||
.foregroundStyle(Color.grayMain2c700)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
NavigationLink(destination: RegisterFragment(registerViewModel: RegisterViewModel()), isActive: $isLinkREGActive, label: { Text("assistant_account_register")
|
||||
NavigationLink(destination: RegisterFragment(), isActive: $isLinkREGActive, label: { Text("assistant_account_register")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
})
|
||||
|
|
@ -347,6 +387,7 @@ struct LoginFragment: View {
|
|||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
.padding(.bottom, keyboard.currentHeight)
|
||||
}
|
||||
|
||||
func acceptGeneralTerms() {
|
||||
|
|
|
|||
|
|
@ -145,20 +145,21 @@ struct ProfileModeFragment: View {
|
|||
}
|
||||
|
||||
if self.isShowPopup {
|
||||
PopupView(isShowPopup: $isShowPopup,
|
||||
title: Text(isShowPopupForDefault ? "Default mode" : "Interoperable mode"),
|
||||
content: Text(
|
||||
isShowPopupForDefault
|
||||
? "Texte explicatif du default mode : lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
+ "Etiam velit sapien, egestas sit amet dictum eget, condimentum a ligula."
|
||||
: "Texte explicatif du interoperable mode : lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
+ " Etiam velit sapien, egestas sit amet dictum eget, condimentum a ligula."),
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_close"),
|
||||
actionSecondButton: {
|
||||
self.isShowPopup.toggle()
|
||||
}
|
||||
PopupView(
|
||||
isShowPopup: $isShowPopup,
|
||||
title: Text(isShowPopupForDefault ? "Default mode" : "Interoperable mode"),
|
||||
content: Text(
|
||||
isShowPopupForDefault
|
||||
? "Texte explicatif du default mode : lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
+ "Etiam velit sapien, egestas sit amet dictum eget, condimentum a ligula."
|
||||
: "Texte explicatif du interoperable mode : lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
+ " Etiam velit sapien, egestas sit amet dictum eget, condimentum a ligula."),
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: nil,
|
||||
actionSecondButton: {},
|
||||
titleThirdButton: Text("dialog_close"),
|
||||
actionThirdButton: { self.isShowPopup.toggle() }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.onTapGesture {
|
||||
|
|
|
|||
|
|
@ -22,10 +22,12 @@
|
|||
import SwiftUI
|
||||
|
||||
struct RegisterFragment: View {
|
||||
|
||||
@ObservedObject var registerViewModel: RegisterViewModel
|
||||
@ObservedObject var sharedMainViewModel = SharedMainViewModel.shared
|
||||
|
||||
@StateObject private var registerViewModel = RegisterViewModel()
|
||||
|
||||
@StateObject private var keyboard = KeyboardResponder()
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@State private var isSecured: Bool = true
|
||||
|
|
@ -55,22 +57,21 @@ struct RegisterFragment: View {
|
|||
let titlePopup = Text("assistant_dialog_confirm_phone_number_title")
|
||||
let contentPopup = Text(String(format: String(localized: "assistant_dialog_confirm_phone_number_message"), registerViewModel.phoneNumber))
|
||||
|
||||
|
||||
PopupView(
|
||||
isShowPopup: $isShowPopup,
|
||||
title: titlePopup,
|
||||
content: contentPopup,
|
||||
titleFirstButton: Text("dialog_cancel"),
|
||||
actionFirstButton: {
|
||||
self.isShowPopup = false
|
||||
},
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_continue"),
|
||||
actionSecondButton: {
|
||||
self.isShowPopup = false
|
||||
registerViewModel.createInProgress = true
|
||||
registerViewModel.startAccountCreation()
|
||||
registerViewModel.phoneNumberConfirmedByUser()
|
||||
}
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: { self.isShowPopup = false },
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.onTapGesture {
|
||||
|
|
@ -269,13 +270,13 @@ struct RegisterFragment: View {
|
|||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background((registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) ? Color.orangeMain100 : Color.orangeMain500)
|
||||
.background((registerViewModel.username.isEmpty || registerViewModel.dialPlanValueSelected == "---" || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) ? Color.orangeMain100 : Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.disabled(!registerViewModel.isLinkActive)
|
||||
.padding(.bottom)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
if !(registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) {
|
||||
if !(registerViewModel.username.isEmpty || registerViewModel.dialPlanValueSelected == "---" || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) {
|
||||
withAnimation {
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
|
@ -347,11 +348,8 @@ struct RegisterFragment: View {
|
|||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
.padding(.bottom, keyboard.currentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
RegisterFragment(registerViewModel: RegisterViewModel())
|
||||
}
|
||||
|
||||
// swiftlint:enable line_length
|
||||
|
|
|
|||
|
|
@ -24,25 +24,61 @@ struct ThirdPartySipAccountLoginFragment: View {
|
|||
@ObservedObject private var coreContext = CoreContext.shared
|
||||
@ObservedObject var accountLoginViewModel: AccountLoginViewModel
|
||||
|
||||
@StateObject private var keyboard = KeyboardResponder()
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@State private var isSecured: Bool = true
|
||||
@State private var advancedSettingsIsOpen: Bool = false
|
||||
|
||||
@FocusState var isNameFocused: Bool
|
||||
@FocusState var isPasswordFocused: Bool
|
||||
@FocusState var isDomainFocused: Bool
|
||||
@FocusState var isDisplayNameFocused: Bool
|
||||
@FocusState var isSipProxyUrlFocused: Bool
|
||||
@FocusState var isAuthIdFocused: Bool
|
||||
@FocusState var isOutboundProxyFocused: Bool
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
ScrollViewReader { proxy in
|
||||
if #available(iOS 16.4, *) {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
.onChange(of: isAuthIdFocused) { field in
|
||||
if field {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
proxy.scrollTo(2, anchor: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: isOutboundProxyFocused) { field in
|
||||
if field {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
proxy.scrollTo(2, anchor: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ScrollView(.vertical) {
|
||||
innerScrollView(geometry: geometry)
|
||||
}
|
||||
.onChange(of: isAuthIdFocused) { field in
|
||||
if field {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
proxy.scrollTo(2, anchor: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: isOutboundProxyFocused) { field in
|
||||
if field {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
proxy.scrollTo(2, anchor: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -208,6 +244,74 @@ struct ThirdPartySipAccountLoginFragment: View {
|
|||
.stroke(Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom)
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("settings_advanced_title")
|
||||
.default_text_style_800(styleSize: 18)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(advancedSettingsIsOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 10)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
advancedSettingsIsOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if advancedSettingsIsOpen {
|
||||
VStack(alignment: .leading) {
|
||||
Text("authentication_id")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("authentication_id", text: $accountLoginViewModel.authId)
|
||||
.id(1)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.background(.white)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isAuthIdFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isAuthIdFocused)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("account_settings_sip_proxy_url_title")
|
||||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
TextField("account_settings_sip_proxy_url_title", text: $accountLoginViewModel.outboundProxy)
|
||||
.id(2)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
.background(.white)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isOutboundProxyFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
|
||||
)
|
||||
.focused($isOutboundProxyFocused)
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: SharedMainViewModel.shared.maxWidth)
|
||||
.padding(.horizontal, 20)
|
||||
|
|
@ -241,6 +345,7 @@ struct ThirdPartySipAccountLoginFragment: View {
|
|||
.clipped()
|
||||
}
|
||||
.frame(minHeight: geometry.size.height)
|
||||
.padding(.bottom, keyboard.currentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ class AccountLoginViewModel: ObservableObject {
|
|||
@Published var domain: String = "sip.linphone.org"
|
||||
@Published var displayName: String = ""
|
||||
@Published var transportType: String = "TLS"
|
||||
@Published var authId: String = ""
|
||||
@Published var outboundProxy: String = ""
|
||||
|
||||
private var mCoreDelegate: CoreDelegate!
|
||||
|
||||
|
|
@ -83,7 +85,7 @@ class AccountLoginViewModel: ObservableObject {
|
|||
// The realm will be determined automatically from the first register, as well as the algorithm
|
||||
let authInfo = try Factory.Instance.createAuthInfo(
|
||||
username: self.username,
|
||||
userid: "",
|
||||
userid: self.authId,
|
||||
passwd: self.passwd,
|
||||
ha1: "",
|
||||
realm: "",
|
||||
|
|
@ -100,15 +102,26 @@ class AccountLoginViewModel: ObservableObject {
|
|||
try accountParams.setIdentityaddress(newValue: identity)
|
||||
|
||||
// We also need to configure where the proxy server is located
|
||||
let address = try Factory.Instance.createAddress(addr: String("sip:" + self.domain))
|
||||
var serverAddress: Address
|
||||
if (!self.outboundProxy.isEmpty) {
|
||||
let server = self.outboundProxy.starts(with: "sip:") ? self.outboundProxy : String("sip:" + self.outboundProxy)
|
||||
serverAddress = try Factory.Instance.createAddress(addr: server)
|
||||
} else {
|
||||
serverAddress = try Factory.Instance.createAddress(addr: String("sip:" + self.domain))
|
||||
}
|
||||
|
||||
let address = serverAddress
|
||||
|
||||
// We use the Address object to easily set the transport protocol
|
||||
try address.setTransport(newValue: transport)
|
||||
try accountParams.setServeraddress(newValue: address)
|
||||
// And we ensure the account will start the registration process
|
||||
accountParams.registerEnabled = true
|
||||
accountParams.pushNotificationAllowed = true
|
||||
accountParams.remotePushNotificationAllowed = true
|
||||
|
||||
if accountParams.pushNotificationAllowed {
|
||||
accountParams.pushNotificationAllowed = true
|
||||
accountParams.remotePushNotificationAllowed = true
|
||||
}
|
||||
#if DEBUG
|
||||
let pushEnvironment = ".dev"
|
||||
#else
|
||||
|
|
@ -116,10 +129,6 @@ class AccountLoginViewModel: ObservableObject {
|
|||
#endif
|
||||
accountParams.pushNotificationConfig?.provider = "apns" + pushEnvironment
|
||||
|
||||
accountParams.internationalPrefix = "33"
|
||||
accountParams.internationalPrefixIsoCountryCode = "FRA"
|
||||
accountParams.useInternationalPrefixForCallsAndChats = true
|
||||
|
||||
self.mCoreDelegate = CoreDelegateStub(onAccountRegistrationStateChanged: { (core: Core, account: Account, state: RegistrationState, message: String) in
|
||||
|
||||
Log.info("New registration state is \(state) for user id " +
|
||||
|
|
@ -153,6 +162,8 @@ class AccountLoginViewModel: ObservableObject {
|
|||
DispatchQueue.main.async {
|
||||
self.domain = "sip.linphone.org"
|
||||
self.transportType = "TLS"
|
||||
self.authId = ""
|
||||
self.outboundProxy = ""
|
||||
}
|
||||
|
||||
} catch { NSLog(error.localizedDescription) }
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class RegisterViewModel: ObservableObject {
|
|||
@Published var displayName: String = ""
|
||||
@Published var transportType: String = "TLS"
|
||||
|
||||
@Published var dialPlanValueSelected: String = "🇫🇷 +33"
|
||||
@Published var dialPlanValueSelected: String = "---"
|
||||
|
||||
private let HASHALGORITHM = "SHA-256"
|
||||
|
||||
|
|
@ -257,7 +257,7 @@ class RegisterViewModel: ObservableObject {
|
|||
|
||||
SharedMainViewModel.shared.dialPlansList.forEach { dial in
|
||||
let countryCode = dialPlanValueSelected.components(separatedBy: "+")
|
||||
if dial.countryCallingCode == countryCode[1] {
|
||||
if dial?.countryCallingCode == countryCode[1] {
|
||||
dialPlan = dial
|
||||
}
|
||||
}
|
||||
|
|
@ -412,7 +412,7 @@ class RegisterViewModel: ObservableObject {
|
|||
|
||||
for dial in SharedMainViewModel.shared.dialPlansList {
|
||||
let countryCode = self.dialPlanValueSelected.components(separatedBy: "+")
|
||||
if dial.countryCallingCode == countryCode[1] {
|
||||
if dial?.countryCallingCode == countryCode[1] {
|
||||
dialPlan = dial
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ struct CallView: View {
|
|||
.padding(.leading, 50)
|
||||
.padding(.top, 35)
|
||||
|
||||
Text("call_zrtp_end_to_end_encrypted")
|
||||
Text(callViewModel.isConference ? "call_srtp_point_to_point_encrypted" : "call_zrtp_end_to_end_encrypted")
|
||||
.foregroundStyle(Color.blueInfo500)
|
||||
.default_text_style_white(styleSize: 12)
|
||||
.padding(.top, 35)
|
||||
|
|
@ -526,8 +526,10 @@ struct CallView: View {
|
|||
.padding(.top)
|
||||
.default_text_style_white(styleSize: 22)
|
||||
|
||||
Text(callViewModel.remoteAddressCleanedString)
|
||||
.default_text_style_white_300(styleSize: 16)
|
||||
if !CorePreferences.hideSipAddresses {
|
||||
Text(callViewModel.remoteAddressCleanedString)
|
||||
.default_text_style_white_300(styleSize: 16)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
@ -558,6 +560,10 @@ struct CallView: View {
|
|||
}
|
||||
}
|
||||
.onDisappear {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativeVideoWindow = nil
|
||||
}
|
||||
|
||||
if callViewModel.videoDisplayed {
|
||||
if !callViewModel.isPaused && TelecomManager.shared.callInProgress
|
||||
&& !(coreContext.pipViewModel.pipController?.isPictureInPictureActive ?? false) {
|
||||
|
|
@ -585,6 +591,11 @@ struct CallView: View {
|
|||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = nil
|
||||
}
|
||||
}
|
||||
.aspectRatio(callViewModel.callStatsModel.sentVideoWindow.widthFactor/callViewModel.callStatsModel.sentVideoWindow.heightFactor, contentMode: .fill)
|
||||
.frame(maxWidth: callViewModel.callStatsModel.sentVideoWindow.widthFactor * 256,
|
||||
maxHeight: callViewModel.callStatsModel.sentVideoWindow.heightFactor * 256)
|
||||
|
|
@ -703,6 +714,11 @@ struct CallView: View {
|
|||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = nil
|
||||
}
|
||||
}
|
||||
.aspectRatio(callViewModel.callStatsModel.sentVideoWindow.widthFactor/callViewModel.callStatsModel.sentVideoWindow.heightFactor, contentMode: .fill)
|
||||
.frame(maxWidth: callViewModel.callStatsModel.sentVideoWindow.widthFactor * 256,
|
||||
maxHeight: callViewModel.callStatsModel.sentVideoWindow.heightFactor * 256)
|
||||
|
|
@ -897,6 +913,9 @@ struct CallView: View {
|
|||
}
|
||||
}
|
||||
.onDisappear {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativeVideoWindow = nil
|
||||
}
|
||||
if !callViewModel.isPaused && TelecomManager.shared.callInProgress
|
||||
&& !(coreContext.pipViewModel.pipController?.isPictureInPictureActive ?? false) {
|
||||
// TODO: Enable PIP in 6.1
|
||||
|
|
@ -978,6 +997,11 @@ struct CallView: View {
|
|||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = nil
|
||||
}
|
||||
}
|
||||
.frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2)
|
||||
.scaledToFill()
|
||||
.clipped()
|
||||
|
|
@ -1143,6 +1167,11 @@ struct CallView: View {
|
|||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = nil
|
||||
}
|
||||
}
|
||||
.frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2)
|
||||
.scaledToFill()
|
||||
.clipped()
|
||||
|
|
@ -1362,6 +1391,11 @@ struct CallView: View {
|
|||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = nil
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
width: 120 * ceil(maxValue / 120),
|
||||
height: 160 * ceil(maxValue / 120)
|
||||
|
|
@ -1598,6 +1632,11 @@ struct CallView: View {
|
|||
core.nativePreviewWindow = view
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
core.nativePreviewWindow = nil
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
width: 160 * ceil(maxValue / 120),
|
||||
height: 120 * ceil(maxValue / 120)
|
||||
|
|
@ -1917,36 +1956,38 @@ struct CallView: View {
|
|||
|
||||
Spacer()
|
||||
|
||||
ZStack {
|
||||
Button {
|
||||
if optionsChangeLayout == 3 {
|
||||
optionsChangeLayout = 2
|
||||
callViewModel.toggleVideoMode(isAudioOnlyMode: false)
|
||||
} else {
|
||||
callViewModel.displayMyVideo()
|
||||
if !SharedMainViewModel.shared.disableVideoCall {
|
||||
ZStack {
|
||||
Button {
|
||||
if optionsChangeLayout == 3 {
|
||||
optionsChangeLayout = 2
|
||||
callViewModel.toggleVideoMode(isAudioOnlyMode: false)
|
||||
} else {
|
||||
callViewModel.displayMyVideo()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(callViewModel.videoDisplayed ? "video-camera" : "video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(callViewModel.videoDisplayed ? "video-camera" : "video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: buttonSize))
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote || telecomManager.outgoingCallStarted || optionsChangeLayout == 3)
|
||||
|
||||
if callViewModel.isPaused || telecomManager.isPausedByRemote || telecomManager.outgoingCallStarted || optionsChangeLayout == 3 {
|
||||
Color.gray600.opacity(0.8)
|
||||
.cornerRadius(40)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: buttonSize))
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
|
||||
|
||||
if callViewModel.isPaused || telecomManager.isPausedByRemote {
|
||||
Color.gray600.opacity(0.8)
|
||||
.cornerRadius(40)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
|
||||
Button {
|
||||
callViewModel.toggleMuteMicrophone()
|
||||
|
|
@ -2219,7 +2260,7 @@ struct CallView: View {
|
|||
changeLayoutSheet = true
|
||||
} label: {
|
||||
HStack {
|
||||
Image("notebook")
|
||||
Image("layout")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
|
|
@ -2648,7 +2689,7 @@ struct CallView: View {
|
|||
changeLayoutSheet = true
|
||||
} label: {
|
||||
HStack {
|
||||
Image("notebook")
|
||||
Image("layout")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
|
|
|
|||
|
|
@ -103,17 +103,21 @@ struct CallsListFragment: View {
|
|||
.background(.white)
|
||||
|
||||
if self.isShowPopup {
|
||||
PopupView(isShowPopup: $isShowPopup,
|
||||
title: Text("calls_list_dialog_merge_into_conference_title"),
|
||||
content: nil,
|
||||
titleFirstButton: Text("dialog_cancel"),
|
||||
actionFirstButton: {self.isShowPopup.toggle()},
|
||||
titleSecondButton: Text("calls_list_dialog_merge_into_conference_label"),
|
||||
actionSecondButton: {
|
||||
callViewModel.mergeCallsIntoConference()
|
||||
self.isShowPopup.toggle()
|
||||
isShowCallsListFragment.toggle()
|
||||
})
|
||||
PopupView(
|
||||
isShowPopup: $isShowPopup,
|
||||
title: Text("calls_list_dialog_merge_into_conference_title"),
|
||||
content: nil,
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("calls_list_dialog_merge_into_conference_label"),
|
||||
actionSecondButton: {
|
||||
callViewModel.mergeCallsIntoConference()
|
||||
self.isShowPopup.toggle()
|
||||
isShowCallsListFragment.toggle()
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: { self.isShowPopup.toggle() },
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.onTapGesture {
|
||||
self.isShowPopup.toggle()
|
||||
|
|
|
|||
|
|
@ -108,17 +108,21 @@ struct ParticipantsListFragment: View {
|
|||
|
||||
if self.isShowPopup {
|
||||
let contentPopup = Text(String(format: String(localized: "meeting_call_remove_participant_confirmation_message"), callViewModel.participantList[indexToRemove].name))
|
||||
PopupView(isShowPopup: $isShowPopup,
|
||||
title: Text("meeting_call_remove_participant_confirmation_title"),
|
||||
content: contentPopup,
|
||||
titleFirstButton: Text("dialog_no"),
|
||||
actionFirstButton: {self.isShowPopup.toggle()},
|
||||
titleSecondButton: Text("dialog_yes"),
|
||||
actionSecondButton: {
|
||||
callViewModel.removeParticipant(index: indexToRemove)
|
||||
self.isShowPopup.toggle()
|
||||
indexToRemove = -1
|
||||
})
|
||||
PopupView(
|
||||
isShowPopup: $isShowPopup,
|
||||
title: Text("meeting_call_remove_participant_confirmation_title"),
|
||||
content: contentPopup,
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_yes"),
|
||||
actionSecondButton: {
|
||||
callViewModel.removeParticipant(index: indexToRemove)
|
||||
self.isShowPopup.toggle()
|
||||
indexToRemove = -1
|
||||
},
|
||||
titleThirdButton: Text("dialog_no"),
|
||||
actionThirdButton: { self.isShowPopup.toggle() }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.onTapGesture {
|
||||
self.isShowPopup.toggle()
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ struct MeetingWaitingRoomFragment: View {
|
|||
.sheet(isPresented: $audioRouteSheet, onDismiss: {
|
||||
audioRouteSheet = false
|
||||
}, content: {
|
||||
innerBottomSheet().presentationDetents([.fraction(0.3)])
|
||||
innerBottomSheet().presentationDetents([.fraction(0.4)])
|
||||
})
|
||||
.onAppear {
|
||||
meetingWaitingRoomViewModel.enableAVAudioSession()
|
||||
|
|
@ -266,23 +266,25 @@ struct MeetingWaitingRoomFragment: View {
|
|||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
!meetingWaitingRoomViewModel.videoDisplayed
|
||||
? meetingWaitingRoomViewModel.enableVideoPreview() : meetingWaitingRoomViewModel.disableVideoPreview()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(meetingWaitingRoomViewModel.videoDisplayed ? "video-camera" : "video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
if !SharedMainViewModel.shared.disableVideoCall {
|
||||
Button {
|
||||
!meetingWaitingRoomViewModel.videoDisplayed
|
||||
? meetingWaitingRoomViewModel.enableVideoPreview() : meetingWaitingRoomViewModel.disableVideoPreview()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(meetingWaitingRoomViewModel.videoDisplayed ? "video-camera" : "video-camera-slash")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: 60))
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal, 5)
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: 60))
|
||||
.frame(width: 60, height: 60)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
.padding(.horizontal, 5)
|
||||
|
||||
Button {
|
||||
meetingWaitingRoomViewModel.toggleMuteMicrophone()
|
||||
|
|
|
|||
|
|
@ -178,7 +178,9 @@ class CallViewModel: ObservableObject {
|
|||
let friend = ContactsManager.shared.getFriendWithAddress(address: self.currentCall!.remoteAddress)
|
||||
if friend != nil && friend!.address != nil && friend!.address!.displayName != nil {
|
||||
displayNameTmp = friend!.address!.displayName!
|
||||
} else {
|
||||
} else if friend != nil && friend?.name != nil {
|
||||
displayNameTmp = friend?.name ?? "No name"
|
||||
} else {
|
||||
if self.currentCall!.remoteAddress!.displayName != nil {
|
||||
displayNameTmp = self.currentCall!.remoteAddress!.displayName!
|
||||
} else if self.currentCall!.remoteAddress!.username != nil && displayNameTmp.isEmpty {
|
||||
|
|
@ -292,6 +294,10 @@ class CallViewModel: ObservableObject {
|
|||
self.zrtpPopupDisplayed = true
|
||||
}
|
||||
}
|
||||
}, onStateChanged: { (_: Call, state: Call.State, message: String) in
|
||||
if let currentParamsTmp = self.currentCall?.currentParams, state == .StreamsRunning, currentParamsTmp.mediaEncryption == .None || currentParamsTmp.mediaEncryption == .SRTP {
|
||||
self.updateEncryption(withToast: false)
|
||||
}
|
||||
}, onStatsUpdated: { (_: Call, stats: CallStats) in
|
||||
DispatchQueue.main.async {
|
||||
if self.currentCall != nil {
|
||||
|
|
@ -713,44 +719,45 @@ class CallViewModel: ObservableObject {
|
|||
|
||||
func displayMyVideo() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if self.currentCall != nil {
|
||||
do {
|
||||
let params = try core.createCallParams(call: self.currentCall)
|
||||
|
||||
if (params.videoEnabled == false) {
|
||||
Log.info("\(CallViewModel.TAG) Conference found and video disabled in params, enabling it")
|
||||
params.videoEnabled = true
|
||||
params.videoDirection = MediaDirection.SendRecv
|
||||
guard let call = self.currentCall else { return }
|
||||
|
||||
guard call.state == .StreamsRunning else {
|
||||
Log.warn("\(CallViewModel.TAG) displayMyVideo called in invalid state: \(call.state), skipping update")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let params = try core.createCallParams(call: call)
|
||||
|
||||
if !params.videoEnabled {
|
||||
Log.info("\(CallViewModel.TAG) Video disabled in params, enabling it")
|
||||
params.videoEnabled = true
|
||||
params.videoDirection = .SendRecv
|
||||
} else {
|
||||
if params.videoDirection == .SendRecv || params.videoDirection == .SendOnly {
|
||||
Log.info("\(CallViewModel.TAG) Video already enabled, switching to recv only")
|
||||
params.videoDirection = .RecvOnly
|
||||
} else {
|
||||
if (params.videoDirection == MediaDirection.SendRecv || params.videoDirection == MediaDirection.SendOnly) {
|
||||
Log.info(
|
||||
"\(CallViewModel.TAG) Conference found with video already enabled, changing video media direction to receive only"
|
||||
)
|
||||
params.videoDirection = MediaDirection.RecvOnly
|
||||
} else {
|
||||
Log.info(
|
||||
"\(CallViewModel.TAG) Conference found with video already enabled, changing video media direction to send & receive"
|
||||
)
|
||||
params.videoDirection = MediaDirection.SendRecv
|
||||
}
|
||||
Log.info("\(CallViewModel.TAG) Video already enabled, switching to send & recv")
|
||||
params.videoDirection = .SendRecv
|
||||
}
|
||||
|
||||
try self.currentCall!.update(params: params)
|
||||
|
||||
let video = params.videoDirection == .SendRecv || params.videoDirection == .SendOnly
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + (video ? 1 : 0)) {
|
||||
if video {
|
||||
self.videoDisplayed = false
|
||||
}
|
||||
self.videoDisplayed = video
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
|
||||
try call.update(params: params)
|
||||
|
||||
let video = params.videoDirection == .SendRecv || params.videoDirection == .SendOnly
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + (video ? 1 : 0)) {
|
||||
if video {
|
||||
self.videoDisplayed = false
|
||||
}
|
||||
self.videoDisplayed = video
|
||||
}
|
||||
} catch {
|
||||
Log.error("\(CallViewModel.TAG) Failed to update video params: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func toggleVideoMode(isAudioOnlyMode: Bool) {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
|
|
@ -985,14 +992,11 @@ class CallViewModel: ObservableObject {
|
|||
self.isNotEncrypted = false
|
||||
}
|
||||
case MediaEncryption.None:
|
||||
let isNotEncryptedTmp = self.currentCall?.state == .StreamsRunning && !self.telecomManager.outgoingCallStarted
|
||||
DispatchQueue.main.async {
|
||||
self.isMediaEncrypted = false
|
||||
self.isZrtp = false
|
||||
if self.currentCall!.state == .StreamsRunning {
|
||||
self.isNotEncrypted = true
|
||||
} else {
|
||||
self.isNotEncrypted = false
|
||||
}
|
||||
self.isNotEncrypted = isNotEncryptedTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ class MeetingWaitingRoomViewModel: ObservableObject {
|
|||
let confNameTmp = conf?.subject ?? "Conference"
|
||||
var userNameTmp = ""
|
||||
|
||||
let friend = core.defaultAccount != nil && core.defaultAccount!.contactAddress != nil
|
||||
? ContactsManager.shared.getFriendWithAddress(address: core.defaultAccount?.contactAddress)
|
||||
let friend = core.defaultAccount != nil && core.defaultAccount!.params?.identityAddress != nil
|
||||
? ContactsManager.shared.getFriendWithAddress(address: core.defaultAccount?.params?.identityAddress)
|
||||
: nil
|
||||
|
||||
let addressTmp = friend?.address?.asStringUriOnly() ?? ""
|
||||
|
|
@ -78,13 +78,13 @@ class MeetingWaitingRoomViewModel: ObservableObject {
|
|||
if friend != nil && friend!.address != nil && friend!.address!.displayName != nil {
|
||||
userNameTmp = friend!.address!.displayName!
|
||||
} else {
|
||||
if core.defaultAccount != nil && core.defaultAccount!.contactAddress != nil {
|
||||
if core.defaultAccount!.contactAddress!.displayName != nil {
|
||||
userNameTmp = core.defaultAccount!.contactAddress!.displayName!
|
||||
} else if core.defaultAccount!.contactAddress!.username != nil {
|
||||
userNameTmp = core.defaultAccount!.contactAddress!.username!
|
||||
if core.defaultAccount != nil && core.defaultAccount!.params?.identityAddress != nil {
|
||||
if core.defaultAccount!.params?.identityAddress!.displayName != nil {
|
||||
userNameTmp = core.defaultAccount!.params!.identityAddress!.displayName!
|
||||
} else if core.defaultAccount!.params?.identityAddress!.username != nil {
|
||||
userNameTmp = core.defaultAccount!.params!.identityAddress!.username!
|
||||
} else {
|
||||
userNameTmp = String(core.defaultAccount!.contactAddress!.asStringUriOnly().dropFirst(4))
|
||||
userNameTmp = String(core.defaultAccount!.params!.identityAddress!.asStringUriOnly().dropFirst(4))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ class MeetingWaitingRoomViewModel: ObservableObject {
|
|||
let avatarModelTmp = friend != nil
|
||||
? ContactsManager.shared.avatarListModel.first(where: {
|
||||
$0.friend!.name == friend!.name
|
||||
&& $0.friend!.address!.asStringUriOnly() == core.defaultAccount!.contactAddress!.asStringUriOnly()
|
||||
&& $0.friend!.address!.asStringUriOnly() == core.defaultAccount!.params?.identityAddress!.asStringUriOnly()
|
||||
}) ?? ContactAvatarModel(friend: nil, name: userNameTmp, address: addressTmp, withPresence: false)
|
||||
: ContactAvatarModel(friend: nil, name: userNameTmp, address: addressTmp, withPresence: false)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,22 +32,24 @@ struct ContactsView: View {
|
|||
ZStack(alignment: .bottomTrailing) {
|
||||
ContactsFragment(isShowDeletePopup: $isShowDeletePopup, text: $text)
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
contactsListViewModel.selectedEditFriend = nil
|
||||
isShowEditContactFragment.toggle()
|
||||
if !CorePreferences.disableAddContact {
|
||||
Button {
|
||||
withAnimation {
|
||||
contactsListViewModel.selectedEditFriend = nil
|
||||
isShowEditContactFragment.toggle()
|
||||
}
|
||||
} label: {
|
||||
Image("user-plus")
|
||||
.renderingMode(.template)
|
||||
.foregroundStyle(.white)
|
||||
.padding()
|
||||
.background(Color.orangeMain500)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
|
||||
}
|
||||
} label: {
|
||||
Image("user-plus")
|
||||
.renderingMode(.template)
|
||||
.foregroundStyle(.white)
|
||||
.padding()
|
||||
.background(Color.orangeMain500)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4)
|
||||
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
|
||||
// For testing crashlytics
|
||||
/*Button(action: CoreContext.shared.crashForCrashlytics, label: {
|
||||
|
|
|
|||
|
|
@ -39,127 +39,152 @@ struct ContactInnerActionsFragment: View {
|
|||
|
||||
var actionEditButton: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
Text("contact_details_numbers_and_addresses_title")
|
||||
.default_text_style_800(styleSize: 15)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(informationIsOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.bottom, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color.gray100)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
informationIsOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if informationIsOpen {
|
||||
VStack(spacing: 0) {
|
||||
ForEach(0..<contactAvatarModel.addresses.count, id: \.self) { index in
|
||||
HStack {
|
||||
HStack {
|
||||
VStack {
|
||||
Text(String(localized: "sip_address") + ":")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text(contactAvatarModel.addresses[index].dropFirst(4))
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
do {
|
||||
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.addresses[index])
|
||||
withAnimation {
|
||||
telecomManager.doCallOrJoinConf(address: address)
|
||||
}
|
||||
} catch {
|
||||
Log.error("[ContactInnerActionsFragment] unable to create address for a new outgoing call : \(contactAvatarModel.addresses[index]) \(error) ")
|
||||
}
|
||||
}
|
||||
.onLongPressGesture(minimumDuration: 0.2) {
|
||||
contactsListViewModel.stringToCopy = contactAvatarModel.addresses[index]
|
||||
showingSheet.toggle()
|
||||
}
|
||||
|
||||
if !contactAvatarModel.phoneNumbersWithLabel.isEmpty
|
||||
|| index < contactAvatarModel.addresses.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
var body: some View {
|
||||
if !CorePreferences.hideSipAddresses || (CorePreferences.hideSipAddresses && !contactAvatarModel.phoneNumbersWithLabel.isEmpty) {
|
||||
HStack(alignment: .center) {
|
||||
Text("contact_details_numbers_and_addresses_title")
|
||||
.default_text_style_800(styleSize: 15)
|
||||
|
||||
ForEach(contactAvatarModel.phoneNumbersWithLabel.indices, id: \.self) { index in
|
||||
let entry = contactAvatarModel.phoneNumbersWithLabel[index]
|
||||
HStack {
|
||||
HStack {
|
||||
VStack {
|
||||
if !entry.label.isEmpty {
|
||||
Text(String(localized: "phone_number") + " (\(entry.label)):")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
Text(String(localized: "phone_number") + ":")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Spacer()
|
||||
|
||||
Image(informationIsOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.bottom, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color.gray100)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
informationIsOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if informationIsOpen {
|
||||
VStack(spacing: 0) {
|
||||
if !CorePreferences.hideSipAddresses {
|
||||
ForEach(0..<contactAvatarModel.addresses.count, id: \.self) { index in
|
||||
HStack {
|
||||
HStack {
|
||||
VStack {
|
||||
Text(String(localized: "sip_address") + ":")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text(contactAvatarModel.addresses[index].dropFirst(4))
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
Text(entry.phoneNumber)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
do {
|
||||
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.addresses[index])
|
||||
telecomManager.doCallOrJoinConf(address: address)
|
||||
} catch {
|
||||
Log.error("[ContactInnerActionsFragment] unable to create address for a new outgoing call : \(contactAvatarModel.addresses[index]) \(error) ")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onLongPressGesture(minimumDuration: 0.2) {
|
||||
contactsListViewModel.stringToCopy = contactAvatarModel.addresses[index]
|
||||
showingSheet.toggle()
|
||||
}
|
||||
|
||||
if !contactAvatarModel.phoneNumbersWithLabel.isEmpty
|
||||
|| index < contactAvatarModel.addresses.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.background(.white)
|
||||
.onLongPressGesture(minimumDuration: 0.2) {
|
||||
contactsListViewModel.stringToCopy = entry.phoneNumber
|
||||
showingSheet.toggle()
|
||||
}
|
||||
|
||||
if index < contactAvatarModel.phoneNumbersWithLabel.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
ForEach(contactAvatarModel.phoneNumbersWithLabel.indices, id: \.self) { index in
|
||||
let entry = contactAvatarModel.phoneNumbersWithLabel[index]
|
||||
HStack {
|
||||
HStack {
|
||||
VStack {
|
||||
if !entry.label.isEmpty {
|
||||
Text(String(localized: "phone_number") + " (\(entry.label)):")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
Text(String(localized: "phone_number") + ":")
|
||||
.default_text_style_700(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
Text(entry.phoneNumber)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
let address = core.interpretUrl(url: contactAvatarModel.phoneNumbersWithLabel[index].phoneNumber, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core))
|
||||
if address != nil {
|
||||
TelecomManager.shared.doCallOrJoinConf(address: address!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onLongPressGesture(minimumDuration: 0.2) {
|
||||
contactsListViewModel.stringToCopy = entry.phoneNumber
|
||||
showingSheet.toggle()
|
||||
}
|
||||
|
||||
if index < contactAvatarModel.phoneNumbersWithLabel.count - 1 {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
} else {
|
||||
HStack {}
|
||||
.frame(height: 20)
|
||||
}
|
||||
.background(.white)
|
||||
.cornerRadius(15)
|
||||
.padding(.horizontal)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
if !contactAvatarModel.organization.isEmpty || !contactAvatarModel.jobTitle.isEmpty {
|
||||
VStack {
|
||||
|
|
@ -203,33 +228,11 @@ struct ContactInnerActionsFragment: View {
|
|||
.background(Color.gray100)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
if !contactAvatarModel.nativeUri.isEmpty {
|
||||
Button {
|
||||
actionEditButton()
|
||||
} label: {
|
||||
HStack {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text("contact_details_edit")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
} else {
|
||||
NavigationLink(destination: EditContactFragment(
|
||||
contactAvatarModel: contactAvatarModel,
|
||||
isShowEditContactFragment: $isShowEditContactFragmentInContactDetails,
|
||||
isShowDismissPopup: $isShowDismissPopup)) {
|
||||
if !contactAvatarModel.isReadOnly {
|
||||
if !contactAvatarModel.editable {
|
||||
Button {
|
||||
actionEditButton()
|
||||
} label: {
|
||||
HStack {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
|
|
@ -247,47 +250,71 @@ struct ContactInnerActionsFragment: View {
|
|||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
} else {
|
||||
NavigationLink(destination: EditContactFragment(
|
||||
contactAvatarModel: contactAvatarModel,
|
||||
isShowEditContactFragment: $isShowEditContactFragmentInContactDetails,
|
||||
isShowDismissPopup: $isShowDismissPopup)) {
|
||||
HStack {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text("contact_details_edit")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
isShowEditContactFragmentInContactDetails = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
contactsListViewModel.toggleStarredSelectedFriend()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(contactAvatarModel.starred == true ? "heart-fill" : "heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(contactAvatarModel.starred == true ? Color.redDanger500 : Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
Text(contactAvatarModel.starred == true
|
||||
? "contact_details_remove_from_favourites"
|
||||
: "contact_details_add_to_favourites")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
contactsListViewModel.toggleStarredSelectedFriend()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(contactAvatarModel.starred == true ? "heart-fill" : "heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(contactAvatarModel.starred == true ? Color.redDanger500 : Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
Text(contactAvatarModel.starred == true
|
||||
? "contact_details_remove_from_favourites"
|
||||
: "contact_details_add_to_favourites")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
showShareSheet.toggle()
|
||||
} label: {
|
||||
|
|
@ -310,32 +337,34 @@ struct ContactInnerActionsFragment: View {
|
|||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
isShowDeletePopup.toggle()
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text("contact_details_delete")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
if !contactAvatarModel.isReadOnly {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Button {
|
||||
isShowDeletePopup.toggle()
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text("contact_details_delete")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.padding(.vertical, 15)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
.background(.white)
|
||||
|
|
|
|||
|
|
@ -72,23 +72,11 @@ struct ContactInnerFragment: View {
|
|||
|
||||
Spacer()
|
||||
|
||||
if !contactAvatarModel.nativeUri.isEmpty {
|
||||
Button(action: {
|
||||
editNativeContact()
|
||||
}, label: {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, 2)
|
||||
})
|
||||
} else {
|
||||
NavigationLink(destination: EditContactFragment(
|
||||
contactAvatarModel: contactAvatarModel,
|
||||
isShowEditContactFragment: $isShowEditContactFragmentInContactDetails,
|
||||
isShowDismissPopup: $isShowDismissPopup)) {
|
||||
if !contactAvatarModel.isReadOnly {
|
||||
if !contactAvatarModel.editable {
|
||||
Button(action: {
|
||||
editNativeContact()
|
||||
}, label: {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
|
|
@ -96,12 +84,26 @@ struct ContactInnerFragment: View {
|
|||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
isShowEditContactFragmentInContactDetails = true
|
||||
})
|
||||
} else {
|
||||
NavigationLink(destination: EditContactFragment(
|
||||
contactAvatarModel: contactAvatarModel,
|
||||
isShowEditContactFragment: $isShowEditContactFragmentInContactDetails,
|
||||
isShowDismissPopup: $isShowDismissPopup)) {
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
)
|
||||
.simultaneousGesture(
|
||||
TapGesture().onEnded {
|
||||
isShowEditContactFragmentInContactDetails = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
@ -142,16 +144,24 @@ struct ContactInnerFragment: View {
|
|||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
if contactAvatarModel.addresses.count <= 1 {
|
||||
do {
|
||||
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address)
|
||||
telecomManager.doCallOrJoinConf(address: address, isVideo: false)
|
||||
} catch {
|
||||
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
if contactAvatarModel.addresses.count == 1 {
|
||||
do {
|
||||
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address)
|
||||
telecomManager.doCallOrJoinConf(address: address, isVideo: false)
|
||||
} catch {
|
||||
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
|
||||
}
|
||||
} else if contactAvatarModel.addresses.count < 1 && contactAvatarModel.phoneNumbersWithLabel.count == 1 {
|
||||
if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) {
|
||||
telecomManager.doCallOrJoinConf(address: address, isVideo: false)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
isShowSipAddressesPopupType = 0
|
||||
isShowSipAddressesPopup = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isShowSipAddressesPopupType = 0
|
||||
isShowSipAddressesPopup = true
|
||||
}
|
||||
}, label: {
|
||||
VStack {
|
||||
|
|
@ -159,88 +169,103 @@ struct ContactInnerFragment: View {
|
|||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(contactAvatarModel.address.isEmpty ? Color.grayMain2c400 : Color.grayMain2c600)
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
}
|
||||
.padding(16)
|
||||
.background(contactAvatarModel.address.isEmpty ? Color.grayMain2c100 : Color.grayMain2c200)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_call_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.disabled(contactAvatarModel.address.isEmpty)
|
||||
|
||||
if !CorePreferences.disableChatFeature {
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
if contactAvatarModel.addresses.count <= 1 {
|
||||
do {
|
||||
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address)
|
||||
contactsListViewModel.createOneToOneChatRoomWith(remote: address)
|
||||
} catch {
|
||||
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
|
||||
}
|
||||
} else {
|
||||
isShowSipAddressesPopupType = 1
|
||||
isShowSipAddressesPopup = true
|
||||
}
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
if contactAvatarModel.addresses.count == 1 {
|
||||
do {
|
||||
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address)
|
||||
contactsListViewModel.createOneToOneChatRoomWith(remote: address)
|
||||
} catch {
|
||||
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
|
||||
}
|
||||
} else if contactAvatarModel.addresses.count < 1 && contactAvatarModel.phoneNumbersWithLabel.count == 1 {
|
||||
if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) {
|
||||
contactsListViewModel.createOneToOneChatRoomWith(remote: address)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
isShowSipAddressesPopupType = 1
|
||||
isShowSipAddressesPopup = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(contactAvatarModel.address.isEmpty ? Color.grayMain2c400 : Color.grayMain2c600)
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
}
|
||||
.padding(16)
|
||||
.background(contactAvatarModel.address.isEmpty ? Color.grayMain2c100 : Color.grayMain2c200)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_message_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.disabled(contactAvatarModel.address.isEmpty)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
if contactAvatarModel.addresses.count <= 1 {
|
||||
do {
|
||||
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address)
|
||||
telecomManager.doCallOrJoinConf(address: address, isVideo: true)
|
||||
} catch {
|
||||
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
|
||||
if !SharedMainViewModel.shared.disableVideoCall {
|
||||
Button(action: {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
if contactAvatarModel.addresses.count == 1 {
|
||||
do {
|
||||
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address)
|
||||
telecomManager.doCallOrJoinConf(address: address, isVideo: true)
|
||||
} catch {
|
||||
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
|
||||
}
|
||||
} else if contactAvatarModel.addresses.count < 1 && contactAvatarModel.phoneNumbersWithLabel.count == 1 {
|
||||
if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) {
|
||||
telecomManager.doCallOrJoinConf(address: address, isVideo: true)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
isShowSipAddressesPopupType = 2
|
||||
isShowSipAddressesPopup = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isShowSipAddressesPopupType = 2
|
||||
isShowSipAddressesPopup = true
|
||||
}
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(contactAvatarModel.address.isEmpty ? Color.grayMain2c400 : Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_video_call_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
.padding(16)
|
||||
.background(contactAvatarModel.address.isEmpty ? Color.grayMain2c100 : Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_video_call_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
})
|
||||
.disabled(contactAvatarModel.address.isEmpty)
|
||||
|
||||
Spacer()
|
||||
})
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.top, 20)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ struct ContactListBottomSheet: View {
|
|||
.padding(.trailing)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Button {
|
||||
UIPasteboard.general.setValue(
|
||||
contactsListViewModel.stringToCopy.prefix(4) == "sip:"
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ struct ContactsFragment: View {
|
|||
showingSheet: $showingSheet,
|
||||
showShareSheet: $showShareSheet
|
||||
)
|
||||
.presentationDetents([.fraction(0.3)])
|
||||
.presentationDetents(contactsListViewModel.selectedFriend?.isReadOnly == true ? [.fraction(0.1)] : [.fraction(0.4)])
|
||||
}
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(friendToShare: contactsListViewModel.selectedFriendToShare!)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ import linphonesw
|
|||
|
||||
struct ContactsInnerFragment: View {
|
||||
|
||||
@ObservedObject var sharedMainViewModel = SharedMainViewModel.shared
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@EnvironmentObject var contactsListViewModel: ContactsListViewModel
|
||||
|
||||
|
|
@ -32,71 +34,84 @@ struct ContactsInnerFragment: View {
|
|||
@Binding var text: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if contactsManager.avatarListModel.contains(where: { $0.starred }) {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_favourites_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(isFavoriteOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
isFavoriteOpen.toggle()
|
||||
ZStack {
|
||||
VStack(alignment: .leading) {
|
||||
if contactsManager.avatarListModel.contains(where: { $0.starred }) {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_favourites_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(isFavoriteOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
}
|
||||
|
||||
if isFavoriteOpen {
|
||||
FavoriteContactsListFragment(showingSheet: $showingSheet)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
VStack {
|
||||
List {
|
||||
ContactsListFragment(showingSheet: $showingSheet, startCallFunc: {_ in })}
|
||||
.safeAreaInset(edge: .top, content: {
|
||||
Spacer()
|
||||
.frame(height: 12)
|
||||
})
|
||||
.listStyle(.plain)
|
||||
.overlay(
|
||||
VStack {
|
||||
if contactsManager.avatarListModel.isEmpty {
|
||||
Spacer()
|
||||
Image("illus-belledonne")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipped()
|
||||
.padding(.all)
|
||||
Text(!text.isEmpty ? "list_filter_no_result_found" : "contacts_list_empty")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
Spacer()
|
||||
Spacer()
|
||||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
isFavoriteOpen.toggle()
|
||||
}
|
||||
}
|
||||
.padding(.all)
|
||||
)
|
||||
|
||||
if isFavoriteOpen {
|
||||
FavoriteContactsListFragment(showingSheet: $showingSheet)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
VStack {
|
||||
List {
|
||||
ContactsListFragment(showingSheet: $showingSheet, startCallFunc: {_ in })}
|
||||
.safeAreaInset(edge: .top, content: {
|
||||
Spacer()
|
||||
.frame(height: 12)
|
||||
})
|
||||
.listStyle(.plain)
|
||||
.if(sharedMainViewModel.cardDavFriendsListsCount > 0) { view in
|
||||
view.refreshable {
|
||||
contactsManager.refreshCardDavContacts()
|
||||
}
|
||||
}
|
||||
.overlay(
|
||||
VStack {
|
||||
if contactsManager.avatarListModel.isEmpty {
|
||||
Spacer()
|
||||
Image("illus-belledonne")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipped()
|
||||
.padding(.all)
|
||||
Text(!text.isEmpty ? "list_filter_no_result_found" : "contacts_list_empty")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.all)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if magicSearch.isLoading {
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
|
|
|
|||
|
|
@ -55,49 +55,51 @@ struct ContactsListBottomSheet: View {
|
|||
.padding(.trailing)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
self.contactsListViewModel.toggleStarredSelectedFriend()
|
||||
if !contactsListViewModel.selectedFriend!.isReadOnly {
|
||||
Spacer()
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
Button {
|
||||
self.contactsListViewModel.toggleStarredSelectedFriend()
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true ? "heart-fill" : "heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(
|
||||
contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true
|
||||
? Color.redDanger500
|
||||
: Color.grayMain2c500
|
||||
)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
Text(contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true
|
||||
? "contact_details_remove_from_favourites"
|
||||
: "contact_details_add_to_favourites")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true ? "heart-fill" : "heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(
|
||||
contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true
|
||||
? Color.redDanger500
|
||||
: Color.grayMain2c500
|
||||
)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
Text(contactsListViewModel.selectedFriend != nil && contactsListViewModel.selectedFriend!.starred == true
|
||||
? "contact_details_remove_from_favourites"
|
||||
: "contact_details_add_to_favourites")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if #available(iOS 16.0, *) {
|
||||
|
|
@ -135,45 +137,46 @@ struct ContactsListBottomSheet: View {
|
|||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if contactsListViewModel.selectedFriend != nil {
|
||||
isShowDeletePopup.toggle()
|
||||
if !contactsListViewModel.selectedFriend!.isReadOnly {
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
Button {
|
||||
if contactsListViewModel.selectedFriend != nil {
|
||||
isShowDeletePopup.toggle()
|
||||
}
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
Text("contact_details_delete")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
Text("contact_details_delete")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
}
|
||||
.background(Color.gray100)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ struct EditContactFragment: View {
|
|||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@StateObject private var editContactViewModel: EditContactViewModel
|
||||
@StateObject private var keyboard = KeyboardResponder()
|
||||
|
||||
@Binding var isShowEditContactFragment: Bool
|
||||
@Binding var isShowDismissPopup: Bool
|
||||
|
|
@ -100,8 +101,8 @@ struct EditContactFragment: View {
|
|||
if editContactViewModel.selectedEditFriend == nil
|
||||
&& editContactViewModel.firstName.isEmpty
|
||||
&& editContactViewModel.lastName.isEmpty
|
||||
&& editContactViewModel.sipAddresses.first!.isEmpty
|
||||
&& editContactViewModel.phoneNumbers.first!.isEmpty
|
||||
&& editContactViewModel.sipAddresses.first?.isEmpty ?? true
|
||||
&& editContactViewModel.phoneNumbers.first?.isEmpty ?? true
|
||||
&& editContactViewModel.company.isEmpty
|
||||
&& editContactViewModel.jobTitle.isEmpty {
|
||||
delayColorDismiss()
|
||||
|
|
@ -113,8 +114,8 @@ struct EditContactFragment: View {
|
|||
} else {
|
||||
if editContactViewModel.firstName.isEmpty
|
||||
&& editContactViewModel.lastName.isEmpty
|
||||
&& editContactViewModel.sipAddresses.first!.isEmpty
|
||||
&& editContactViewModel.phoneNumbers.first!.isEmpty
|
||||
&& editContactViewModel.sipAddresses.first?.isEmpty ?? true
|
||||
&& editContactViewModel.phoneNumbers.first?.isEmpty ?? true
|
||||
&& editContactViewModel.company.isEmpty
|
||||
&& editContactViewModel.jobTitle.isEmpty {
|
||||
withAnimation {
|
||||
|
|
@ -318,7 +319,6 @@ struct EditContactFragment: View {
|
|||
.padding(.bottom, -5)
|
||||
|
||||
ForEach(editContactViewModel.sipAddresses.indices, id: \.self) { index in
|
||||
|
||||
HStack(alignment: .center) {
|
||||
TextField("sip_address", text: $editContactViewModel.sipAddresses[index])
|
||||
.default_text_style(styleSize: 15)
|
||||
|
|
@ -336,27 +336,27 @@ struct EditContactFragment: View {
|
|||
)
|
||||
.focused($isSIPAddressFocused, equals: index)
|
||||
.onChange(of: editContactViewModel.sipAddresses[index]) { newValue in
|
||||
if !newValue.isEmpty && index + 1 == editContactViewModel.sipAddresses.count {
|
||||
if !newValue.isEmpty && index == editContactViewModel.sipAddresses.count - 1 {
|
||||
editContactViewModel.sipAddresses.append("")
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
guard editContactViewModel.sipAddresses.indices.contains(index) else { return }
|
||||
editContactViewModel.sipAddresses.remove(at: index)
|
||||
}, label: {
|
||||
}) {
|
||||
Image("x")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(
|
||||
editContactViewModel.sipAddresses[index].isEmpty && editContactViewModel.sipAddresses.count == index + 1
|
||||
editContactViewModel.sipAddresses[index].isEmpty && index == editContactViewModel.sipAddresses.count - 1
|
||||
? Color.gray100
|
||||
: Color.grayMain2c600
|
||||
)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
})
|
||||
.disabled(editContactViewModel.sipAddresses[index].isEmpty && editContactViewModel.sipAddresses.count == index + 1)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.disabled(editContactViewModel.sipAddresses[index].isEmpty && index == editContactViewModel.sipAddresses.count - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -367,12 +367,12 @@ struct EditContactFragment: View {
|
|||
.default_text_style_700(styleSize: 15)
|
||||
.padding(.bottom, -5)
|
||||
|
||||
ForEach(0..<editContactViewModel.phoneNumbers.count, id: \.self) { index in
|
||||
ForEach(editContactViewModel.phoneNumbers.indices, id: \.self) { index in
|
||||
HStack(alignment: .center) {
|
||||
TextField("phone_number", text: $editContactViewModel.phoneNumbers[index])
|
||||
.default_text_style(styleSize: 15)
|
||||
.textContentType(.oneTimeCode)
|
||||
.keyboardType(.numberPad)
|
||||
.keyboardType(.phonePad)
|
||||
.frame(height: 25)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 15)
|
||||
|
|
@ -385,7 +385,7 @@ struct EditContactFragment: View {
|
|||
)
|
||||
.focused($isPhoneNumberFocused, equals: index)
|
||||
.onChange(of: editContactViewModel.phoneNumbers[index]) { newValue in
|
||||
if !newValue.isEmpty && index + 1 == editContactViewModel.phoneNumbers.count {
|
||||
if !newValue.isEmpty && index == editContactViewModel.phoneNumbers.count - 1 {
|
||||
withAnimation {
|
||||
editContactViewModel.phoneNumbers.append("")
|
||||
}
|
||||
|
|
@ -393,21 +393,21 @@ struct EditContactFragment: View {
|
|||
}
|
||||
|
||||
Button(action: {
|
||||
guard editContactViewModel.phoneNumbers.indices.contains(index) else { return }
|
||||
editContactViewModel.phoneNumbers.remove(at: index)
|
||||
}, label: {
|
||||
}) {
|
||||
Image("x")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(
|
||||
editContactViewModel.phoneNumbers[index].isEmpty && editContactViewModel.phoneNumbers.count == index + 1
|
||||
editContactViewModel.phoneNumbers[index].isEmpty && index == editContactViewModel.phoneNumbers.count - 1
|
||||
? Color.gray100
|
||||
: Color.grayMain2c600
|
||||
)
|
||||
.frame(width: 25, height: 25)
|
||||
.padding(.all, 10)
|
||||
})
|
||||
.disabled(editContactViewModel.phoneNumbers[index].isEmpty && editContactViewModel.phoneNumbers.count == index + 1)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.disabled(editContactViewModel.phoneNumbers[index].isEmpty && index == editContactViewModel.phoneNumbers.count - 1)
|
||||
}
|
||||
.zIndex(isPhoneNumberFocused == index ? 1 : 0)
|
||||
.transition(.move(edge: .top))
|
||||
|
|
@ -510,83 +510,91 @@ struct EditContactFragment: View {
|
|||
organizationName: editContactViewModel.company,
|
||||
jobTitle: editContactViewModel.jobTitle,
|
||||
displayName: "",
|
||||
sipAddresses: editContactViewModel.sipAddresses.map { $0 },
|
||||
phoneNumbers: editContactViewModel.phoneNumbers.map { PhoneNumber(numLabel: "", num: $0)},
|
||||
sipAddresses: editContactViewModel.sipAddresses,
|
||||
phoneNumbers: editContactViewModel.phoneNumbers.map { PhoneNumber(numLabel: "", num: $0) },
|
||||
imageData: ""
|
||||
)
|
||||
|
||||
if editContactViewModel.selectedEditFriend != nil && editContactViewModel.selectedEditFriend!.friend != nil && selectedImage == nil &&
|
||||
!removedImage && editContactViewModel.selectedEditFriend!.friend!.photo!.suffix(11) != "default.png" {
|
||||
ContactsManager.shared.saveFriend(
|
||||
result: String(editContactViewModel.selectedEditFriend!.friend!.photo!.dropFirst(6)),
|
||||
contact: newContact,
|
||||
existingFriend: editContactViewModel.selectedEditFriend!.friend, completion: {_ in
|
||||
if let selectedFriendTmp = editContactViewModel.selectedEditFriend?.friend {
|
||||
let addressTmp = selectedFriendTmp.address?.clone()?.asStringUriOnly() ?? ""
|
||||
SharedMainViewModel.shared.displayedFriend?.resetContactAvatarModel(
|
||||
friend: selectedFriendTmp,
|
||||
name: selectedFriendTmp.name ?? "",
|
||||
address: addressTmp,
|
||||
withPresence: SharedMainViewModel.shared.displayedFriend?.withPresence
|
||||
)
|
||||
}
|
||||
let friendIsNil = editContactViewModel.selectedEditFriend?.friend == nil
|
||||
DispatchQueue.main.async {
|
||||
delayColorDismiss()
|
||||
if friendIsNil {
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
}
|
||||
} else {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
}
|
||||
)
|
||||
let existingFriend = editContactViewModel.selectedEditFriend?.friend
|
||||
let friendHasCustomPhoto = existingFriend?.photo?.suffix(11) != "default.png"
|
||||
|
||||
// Case: editing existing friend without changing the image
|
||||
if let existingFriend = existingFriend,
|
||||
selectedImage == nil,
|
||||
!removedImage,
|
||||
friendHasCustomPhoto,
|
||||
let photo = existingFriend.photo {
|
||||
|
||||
let resultPhoto = String(photo.dropFirst(6))
|
||||
ContactsManager.shared.saveFriend(result: resultPhoto, contact: newContact, existingFriend: existingFriend) { _ in
|
||||
self.updateAvatar(for: existingFriend)
|
||||
self.finishUIUpdate(existingFriend: existingFriend)
|
||||
}
|
||||
|
||||
} else {
|
||||
ContactsManager.shared.saveImage(
|
||||
image: selectedImage
|
||||
?? ContactsManager.shared.textToImage(
|
||||
firstName: editContactViewModel.firstName, lastName: editContactViewModel.lastName),
|
||||
name: editContactViewModel.firstName
|
||||
+ editContactViewModel.lastName,
|
||||
prefix: ((selectedImage == nil) ? "-default" : ""),
|
||||
contact: newContact, linphoneFriend: "Linphone address-book", existingFriend: editContactViewModel.selectedEditFriend?.friend) {
|
||||
if let selectedFriendTmp = editContactViewModel.selectedEditFriend?.friend {
|
||||
let addressTmp = selectedFriendTmp.address?.clone()?.asStringUriOnly() ?? ""
|
||||
SharedMainViewModel.shared.displayedFriend?.resetContactAvatarModel(
|
||||
friend: selectedFriendTmp,
|
||||
name: selectedFriendTmp.name ?? "",
|
||||
address: addressTmp,
|
||||
withPresence: SharedMainViewModel.shared.displayedFriend?.withPresence
|
||||
)
|
||||
} else {
|
||||
MagicSearchSingleton.shared.searchForContacts()
|
||||
ContactsManager.shared.updateSubscriptionsLinphoneList()
|
||||
}
|
||||
|
||||
let friendIsNil = editContactViewModel.selectedEditFriend?.friend == nil
|
||||
|
||||
DispatchQueue.main.async {
|
||||
delayColorDismiss()
|
||||
if friendIsNil {
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
}
|
||||
} else {
|
||||
withAnimation {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
}
|
||||
// Case: creating new friend or updating with a new image
|
||||
let imageToSave = selectedImage ?? ContactsManager.shared.textToImage(
|
||||
firstName: editContactViewModel.firstName,
|
||||
lastName: editContactViewModel.lastName
|
||||
)
|
||||
let prefix = selectedImage == nil ? "-default" : ""
|
||||
|
||||
saveImageThreadSafe(
|
||||
image: imageToSave,
|
||||
name: editContactViewModel.firstName + editContactViewModel.lastName,
|
||||
prefix: prefix,
|
||||
contact: newContact,
|
||||
existingFriend: existingFriend,
|
||||
linphoneFriend: CorePreferences.friendListInWhichStoreNewlyCreatedFriends
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveImageThreadSafe(image: UIImage, name: String, prefix: String, contact: Contact, existingFriend: Friend?, linphoneFriend: String) {
|
||||
ContactsManager.shared.saveImage(
|
||||
image: image,
|
||||
name: name,
|
||||
prefix: prefix,
|
||||
contact: contact,
|
||||
linphoneFriend: linphoneFriend,
|
||||
existingFriend: existingFriend,
|
||||
editingFriend: true
|
||||
) {
|
||||
if let existingFriend = existingFriend {
|
||||
self.updateAvatar(for: existingFriend)
|
||||
} else {
|
||||
MagicSearchSingleton.shared.searchForContacts()
|
||||
ContactsManager.shared.updateSubscriptionsLinphoneList()
|
||||
}
|
||||
self.finishUIUpdate(existingFriend: existingFriend)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAvatar(for friend: Friend) {
|
||||
let addressTmp = friend.address?.clone()?.asStringUriOnly() ?? ""
|
||||
SharedMainViewModel.shared.displayedFriend?.resetContactAvatarModel(
|
||||
friend: friend,
|
||||
name: friend.name ?? "",
|
||||
address: addressTmp,
|
||||
withPresence: SharedMainViewModel.shared.displayedFriend?.withPresence
|
||||
)
|
||||
}
|
||||
|
||||
private func finishUIUpdate(existingFriend: Friend?) {
|
||||
let friendIsNil = existingFriend == nil
|
||||
DispatchQueue.main.async {
|
||||
delayColorDismiss()
|
||||
withAnimation {
|
||||
if friendIsNil {
|
||||
isShowEditContactFragment.toggle()
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
editContactViewModel.resetValues()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ class ContactAvatarModel: ObservableObject, Identifiable {
|
|||
@Published var phoneNumbersWithLabel: [(label: String, phoneNumber: String)] = []
|
||||
|
||||
var nativeUri: String = ""
|
||||
var editable: Bool = true
|
||||
var isReadOnly: Bool = false
|
||||
var withPresence: Bool?
|
||||
|
||||
@Published var starred: Bool = false
|
||||
|
|
@ -70,12 +72,20 @@ class ContactAvatarModel: ObservableObject, Identifiable {
|
|||
}
|
||||
}
|
||||
let nativeUriTmp = friend?.nativeUri ?? ""
|
||||
let editableTmp = friend?.friendList?.type == .CardDAV || nativeUriTmp.isEmpty
|
||||
let isReadOnlyTmp = (friend?.isReadOnly == true) || (friend?.inList() == false)
|
||||
let withPresenceTmp = withPresence
|
||||
let starredTmp = friend?.starred ?? false
|
||||
let vcardTmp = friend?.vcard ?? nil
|
||||
let organizationTmp = friend?.organization ?? ""
|
||||
let jobTitleTmp = friend?.jobTitle ?? ""
|
||||
let photoTmp = friend?.photo ?? ""
|
||||
var photoTmp = friend?.photo ?? ""
|
||||
|
||||
if friend?.friendList?.type == .CardDAV && friend?.photo?.isEmpty == false {
|
||||
let fileName = "file:/" + name + ".png"
|
||||
photoTmp = fileName.replacingOccurrences(of: " ", with: "")
|
||||
}
|
||||
|
||||
var lastPresenceInfoTmp = ""
|
||||
var presenceStatusTmp: ConsolidatedPresence = .Offline
|
||||
|
||||
|
|
@ -108,6 +118,8 @@ class ContactAvatarModel: ObservableObject, Identifiable {
|
|||
self.addresses = addressesTmp
|
||||
self.phoneNumbersWithLabel = phoneNumbersWithLabelTmp
|
||||
self.nativeUri = nativeUriTmp
|
||||
self.editable = editableTmp
|
||||
self.isReadOnly = isReadOnlyTmp
|
||||
self.withPresence = withPresenceTmp
|
||||
self.starred = starredTmp
|
||||
self.vcard = vcardTmp
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ class ContactsListViewModel: ObservableObject {
|
|||
|
||||
private var contactChatRoomDelegate: ChatRoomDelegate?
|
||||
|
||||
private let nativeAddressBookFriendList = "Native address-book"
|
||||
let linphoneAddressBookFriendList = "Linphone address-book"
|
||||
let tempRemoteAddressBookFriendList = "TempRemoteDirectoryContacts address-book"
|
||||
|
||||
init() {}
|
||||
|
||||
func createOneToOneChatRoomWith(remote: Address) {
|
||||
|
|
|
|||
|
|
@ -39,11 +39,11 @@ class EditContactViewModel: ObservableObject {
|
|||
|
||||
func resetValues() {
|
||||
CoreContext.shared.doOnCoreQueue { _ in
|
||||
let nativeUriTmp = (self.selectedEditFriend == nil ? "" : self.selectedEditFriend!.nativeUri) ?? ""
|
||||
let nativeUriTmp = self.selectedEditFriend == nil ? "" : self.selectedEditFriend!.nativeUri
|
||||
let givenNameTmp = (self.selectedEditFriend == nil ? "" : self.selectedEditFriend!.vcard?.givenName) ?? ""
|
||||
let familyNameTmp = (self.selectedEditFriend == nil ? "" : self.selectedEditFriend!.vcard?.familyName) ?? ""
|
||||
let organizationTmp = (self.selectedEditFriend == nil ? "" : self.selectedEditFriend!.organization) ?? ""
|
||||
let jobTitleTmp = (self.selectedEditFriend == nil ? "" : self.selectedEditFriend!.jobTitle) ?? ""
|
||||
let organizationTmp = self.selectedEditFriend == nil ? "" : self.selectedEditFriend!.organization
|
||||
let jobTitleTmp = self.selectedEditFriend == nil ? "" : self.selectedEditFriend!.jobTitle
|
||||
|
||||
var sipAddressesTmp: [String] = []
|
||||
var phoneNumbersTmp: [String] = []
|
||||
|
|
|
|||
|
|
@ -60,11 +60,13 @@ struct ContentView: View {
|
|||
@State var isShowDismissPopup = false
|
||||
@State var isShowSendCancelMeetingNotificationPopup = false
|
||||
@State var isShowStartCallGroupPopup = false
|
||||
@State var isShowDeleteMessagePopup = false
|
||||
@State var isShowSipAddressesPopup = false
|
||||
@State var isShowSipAddressesPopupType = 0 // 0 to call, 1 to message, 2 to video call
|
||||
@State var isShowConversationFragment = false
|
||||
@State var isShowAccountProfileFragment = false
|
||||
@State var isShowSettingsFragment = false
|
||||
@State var isShowRecordingsListFragment = false
|
||||
@State var isShowHelpFragment = false
|
||||
|
||||
@State var fullscreenVideo = false
|
||||
|
|
@ -138,6 +140,54 @@ struct ContentView: View {
|
|||
.background(Color.redDanger500)
|
||||
}
|
||||
|
||||
if sharedMainViewModel.waitingMessageCount > 0 && (!telecomManager.callInProgress || (telecomManager.callInProgress && !telecomManager.callDisplayed)) {
|
||||
HStack {
|
||||
Image("voicemail")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 26, height: 26)
|
||||
.padding(.leading, 10)
|
||||
|
||||
if sharedMainViewModel.waitingMessageCount > 1 {
|
||||
Text(String(format: String(localized: "mwi_messages_are_waiting_multiple"), sharedMainViewModel.waitingMessageCount.description))
|
||||
.default_text_style_white(styleSize: 16)
|
||||
} else {
|
||||
Text(String(localized: "mwi_messages_are_waiting_single"))
|
||||
.default_text_style_white(styleSize: 16)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(
|
||||
action: {
|
||||
withAnimation {
|
||||
sharedMainViewModel.waitingMessageCount = 0
|
||||
}
|
||||
}, label: {
|
||||
Image("x")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 26, height: 26)
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 40)
|
||||
.padding(.horizontal, 10)
|
||||
.background(Color.gray)
|
||||
.onTapGesture {
|
||||
if let index = accountProfileViewModel.defaultAccountModelIndex,
|
||||
index < coreContext.accounts.count {
|
||||
sharedMainViewModel.waitingMessageCount = 0
|
||||
coreContext.accounts[index].callVoicemailUri()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !sharedMainViewModel.fileUrlsToShare.isEmpty && (!telecomManager.callInProgress || (telecomManager.callInProgress && !telecomManager.callDisplayed)) {
|
||||
HStack {
|
||||
Image("share-network")
|
||||
|
|
@ -346,32 +396,34 @@ struct ContentView: View {
|
|||
}
|
||||
.frame(height: geometry.size.height/4)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 3)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
sharedMainViewModel.displayedConversation = nil
|
||||
}, label: {
|
||||
VStack {
|
||||
Image("video-conference")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(sharedMainViewModel.indexView == 3 ? Color.orangeMain500 : Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
if sharedMainViewModel.indexView == 0 {
|
||||
Text("bottom_navigation_meetings_label")
|
||||
.default_text_style_700(styleSize: 10)
|
||||
} else {
|
||||
Text("bottom_navigation_meetings_label")
|
||||
.default_text_style(styleSize: 10)
|
||||
}
|
||||
}
|
||||
})
|
||||
.padding(.top)
|
||||
.frame(height: geometry.size.height/4)
|
||||
|
||||
Spacer()
|
||||
if !sharedMainViewModel.disableMeetingFeature {
|
||||
Button(action: {
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 3)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
sharedMainViewModel.displayedConversation = nil
|
||||
}, label: {
|
||||
VStack {
|
||||
Image("video-conference")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(sharedMainViewModel.indexView == 3 ? Color.orangeMain500 : Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
if sharedMainViewModel.indexView == 0 {
|
||||
Text("bottom_navigation_meetings_label")
|
||||
.default_text_style_700(styleSize: 10)
|
||||
} else {
|
||||
Text("bottom_navigation_meetings_label")
|
||||
.default_text_style(styleSize: 10)
|
||||
}
|
||||
}
|
||||
})
|
||||
.padding(.top)
|
||||
.frame(height: geometry.size.height/4)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: 75, height: geometry.size.height)
|
||||
|
|
@ -405,6 +457,17 @@ struct ContentView: View {
|
|||
VStack(spacing: 0) {
|
||||
if searchIsActive == false {
|
||||
HStack {
|
||||
Button {
|
||||
openMenu()
|
||||
} label: {
|
||||
Image("list")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 5)
|
||||
}
|
||||
|
||||
if let index = accountProfileViewModel.defaultAccountModelIndex,
|
||||
index < coreContext.accounts.count {
|
||||
|
||||
|
|
@ -473,7 +536,7 @@ struct ContentView: View {
|
|||
|
||||
Text(String(localized: sharedMainViewModel.indexView == 0 ? "bottom_navigation_contacts_label" : (sharedMainViewModel.indexView == 1 ? "bottom_navigation_calls_label" : (sharedMainViewModel.indexView == 2 ? "bottom_navigation_conversations_label" : "bottom_navigation_meetings_label"))))
|
||||
.default_text_style_white_800(styleSize: 20)
|
||||
.padding(.leading, 10)
|
||||
.padding(.leading, 2)
|
||||
|
||||
Spacer()
|
||||
|
||||
|
|
@ -509,7 +572,7 @@ struct ContentView: View {
|
|||
Button {
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
isMenuOpen = false
|
||||
magicSearch.allContact = true
|
||||
magicSearch.changeAllContact(allContactBool: true)
|
||||
magicSearch.searchForContacts()
|
||||
} label: {
|
||||
HStack {
|
||||
|
|
@ -527,11 +590,11 @@ struct ContentView: View {
|
|||
Button {
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
isMenuOpen = false
|
||||
magicSearch.allContact = false
|
||||
magicSearch.changeAllContact(allContactBool: false)
|
||||
magicSearch.searchForContacts()
|
||||
} label: {
|
||||
HStack {
|
||||
Text(String(format: String(localized: "contacts_list_filter_popup_see_linphone_only"), Bundle.main.displayName))
|
||||
Text(!magicSearch.linphoneDomain ? String(localized: "contacts_list_filter_popup_see_sip_only") : String(format: String(localized: "contacts_list_filter_popup_see_linphone_only"), Bundle.main.displayName))
|
||||
Spacer()
|
||||
if !magicSearch.allContact {
|
||||
Image("green-check")
|
||||
|
|
@ -895,31 +958,33 @@ struct ContentView: View {
|
|||
.frame(width: 66)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Button(action: {
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 3)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
sharedMainViewModel.displayedConversation = nil
|
||||
}, label: {
|
||||
VStack {
|
||||
Image("video-conference")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(sharedMainViewModel.indexView == 3 ? Color.orangeMain500 : Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
if sharedMainViewModel.indexView == 3 {
|
||||
Text("bottom_navigation_meetings_label")
|
||||
.default_text_style_700(styleSize: 9)
|
||||
} else {
|
||||
Text("bottom_navigation_meetings_label")
|
||||
.default_text_style(styleSize: 9)
|
||||
|
||||
if !sharedMainViewModel.disableMeetingFeature {
|
||||
Spacer()
|
||||
Button(action: {
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 3)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
sharedMainViewModel.displayedConversation = nil
|
||||
}, label: {
|
||||
VStack {
|
||||
Image("video-conference")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(sharedMainViewModel.indexView == 3 ? Color.orangeMain500 : Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
if sharedMainViewModel.indexView == 3 {
|
||||
Text("bottom_navigation_meetings_label")
|
||||
.default_text_style_700(styleSize: 9)
|
||||
} else {
|
||||
Text("bottom_navigation_meetings_label")
|
||||
.default_text_style(styleSize: 9)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.padding(.top)
|
||||
.frame(width: 66)
|
||||
})
|
||||
.padding(.top)
|
||||
.frame(width: 66)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
@ -972,6 +1037,7 @@ struct ContentView: View {
|
|||
ConversationFragment(
|
||||
isShowConversationFragment: $isShowConversationFragment,
|
||||
isShowStartCallGroupPopup: $isShowStartCallGroupPopup,
|
||||
isShowDeleteMessagePopup: $isShowDeleteMessagePopup,
|
||||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress,
|
||||
isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment,
|
||||
|
|
@ -1026,6 +1092,7 @@ struct ContentView: View {
|
|||
isShowLoginFragment: $isShowLoginFragment,
|
||||
isShowAccountProfileFragment: $isShowAccountProfileFragment,
|
||||
isShowSettingsFragment: $isShowSettingsFragment,
|
||||
isShowRecordingsListFragment: $isShowRecordingsListFragment,
|
||||
isShowHelpFragment: $isShowHelpFragment
|
||||
)
|
||||
.environmentObject(accountProfileViewModel)
|
||||
|
|
@ -1047,17 +1114,22 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
if isShowEditContactFragment {
|
||||
EditContactFragment(
|
||||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
isShowDismissPopup: $isShowDismissPopup,
|
||||
isShowEditContactFragmentAddress: isShowEditContactFragmentAddress
|
||||
)
|
||||
VStack {
|
||||
EditContactFragment(
|
||||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
isShowDismissPopup: $isShowDismissPopup,
|
||||
isShowEditContactFragmentAddress: isShowEditContactFragmentAddress
|
||||
)
|
||||
.frame(height: geometry.size.height)
|
||||
.onAppear {
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
isShowEditContactFragmentAddress = ""
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.zIndex(3)
|
||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||
.onAppear {
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
isShowEditContactFragmentAddress = ""
|
||||
}
|
||||
}
|
||||
|
||||
if isShowStartCallFragment {
|
||||
|
|
@ -1091,14 +1163,16 @@ struct ContentView: View {
|
|||
)
|
||||
),
|
||||
content: Text("contact_dialog_delete_message"),
|
||||
titleFirstButton: Text("dialog_cancel"),
|
||||
actionFirstButton: {
|
||||
self.isShowDeleteContactPopup.toggle()},
|
||||
titleSecondButton: Text("dialog_ok"),
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_confirm"),
|
||||
actionSecondButton: {
|
||||
contactsListVM.deleteSelectedContact()
|
||||
self.isShowDeleteContactPopup.toggle()
|
||||
})
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: { self.isShowDeleteContactPopup.toggle() }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onTapGesture {
|
||||
|
|
@ -1110,27 +1184,31 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
if isShowDeleteAllHistoryPopup {
|
||||
PopupView(isShowPopup: $isShowDeleteContactPopup,
|
||||
title: Text("history_dialog_delete_all_call_logs_title"),
|
||||
content: Text("history_dialog_delete_all_call_logs_message"),
|
||||
titleFirstButton: Text("dialog_cancel"),
|
||||
actionFirstButton: {
|
||||
self.isShowDeleteAllHistoryPopup.toggle()
|
||||
if let historyListVM = historyListViewModel {
|
||||
historyListVM.callLogsAddressToDelete = ""
|
||||
PopupView(
|
||||
isShowPopup: $isShowDeleteContactPopup,
|
||||
title: Text("history_dialog_delete_all_call_logs_title"),
|
||||
content: Text("history_dialog_delete_all_call_logs_message"),
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_confirm"),
|
||||
actionSecondButton: {
|
||||
if let historyListVM = historyListViewModel {
|
||||
historyListVM.removeCallLogs()
|
||||
}
|
||||
self.isShowDeleteAllHistoryPopup.toggle()
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_remove_call_logs"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: {
|
||||
self.isShowDeleteAllHistoryPopup.toggle()
|
||||
if let historyListVM = historyListViewModel {
|
||||
historyListVM.callLogsAddressToDelete = ""
|
||||
}
|
||||
}
|
||||
},
|
||||
titleSecondButton: Text("dialog_ok"),
|
||||
actionSecondButton: {
|
||||
if let historyListVM = historyListViewModel {
|
||||
historyListVM.removeCallLogs()
|
||||
}
|
||||
self.isShowDeleteAllHistoryPopup.toggle()
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_remove_call_logs"
|
||||
ToastViewModel.shared.displayToast.toggle()
|
||||
})
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onTapGesture {
|
||||
|
|
@ -1139,20 +1217,24 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
if isShowDismissPopup {
|
||||
PopupView(isShowPopup: $isShowDismissPopup,
|
||||
title: Text("contact_editor_dialog_abort_confirmation_title"),
|
||||
content: Text("contact_editor_dialog_abort_confirmation_message"),
|
||||
titleFirstButton: Text("dialog_cancel"),
|
||||
actionFirstButton: {self.isShowDismissPopup.toggle()},
|
||||
titleSecondButton: Text("dialog_ok"),
|
||||
actionSecondButton: {
|
||||
self.isShowDismissPopup.toggle()
|
||||
if isShowEditContactFragment {
|
||||
isShowEditContactFragment = false
|
||||
} else {
|
||||
isShowEditContactFragmentInContactDetails = false
|
||||
}
|
||||
})
|
||||
PopupView(
|
||||
isShowPopup: $isShowDismissPopup,
|
||||
title: Text("contact_editor_dialog_abort_confirmation_title"),
|
||||
content: Text("contact_editor_dialog_abort_confirmation_message"),
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_confirm"),
|
||||
actionSecondButton: {
|
||||
self.isShowDismissPopup.toggle()
|
||||
if isShowEditContactFragment {
|
||||
isShowEditContactFragment = false
|
||||
} else {
|
||||
isShowEditContactFragmentInContactDetails = false
|
||||
}
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: { self.isShowDismissPopup.toggle() }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onTapGesture {
|
||||
|
|
@ -1265,6 +1347,14 @@ struct ContentView: View {
|
|||
.transition(.move(edge: .trailing))
|
||||
}
|
||||
|
||||
if isShowRecordingsListFragment {
|
||||
RecordingsListFragment(
|
||||
isShowRecordingsListFragment: $isShowRecordingsListFragment
|
||||
)
|
||||
.zIndex(3)
|
||||
.transition(.move(edge: .trailing))
|
||||
}
|
||||
|
||||
if isShowHelpFragment {
|
||||
HelpFragment(
|
||||
isShowHelpFragment: $isShowHelpFragment
|
||||
|
|
@ -1274,21 +1364,25 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
if let meetingsListVM = meetingsListViewModel, isShowSendCancelMeetingNotificationPopup {
|
||||
PopupView(isShowPopup: $isShowSendCancelMeetingNotificationPopup,
|
||||
title: Text("meeting_schedule_cancel_dialog_title"),
|
||||
content: !sharedMainViewModel.disableChatFeature ? Text("meeting_schedule_cancel_dialog_message") : Text(""),
|
||||
titleFirstButton: Text("dialog_cancel"),
|
||||
actionFirstButton: {
|
||||
sharedMainViewModel.displayedMeeting = nil
|
||||
meetingsListVM.deleteSelectedMeeting()
|
||||
self.isShowSendCancelMeetingNotificationPopup.toggle(
|
||||
) },
|
||||
titleSecondButton: Text("dialog_ok"),
|
||||
actionSecondButton: {
|
||||
sharedMainViewModel.displayedMeeting = nil
|
||||
meetingsListVM.cancelMeetingWithNotifications()
|
||||
self.isShowSendCancelMeetingNotificationPopup.toggle()
|
||||
})
|
||||
PopupView(
|
||||
isShowPopup: $isShowSendCancelMeetingNotificationPopup,
|
||||
title: Text("meeting_schedule_cancel_dialog_title"),
|
||||
content: !sharedMainViewModel.disableChatFeature ? Text("meeting_schedule_cancel_dialog_message") : Text(""),
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_confirm"),
|
||||
actionSecondButton: {
|
||||
sharedMainViewModel.displayedMeeting = nil
|
||||
meetingsListVM.cancelMeetingWithNotifications()
|
||||
self.isShowSendCancelMeetingNotificationPopup.toggle()
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: {
|
||||
sharedMainViewModel.displayedMeeting = nil
|
||||
meetingsListVM.deleteSelectedMeeting()
|
||||
self.isShowSendCancelMeetingNotificationPopup.toggle()
|
||||
}
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onTapGesture {
|
||||
|
|
@ -1301,17 +1395,17 @@ struct ContentView: View {
|
|||
isShowPopup: $isShowStartCallGroupPopup,
|
||||
title: Text("conversation_info_confirm_start_group_call_dialog_title"),
|
||||
content: Text("conversation_info_confirm_start_group_call_dialog_message"),
|
||||
titleFirstButton: Text("dialog_cancel"),
|
||||
actionFirstButton: {
|
||||
self.isShowStartCallGroupPopup.toggle()
|
||||
},
|
||||
titleSecondButton: Text("dialog_ok"),
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_confirm"),
|
||||
actionSecondButton: {
|
||||
if sharedMainViewModel.displayedConversation != nil {
|
||||
sharedMainViewModel.displayedConversation!.createGroupCall()
|
||||
}
|
||||
self.isShowStartCallGroupPopup.toggle()
|
||||
}
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: { self.isShowStartCallGroupPopup.toggle() }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
|
|
@ -1320,6 +1414,31 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if isShowDeleteMessagePopup {
|
||||
PopupView(
|
||||
isShowPopup: $isShowDeleteMessagePopup,
|
||||
title: Text("conversation_dialog_delete_chat_message_title"),
|
||||
content: nil,
|
||||
titleFirstButton: Text("conversation_dialog_delete_for_everyone_label"),
|
||||
actionFirstButton: {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("DeleteMessageForEveryone"), object: nil)
|
||||
self.isShowDeleteMessagePopup.toggle()
|
||||
},
|
||||
titleSecondButton: Text("conversation_dialog_delete_locally_label"),
|
||||
actionSecondButton: {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("DeleteMessageForMe"), object: nil)
|
||||
self.isShowDeleteMessagePopup.toggle()
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: { self.isShowDeleteMessagePopup.toggle() }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onTapGesture {
|
||||
self.isShowDeleteMessagePopup.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if isShowConversationInfoPopup {
|
||||
PopupViewWithTextField(
|
||||
isShowConversationInfoPopup: $isShowConversationInfoPopup,
|
||||
|
|
@ -1388,6 +1507,8 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ContactLoaded"))) { _ in
|
||||
callViewModel.resetCallView()
|
||||
|
||||
if let conversationsListVM = conversationsListViewModel {
|
||||
conversationsListVM.updateChatRoomsList()
|
||||
}
|
||||
|
|
@ -1465,6 +1586,7 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.id(coreContext.reloadID)
|
||||
}
|
||||
|
||||
func openMenu() {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ struct ChatBubbleView: View {
|
|||
HStack {
|
||||
if eventLogMessage.eventModel.eventLogType == .ConferenceChatMessage {
|
||||
VStack {
|
||||
if !eventLogMessage.message.text.isEmpty || !eventLogMessage.message.attachments.isEmpty || eventLogMessage.message.isIcalendar {
|
||||
if !eventLogMessage.message.text.isEmpty || !eventLogMessage.message.attachments.isEmpty || eventLogMessage.message.isIcalendar || eventLogMessage.message.isRetracted {
|
||||
HStack(alignment: .top, content: {
|
||||
if eventLogMessage.message.isOutgoing {
|
||||
Spacer()
|
||||
|
|
@ -137,6 +137,12 @@ struct ChatBubbleView: View {
|
|||
.foregroundStyle(Color.grayMain2c700)
|
||||
.default_text_style(styleSize: 14)
|
||||
.lineLimit(/*@START_MENU_TOKEN@*/2/*@END_MENU_TOKEN@*/)
|
||||
} else if eventLogMessage.message.replyMessage!.isRetracted {
|
||||
Text(eventLogMessage.message.replyMessage!.isOutgoing ? "conversation_message_content_deleted_by_us_label" : "conversation_message_content_deleted_label")
|
||||
.italic()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.font(.system(size: 14))
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.padding(.all, 15)
|
||||
|
|
@ -173,7 +179,13 @@ struct ChatBubbleView: View {
|
|||
}
|
||||
|
||||
if !eventLogMessage.message.text.isEmpty {
|
||||
DynamicLinkText(text: eventLogMessage.message.text)
|
||||
DynamicLinkText(text: eventLogMessage.message.text, participantConversationModel: conversationViewModel.participantConversationModel)
|
||||
} else if eventLogMessage.message.isRetracted {
|
||||
Text(eventLogMessage.message.isOutgoing ? "conversation_message_content_deleted_by_us_label" : "conversation_message_content_deleted_label")
|
||||
.italic()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.font(.system(size: 14))
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
if eventLogMessage.message.isIcalendar && eventLogMessage.message.messageConferenceInfo != nil {
|
||||
|
|
@ -325,6 +337,14 @@ struct ChatBubbleView: View {
|
|||
.padding(.top, 1)
|
||||
}
|
||||
|
||||
if eventLogMessage.message.isEdited && eventLogMessage.message.isOutgoing {
|
||||
Text("conversation_message_edited_label")
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.padding(.top, 1)
|
||||
.padding(.trailing, -4)
|
||||
}
|
||||
|
||||
Text(conversationViewModel.getMessageTime(startDate: eventLogMessage.message.dateReceived))
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
|
|
@ -349,6 +369,14 @@ struct ChatBubbleView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if eventLogMessage.message.isEdited && !eventLogMessage.message.isOutgoing {
|
||||
Text("conversation_message_edited_label")
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.padding(.top, 1)
|
||||
.padding(.trailing, -4)
|
||||
}
|
||||
|
||||
if eventLogMessage.message.isEphemeral && !eventLogMessage.message.isOutgoing {
|
||||
Image("clock-countdown")
|
||||
.renderingMode(.template)
|
||||
|
|
@ -918,42 +946,81 @@ struct ChatBubbleView: View {
|
|||
|
||||
struct DynamicLinkText: View {
|
||||
let text: String
|
||||
let participantConversationModel: [ContactAvatarModel]
|
||||
|
||||
var body: some View {
|
||||
let components = text.components(separatedBy: " ")
|
||||
|
||||
Text(makeAttributedString(from: components))
|
||||
Text(makeAttributedString(from: text))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(nil)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.default_text_style(styleSize: 14)
|
||||
}
|
||||
|
||||
// Function to create an AttributedString with clickable links
|
||||
private func makeAttributedString(from components: [String]) -> AttributedString {
|
||||
var result = AttributedString("")
|
||||
for (index, component) in components.enumerated() {
|
||||
if let url = URL(string: component.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""),
|
||||
url.scheme == "http" || url.scheme == "https" {
|
||||
var attributedText = AttributedString(component)
|
||||
attributedText.link = url
|
||||
attributedText.foregroundColor = .blue
|
||||
attributedText.underlineStyle = .single
|
||||
result.append(attributedText)
|
||||
private func makeAttributedString(from text: String) -> AttributedString {
|
||||
var result = AttributedString()
|
||||
var currentWord = ""
|
||||
|
||||
for char in text {
|
||||
if char == " " || char == "\n" {
|
||||
appendWord(currentWord, to: &result)
|
||||
result.append(AttributedString(String(char)))
|
||||
currentWord = ""
|
||||
} else {
|
||||
result.append(AttributedString(component))
|
||||
}
|
||||
|
||||
// Add space between words except for the last one
|
||||
if index < components.count - 1 {
|
||||
result.append(AttributedString(" "))
|
||||
currentWord.append(char)
|
||||
}
|
||||
}
|
||||
|
||||
appendWord(currentWord, to: &result)
|
||||
return result
|
||||
}
|
||||
|
||||
private func appendWord(_ word: String, to result: inout AttributedString) {
|
||||
guard !word.isEmpty else { return }
|
||||
|
||||
// URL
|
||||
if
|
||||
let encoded = word.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
|
||||
let url = URL(string: encoded),
|
||||
["http", "https"].contains(url.scheme)
|
||||
{
|
||||
var link = AttributedString(word)
|
||||
link.link = url
|
||||
link.foregroundColor = .blue
|
||||
link.underlineStyle = .single
|
||||
result.append(link)
|
||||
return
|
||||
}
|
||||
|
||||
// Mention
|
||||
if isMention(word),
|
||||
let participant = participantConversationModel.first(where: {($0.address.dropFirst(4).split(separator: "@").first ?? "") == word.dropFirst()}),
|
||||
let mentionURL = URL(string: "linphone-mention://\(participant.address)")
|
||||
{
|
||||
var mention = AttributedString("@" + participant.name)
|
||||
mention.link = mentionURL
|
||||
mention.foregroundColor = Color.orangeMain500
|
||||
mention.font = .system(size: 14, weight: .semibold)
|
||||
result.append(mention)
|
||||
return
|
||||
}
|
||||
|
||||
// Text
|
||||
var normal = AttributedString(word)
|
||||
normal.foregroundColor = Color.grayMain2c700
|
||||
result.append(normal)
|
||||
}
|
||||
|
||||
private func isMention(_ word: String) -> Bool {
|
||||
guard word.first == "@", word.count > 1 else { return false }
|
||||
|
||||
let username = word.dropFirst()
|
||||
return username.allSatisfy {
|
||||
$0.isLetter || $0.isNumber || $0 == "." || $0 == "_"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum URLType {
|
||||
case name(String) // local file name of gif
|
||||
case url(URL) // remote url
|
||||
|
|
|
|||
|
|
@ -141,50 +141,58 @@ struct ConversationForwardMessageFragment: View {
|
|||
.padding(.vertical)
|
||||
.padding(.horizontal)
|
||||
|
||||
ScrollView {
|
||||
if !conversationForwardMessageViewModel.conversationsList.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("bottom_navigation_conversations_label")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
ZStack {
|
||||
ScrollView {
|
||||
if !conversationForwardMessageViewModel.conversationsList.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("bottom_navigation_conversations_label")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
Spacer()
|
||||
conversationsList
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
|
||||
if !ContactsManager.shared.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
|
||||
withAnimation {
|
||||
conversationForwardMessageViewModel.createOneToOneChatRoomWith(remote: addr)
|
||||
}
|
||||
|
||||
})
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
conversationsList
|
||||
if !contactsManager.lastSearchSuggestions.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
}
|
||||
}
|
||||
|
||||
if !ContactsManager.shared.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
|
||||
withAnimation {
|
||||
conversationForwardMessageViewModel.createOneToOneChatRoomWith(remote: addr)
|
||||
}
|
||||
|
||||
})
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
if !contactsManager.lastSearchSuggestions.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
if magicSearch.isLoading {
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -275,6 +283,7 @@ struct ConversationForwardMessageFragment: View {
|
|||
HStack {
|
||||
if index < contactsManager.lastSearchSuggestions.count
|
||||
&& contactsManager.lastSearchSuggestions[index].address != nil {
|
||||
if contactsManager.lastSearchSuggestions[index].address!.domain != CorePreferences.defaultDomain {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)),
|
||||
lastName: ""))
|
||||
|
|
@ -284,9 +293,29 @@ struct ConversationForwardMessageFragment: View {
|
|||
|
||||
Text(String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)))
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
} else {
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
let nameTmp = address.displayName
|
||||
?? address.username
|
||||
?? String(address.asStringUriOnly().dropFirst(4))
|
||||
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: nameTmp,
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(nameTmp)
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ struct ConversationFragment: View {
|
|||
|
||||
@Binding var isShowConversationFragment: Bool
|
||||
@Binding var isShowStartCallGroupPopup: Bool
|
||||
@Binding var isShowDeleteMessagePopup: Bool
|
||||
|
||||
@State private var selectedCategoryIndex = 0
|
||||
|
||||
|
|
@ -88,6 +89,20 @@ struct ConversationFragment: View {
|
|||
|
||||
@State private var isImdnOrReactionsSheetVisible = false
|
||||
|
||||
@State var mentionIsOpen: Bool = false
|
||||
@State var mentionQuery: String = ""
|
||||
|
||||
private let rowHeight: CGFloat = 60
|
||||
private let maxVisibleRows: CGFloat = 3.5
|
||||
|
||||
private var filteredParticipants: [ContactAvatarModel] {
|
||||
conversationViewModel.participantConversationModel.filter {
|
||||
mentionQuery.isEmpty
|
||||
|| $0.name.localizedCaseInsensitiveContains(mentionQuery)
|
||||
|| String($0.address.dropFirst(4).split(separator: "@").first ?? "").localizedCaseInsensitiveContains(mentionQuery)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
GeometryReader { geometry in
|
||||
|
|
@ -233,6 +248,8 @@ struct ConversationFragment: View {
|
|||
if SharedMainViewModel.shared.displayedConversation != nil && (navigationManager.peerAddr == nil || navigationManager.peerAddr!.contains(SharedMainViewModel.shared.displayedConversation!.remoteSipUri)) {
|
||||
conversationViewModel.resetDisplayedChatRoom()
|
||||
}
|
||||
} else {
|
||||
conversationViewModel.compose(stop: true, cachedConversation: cachedConversation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -460,6 +477,7 @@ struct ConversationFragment: View {
|
|||
}
|
||||
}
|
||||
.onDisappear {
|
||||
conversationViewModel.compose(stop: true, cachedConversation: cachedConversation)
|
||||
conversationViewModel.resetMessage()
|
||||
}
|
||||
} else {
|
||||
|
|
@ -555,6 +573,7 @@ struct ConversationFragment: View {
|
|||
conversationViewModel.getMessages()
|
||||
}
|
||||
.onDisappear {
|
||||
conversationViewModel.compose(stop: true, cachedConversation: cachedConversation)
|
||||
conversationViewModel.resetMessage()
|
||||
}
|
||||
}
|
||||
|
|
@ -620,6 +639,43 @@ struct ConversationFragment: View {
|
|||
}
|
||||
}
|
||||
.transition(.move(edge: .bottom))
|
||||
} else if conversationViewModel.messageToEdit != nil {
|
||||
ZStack(alignment: .top) {
|
||||
HStack {
|
||||
VStack {
|
||||
Text("conversation_editing_message_title")
|
||||
.default_text_style_300(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.bottom, 1)
|
||||
.lineLimit(1)
|
||||
|
||||
Text("\(conversationViewModel.messageToEdit!.message.text)")
|
||||
.default_text_style_300(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.all, 20)
|
||||
.background(Color.gray100)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
messageText = ""
|
||||
withAnimation {
|
||||
conversationViewModel.messageToEdit = nil
|
||||
}
|
||||
}, label: {
|
||||
Image("x")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
})
|
||||
}
|
||||
}
|
||||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
|
||||
if !conversationViewModel.mediasToSend.isEmpty || mediasIsLoading {
|
||||
|
|
@ -821,6 +877,72 @@ struct ConversationFragment: View {
|
|||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
|
||||
if mentionIsOpen && SharedMainViewModel.shared.displayedConversation!.isGroup {
|
||||
ZStack(alignment: .top) {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 0) {
|
||||
Text("conversation_participants_list_header")
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.lineLimit(1)
|
||||
.frame(height: 14)
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
if filteredParticipants.isEmpty {
|
||||
VStack {
|
||||
Text("conversation_participants_list_empty")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
.frame(height: rowHeight)
|
||||
}
|
||||
ForEach(filteredParticipants, id: \.id) { participant in
|
||||
Button {
|
||||
messageText = String(messageText.dropLast(mentionQuery.count))
|
||||
messageText.append((participant.address.dropFirst(4).split(separator: "@").first ?? "") + " ")
|
||||
} label: {
|
||||
HStack {
|
||||
Avatar(contactAvatarModel: participant, avatarSize: 40)
|
||||
|
||||
Text(participant.name)
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.frame(height: rowHeight)
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
height: filteredParticipants.isEmpty ? rowHeight + 30 : min(
|
||||
(CGFloat(filteredParticipants.count) * rowHeight) + 30,
|
||||
(rowHeight * maxVisibleRows) + 30
|
||||
)
|
||||
)
|
||||
.clipped()
|
||||
.background(Color.gray100)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
withAnimation { mentionIsOpen = false }
|
||||
} label: {
|
||||
Image("x")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.padding(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
if !voiceRecordingInProgress {
|
||||
Button {
|
||||
|
|
@ -847,9 +969,8 @@ struct ConversationFragment: View {
|
|||
.focused($isMessageTextFocused)
|
||||
.padding(.vertical, 5)
|
||||
.onChange(of: messageText) { text in
|
||||
if !text.isEmpty {
|
||||
conversationViewModel.compose()
|
||||
}
|
||||
self.updateMentionState(from: text)
|
||||
conversationViewModel.compose(stop: text.isEmpty)
|
||||
}
|
||||
} else {
|
||||
ZStack(alignment: .leading) {
|
||||
|
|
@ -860,9 +981,7 @@ struct ConversationFragment: View {
|
|||
.default_text_style(styleSize: 15)
|
||||
.focused($isMessageTextFocused)
|
||||
.onChange(of: messageText) { text in
|
||||
if !text.isEmpty {
|
||||
conversationViewModel.compose()
|
||||
}
|
||||
conversationViewModel.compose(stop: text.isEmpty)
|
||||
}
|
||||
|
||||
if messageText.isEmpty {
|
||||
|
|
@ -879,43 +998,66 @@ struct ConversationFragment: View {
|
|||
}
|
||||
}
|
||||
|
||||
if messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty {
|
||||
Button {
|
||||
voiceRecordingInProgress = true
|
||||
} label: {
|
||||
Image("microphone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 28, height: 28, alignment: .leading)
|
||||
.padding(.all, 6)
|
||||
.padding(.top, 4)
|
||||
if conversationViewModel.messageToEdit == nil {
|
||||
if messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty {
|
||||
Button {
|
||||
voiceRecordingInProgress = true
|
||||
} label: {
|
||||
Image("microphone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 28, height: 28, alignment: .leading)
|
||||
.padding(.all, 6)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
if conversationViewModel.displayedConversationHistorySize > 1 {
|
||||
NotificationCenter.default.post(name: .onScrollToBottom, object: nil)
|
||||
}
|
||||
|
||||
let messageTextTmp = self.messageText
|
||||
messageText = " "
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
messageText = ""
|
||||
isMessageTextFocused = true
|
||||
|
||||
conversationViewModel.sendMessage(messageText: messageTextTmp)
|
||||
}
|
||||
} label: {
|
||||
Image("paper-plane-tilt")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.frame(width: 28, height: 28, alignment: .leading)
|
||||
.padding(.all, 6)
|
||||
.padding(.top, 4)
|
||||
.rotationEffect(.degrees(45))
|
||||
}
|
||||
.padding(.trailing, 4)
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
if conversationViewModel.displayedConversationHistorySize > 1 {
|
||||
NotificationCenter.default.post(name: .onScrollToBottom, object: nil)
|
||||
}
|
||||
|
||||
let messageTextTmp = self.messageText
|
||||
messageText = " "
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
messageText = ""
|
||||
isMessageTextFocused = true
|
||||
|
||||
conversationViewModel.sendMessage(messageText: messageTextTmp)
|
||||
}
|
||||
messageText = " "
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
messageText = ""
|
||||
isMessageTextFocused = true
|
||||
|
||||
conversationViewModel.sendMessage(messageText: messageTextTmp)
|
||||
}
|
||||
} label: {
|
||||
Image("paper-plane-tilt")
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
.foregroundStyle(messageText.isEmpty ? Color.gray300 : Color.orangeMain500)
|
||||
.frame(width: 28, height: 28, alignment: .leading)
|
||||
.padding(.all, 6)
|
||||
.padding(.top, 4)
|
||||
.rotationEffect(.degrees(45))
|
||||
}
|
||||
.padding(.trailing, 4)
|
||||
.disabled(messageText.isEmpty)
|
||||
}
|
||||
}
|
||||
.padding(.leading, 15)
|
||||
|
|
@ -1096,28 +1238,67 @@ struct ConversationFragment: View {
|
|||
|
||||
Divider()
|
||||
}
|
||||
|
||||
Button {
|
||||
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
|
||||
conversationViewModel.selectedMessage = nil
|
||||
conversationViewModel.replyToMessage(index: indexMessage ?? 0, isMessageTextFocused: Binding(
|
||||
get: { isMessageTextFocused },
|
||||
set: { isMessageTextFocused = $0 }
|
||||
))
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_reply_to_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("reply")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
Divider()
|
||||
if conversationViewModel.selectedMessage!.message.isOutgoing
|
||||
&& !(SharedMainViewModel.shared.displayedConversation?.isReadOnly ?? cachedConversation!.isReadOnly)
|
||||
&& conversationViewModel.selectedMessage!.message.isEditable {
|
||||
Button {
|
||||
if let chatMessage = conversationViewModel.selectedMessage {
|
||||
if voiceRecordingInProgress {
|
||||
voiceRecordingInProgress = false
|
||||
}
|
||||
|
||||
messageText = chatMessage.message.text
|
||||
conversationViewModel.selectedMessage = nil
|
||||
conversationViewModel.editMessage(
|
||||
chatMessage: chatMessage,
|
||||
isMessageTextFocused: Binding(
|
||||
get: { isMessageTextFocused },
|
||||
set: { isMessageTextFocused = $0 }
|
||||
)
|
||||
)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_edit_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("pencil-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
Divider()
|
||||
}
|
||||
|
||||
if !conversationViewModel.selectedMessage!.message.isRetracted {
|
||||
Button {
|
||||
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
|
||||
conversationViewModel.selectedMessage = nil
|
||||
conversationViewModel.replyToMessage(index: indexMessage ?? 0, isMessageTextFocused: Binding(
|
||||
get: { isMessageTextFocused },
|
||||
set: { isMessageTextFocused = $0 }
|
||||
))
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_reply_to_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("reply")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
Divider()
|
||||
}
|
||||
|
||||
if !conversationViewModel.selectedMessage!.message.text.isEmpty {
|
||||
Button {
|
||||
|
|
@ -1146,27 +1327,35 @@ struct ConversationFragment: View {
|
|||
Divider()
|
||||
}
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
isShowConversationForwardMessageFragment = true
|
||||
if !conversationViewModel.selectedMessage!.message.isRetracted {
|
||||
Button {
|
||||
withAnimation {
|
||||
isShowConversationForwardMessageFragment = true
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_forward_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("forward")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_forward_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("forward")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
Divider()
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Button {
|
||||
conversationViewModel.deleteMessage()
|
||||
if conversationViewModel.selectedMessage!.message.isOutgoing
|
||||
&& !(SharedMainViewModel.shared.displayedConversation?.isReadOnly ?? cachedConversation!.isReadOnly)
|
||||
&& conversationViewModel.selectedMessage!.message.isRetractable && !conversationViewModel.selectedMessage!.message.isRetracted {
|
||||
isShowDeleteMessagePopup = true
|
||||
} else {
|
||||
conversationViewModel.deleteMessage()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_delete_selected_item")
|
||||
|
|
@ -1211,11 +1400,18 @@ struct ConversationFragment: View {
|
|||
}
|
||||
.onAppear {
|
||||
touchFeedback()
|
||||
if isMessageTextFocused {
|
||||
isMessageTextFocused = false
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
if conversationViewModel.selectedMessage != nil {
|
||||
conversationViewModel.selectedMessage = nil
|
||||
}
|
||||
}.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("DeleteMessageForMe"))) { _ in
|
||||
conversationViewModel.deleteMessage()
|
||||
}.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("DeleteMessageForEveryone"))) { _ in
|
||||
conversationViewModel.deleteMessageForEveryone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1263,6 +1459,41 @@ struct ConversationFragment: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateMentionState(from text: String) {
|
||||
guard let atIndex = text.lastIndex(of: "@") else {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
|
||||
if atIndex > text.startIndex {
|
||||
let before = text[text.index(before: atIndex)]
|
||||
if before != " " && before != "\n" {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let query = String(text[text.index(after: atIndex)...])
|
||||
|
||||
if query.contains(" ") || query.contains("\n") {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
|
||||
withAnimation {
|
||||
mentionQuery = query
|
||||
mentionIsOpen = true
|
||||
}
|
||||
}
|
||||
|
||||
func closeMention() {
|
||||
withAnimation {
|
||||
mentionIsOpen = false
|
||||
mentionQuery = ""
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:enable cyclomatic_complexity
|
||||
// swiftlint:enable function_body_length
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,12 +104,14 @@ struct ConversationInfoFragment: View {
|
|||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text(conversationViewModel.participantConversationModel.first?.address ?? "")
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
if !CorePreferences.hideSipAddresses {
|
||||
Text(conversationViewModel.participantConversationModel.first?.address ?? "")
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
|
||||
if !SharedMainViewModel.shared.displayedConversation!.avatarModel.lastPresenceInfo.isEmpty {
|
||||
Text(SharedMainViewModel.shared.displayedConversation!.avatarModel.lastPresenceInfo)
|
||||
|
|
@ -351,59 +353,65 @@ struct ConversationInfoFragment: View {
|
|||
|
||||
if conversationViewModel.myParticipantConversationModel != nil && conversationViewModel.myParticipantConversationModel!.address != participantConversationModel.address {
|
||||
Menu {
|
||||
Button(
|
||||
action: {
|
||||
let addressConv = participantConversationModel.address
|
||||
|
||||
let friendIndex = contactsManager.avatarListModel.first(
|
||||
where: {$0.addresses.contains(where: {$0 == addressConv})})
|
||||
|
||||
SharedMainViewModel.shared.displayedCall = nil
|
||||
SharedMainViewModel.shared.changeIndexView(indexViewInt: 0)
|
||||
|
||||
if friendIndex != nil {
|
||||
withAnimation {
|
||||
SharedMainViewModel.shared.displayedFriend = friendIndex
|
||||
}
|
||||
} else {
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
isShowEditContactFragmentAddress = String(participantConversationModel.address.dropFirst(4))
|
||||
}
|
||||
}
|
||||
},
|
||||
label: {
|
||||
HStack {
|
||||
let addressConv = participantConversationModel.address
|
||||
|
||||
let friendIndex = contactsManager.lastSearch.firstIndex(
|
||||
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})})
|
||||
|
||||
let disableAddContact = CorePreferences.disableAddContact
|
||||
|
||||
if (!disableAddContact || (disableAddContact && friendIndex != nil)) {
|
||||
Button(
|
||||
action: {
|
||||
let addressConv = participantConversationModel.address
|
||||
|
||||
let friendIndex = contactsManager.lastSearch.firstIndex(
|
||||
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})})
|
||||
let friendIndex = contactsManager.avatarListModel.first(
|
||||
where: {$0.addresses.contains(where: {$0 == addressConv})})
|
||||
|
||||
SharedMainViewModel.shared.changeIndexView(indexViewInt: 0)
|
||||
|
||||
if friendIndex != nil {
|
||||
Image("address-book")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("conversation_info_menu_go_to_contact")
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
withAnimation {
|
||||
SharedMainViewModel.shared.displayedFriend = friendIndex
|
||||
}
|
||||
} else {
|
||||
Image("user-plus")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("conversation_info_menu_add_to_contacts")
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
isShowEditContactFragmentAddress = String(participantConversationModel.address.dropFirst(4))
|
||||
}
|
||||
}
|
||||
|
||||
SharedMainViewModel.shared.displayedConversation = nil
|
||||
},
|
||||
label: {
|
||||
HStack {
|
||||
if friendIndex != nil {
|
||||
Image("address-book")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("conversation_info_menu_go_to_contact")
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
Image("user-plus")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
|
||||
Text("conversation_info_menu_add_to_contacts")
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if conversationViewModel.isUserAdmin {
|
||||
let participantConversationModelIsAdmin = conversationViewModel.participantConversationModelAdmin.first(
|
||||
|
|
@ -525,7 +533,14 @@ struct ConversationInfoFragment: View {
|
|||
|
||||
VStack(spacing: 0) {
|
||||
if !SharedMainViewModel.shared.displayedConversation!.isReadOnly {
|
||||
if !SharedMainViewModel.shared.displayedConversation!.isGroup {
|
||||
let addressConv = conversationViewModel.participantConversationModel.first?.address ?? ""
|
||||
|
||||
let friendIndex = contactsManager.lastSearch.firstIndex(
|
||||
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})})
|
||||
|
||||
let disableAddContact = CorePreferences.disableAddContact
|
||||
|
||||
if !SharedMainViewModel.shared.displayedConversation!.isGroup && (!disableAddContact || (disableAddContact && friendIndex != nil)) {
|
||||
Button(
|
||||
action: {
|
||||
if SharedMainViewModel.shared.displayedConversation != nil {
|
||||
|
|
@ -553,10 +568,6 @@ struct ConversationInfoFragment: View {
|
|||
},
|
||||
label: {
|
||||
HStack {
|
||||
let addressConv = conversationViewModel.participantConversationModel.first?.address ?? ""
|
||||
|
||||
let friendIndex = contactsManager.lastSearch.firstIndex(
|
||||
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})})
|
||||
if friendIndex != nil {
|
||||
Image("address-book")
|
||||
.renderingMode(.template)
|
||||
|
|
|
|||
|
|
@ -105,14 +105,46 @@ struct ConversationRow: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(conversation.lastMessageText)
|
||||
.foregroundStyle(Color.grayMain2c400)
|
||||
.if(conversation.unreadMessagesCount > 0) { view in
|
||||
view.default_text_style_700(styleSize: 14)
|
||||
HStack(spacing: 0) {
|
||||
Text(conversation.lastMessagePrefixText)
|
||||
.foregroundStyle(Color.grayMain2c400)
|
||||
.if(conversation.unreadMessagesCount > 0) { view in
|
||||
view.default_text_style_700(styleSize: 14)
|
||||
}
|
||||
.default_text_style(styleSize: 14)
|
||||
.lineLimit(1)
|
||||
.layoutPriority(1)
|
||||
|
||||
if !conversation.lastMessageIcon.isEmpty {
|
||||
Image(conversation.lastMessageIcon)
|
||||
.resizable()
|
||||
.frame(width: 16, height: 16)
|
||||
.layoutPriority(0)
|
||||
.padding(.trailing, 2)
|
||||
}
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
|
||||
if conversation.lastMessageInItalic {
|
||||
Text(conversation.lastMessageText)
|
||||
.italic()
|
||||
.if(conversation.unreadMessagesCount > 0) { view in
|
||||
view.bold()
|
||||
}
|
||||
.foregroundStyle(Color.grayMain2c400)
|
||||
.font(.system(size: 14))
|
||||
.lineLimit(1)
|
||||
.layoutPriority(-1)
|
||||
} else {
|
||||
Text(conversation.lastMessageText)
|
||||
.foregroundStyle(Color.grayMain2c400)
|
||||
.if(conversation.unreadMessagesCount > 0) { view in
|
||||
view.default_text_style_700(styleSize: 14)
|
||||
}
|
||||
.default_text_style(styleSize: 14)
|
||||
.lineLimit(1)
|
||||
.layoutPriority(-1)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,34 +170,42 @@ struct StartConversationFragment: View {
|
|||
)
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
if !ContactsManager.shared.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
ZStack {
|
||||
ScrollView {
|
||||
if !ContactsManager.shared.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
|
||||
|
||||
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
|
||||
startConversationViewModel.createOneToOneChatRoomWith(remote: addr)
|
||||
})
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
if !contactsManager.lastSearchSuggestions.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
})
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
if !contactsManager.lastSearchSuggestions.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
}
|
||||
}
|
||||
|
||||
if magicSearch.isLoading {
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -257,6 +265,7 @@ struct StartConversationFragment: View {
|
|||
HStack {
|
||||
if index < contactsManager.lastSearchSuggestions.count
|
||||
&& contactsManager.lastSearchSuggestions[index].address != nil {
|
||||
if contactsManager.lastSearchSuggestions[index].address!.domain != CorePreferences.defaultDomain {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)),
|
||||
lastName: ""))
|
||||
|
|
@ -266,9 +275,29 @@ struct StartConversationFragment: View {
|
|||
|
||||
Text(String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)))
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
} else {
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
let nameTmp = address.displayName
|
||||
?? address.username
|
||||
?? String(address.asStringUriOnly().dropFirst(4))
|
||||
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: nameTmp,
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(nameTmp)
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ struct StartGroupConversationFragment: View {
|
|||
Button(action: {
|
||||
startConversationViewModel.createGroupChatRoom()
|
||||
}, label: {
|
||||
Text("dialog_ok")
|
||||
Text("dialog_confirm")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -551,6 +551,12 @@ struct UIList: UIViewRepresentable {
|
|||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
let eventLogMessage = parent.conversationViewModel.conversationMessagesSection[0].rows[indexPath.row]
|
||||
|
||||
guard !eventLogMessage.message.isRetracted && eventLogMessage.eventModel.eventLogType == .ConferenceChatMessage else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let archiveAction = UIContextualAction(style: .normal, title: "") { _, _, completionHandler in
|
||||
self.parent.conversationViewModel.replyToMessage(index: indexPath.row, isMessageTextFocused: Binding(
|
||||
get: { self.parent.isMessageTextFocused },
|
||||
|
|
|
|||
|
|
@ -46,9 +46,12 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
@Published var isMuted: Bool
|
||||
@Published var isEphemeral: Bool
|
||||
@Published var encryptionEnabled: Bool
|
||||
@Published var lastMessagePrefixText: String
|
||||
@Published var lastMessageText: String
|
||||
@Published var lastMessageIcon: String
|
||||
@Published var lastMessageIsOutgoing: Bool
|
||||
@Published var lastMessageState: Int
|
||||
@Published var lastMessageInItalic: Bool
|
||||
@Published var unreadMessagesCount: Int
|
||||
@Published var avatarModel: ContactAvatarModel
|
||||
|
||||
|
|
@ -138,11 +141,17 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
|
||||
self.lastMessage = nil
|
||||
|
||||
self.lastMessagePrefixText = ""
|
||||
|
||||
self.lastMessageText = ""
|
||||
|
||||
self.lastMessageIcon = ""
|
||||
|
||||
self.lastMessageIsOutgoing = false
|
||||
|
||||
self.lastMessageState = 0
|
||||
|
||||
self.lastMessageInItalic = false
|
||||
|
||||
self.unreadMessagesCount = chatRoom.unreadMessagesCount
|
||||
|
||||
|
|
@ -294,8 +303,10 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
fromAddressFriend = nil
|
||||
}
|
||||
|
||||
var lastMessageTextTmp = (fromAddressFriend ?? "")
|
||||
+ (lastMessage!.contents.first(where: {$0.isText == true})?.utf8Text ?? (lastMessage!.contents.first(where: {$0.isFile == true || $0.isFileTransfer == true})?.name ?? ""))
|
||||
let lastMessagePrefixTextTmp = (fromAddressFriend ?? "")
|
||||
var lastMessageTextTmp = (lastMessage!.contents.first(where: {$0.isText == true})?.utf8Text ?? (lastMessage!.contents.first(where: {$0.isFile == true || $0.isFileTransfer == true})?.name ?? ""))
|
||||
var lastMessageIconTmp = ""
|
||||
var lastMessageInItalicTmp = false
|
||||
|
||||
if lastMessage!.contents.first != nil && lastMessage!.contents.first!.isIcalendar == true {
|
||||
if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: lastMessage!.contents.first!) {
|
||||
|
|
@ -308,10 +319,30 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
} else if conferenceInfo.state == .Cancelled {
|
||||
lastMessageTextTmp = String(localized: "message_meeting_invitation_cancelled_notification")
|
||||
}
|
||||
|
||||
lastMessageIconTmp = "calendar"
|
||||
|
||||
lastMessageInItalicTmp = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastMessage!.contents.first(where: {$0.isFile == true || $0.isFileTransfer == true})?.name != nil) {
|
||||
lastMessageIconTmp = "file"
|
||||
} else if lastMessage!.isReply {
|
||||
lastMessageIconTmp = "reply"
|
||||
} else if lastMessage!.isForward {
|
||||
lastMessageIconTmp = "forward"
|
||||
}
|
||||
|
||||
if lastMessage!.isRetracted {
|
||||
lastMessageTextTmp += lastMessage!.isOutgoing ? String(localized: "conversation_message_content_deleted_by_us_label") : String(localized: "conversation_message_content_deleted_label")
|
||||
|
||||
lastMessageIconTmp = "trash"
|
||||
|
||||
lastMessageInItalicTmp = true
|
||||
}
|
||||
|
||||
let lastMessageIsOutgoingTmp = lastMessage?.isOutgoing ?? false
|
||||
|
||||
let lastUpdateTimeTmp = lastMessage?.time ?? chatRoom.lastUpdateTime
|
||||
|
|
@ -319,13 +350,19 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
let lastMessageStateTmp = lastMessage?.state.rawValue ?? 0
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.lastMessagePrefixText = lastMessagePrefixTextTmp
|
||||
|
||||
self.lastMessageText = lastMessageTextTmp
|
||||
|
||||
self.lastMessageIcon = lastMessageIconTmp
|
||||
|
||||
self.lastMessageIsOutgoing = lastMessageIsOutgoingTmp
|
||||
|
||||
self.lastUpdateTime = lastUpdateTimeTmp
|
||||
|
||||
self.lastMessageState = lastMessageStateTmp
|
||||
|
||||
self.lastMessageInItalic = lastMessageInItalicTmp
|
||||
}
|
||||
|
||||
getUnreadMessagesCount()
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ public struct Message: Identifiable, Hashable {
|
|||
public var status: Status?
|
||||
public var createdAt: Date
|
||||
public var isOutgoing: Bool
|
||||
public var isEditable: Bool
|
||||
public var isRetractable: Bool
|
||||
public var isEdited: Bool
|
||||
public var isRetracted: Bool
|
||||
public var dateReceived: time_t
|
||||
|
||||
public var address: String
|
||||
|
|
@ -94,6 +98,10 @@ public struct Message: Identifiable, Hashable {
|
|||
status: Status? = nil,
|
||||
createdAt: Date = Date(),
|
||||
isOutgoing: Bool,
|
||||
isEditable: Bool,
|
||||
isRetractable: Bool,
|
||||
isEdited: Bool,
|
||||
isRetracted: Bool,
|
||||
dateReceived: time_t,
|
||||
address: String,
|
||||
isFirstMessage: Bool = false,
|
||||
|
|
@ -116,6 +124,10 @@ public struct Message: Identifiable, Hashable {
|
|||
self.status = status
|
||||
self.createdAt = createdAt
|
||||
self.isOutgoing = isOutgoing
|
||||
self.isEditable = isEditable
|
||||
self.isRetractable = isRetractable
|
||||
self.isEdited = isEdited
|
||||
self.isRetracted = isRetracted
|
||||
self.dateReceived = dateReceived
|
||||
self.isFirstMessage = isFirstMessage
|
||||
self.address = address
|
||||
|
|
@ -163,6 +175,10 @@ public struct Message: Identifiable, Hashable {
|
|||
status: status,
|
||||
createdAt: draft.createdAt,
|
||||
isOutgoing: draft.isOutgoing,
|
||||
isEditable: draft.isEditable,
|
||||
isRetractable: draft.isRetractable,
|
||||
isEdited: draft.isEdited,
|
||||
isRetracted: draft.isRetracted,
|
||||
dateReceived: draft.dateReceived,
|
||||
address: draft.address,
|
||||
isFirstMessage: draft.isFirstMessage,
|
||||
|
|
@ -184,7 +200,7 @@ extension Message {
|
|||
|
||||
extension Message: Equatable {
|
||||
public static func == (lhs: Message, rhs: Message) -> Bool {
|
||||
lhs.id == rhs.id && lhs.status == rhs.status && lhs.isFirstMessage == rhs.isFirstMessage && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions && lhs.ephemeralExpireTime == rhs.ephemeralExpireTime && lhs.attachments == rhs.attachments
|
||||
lhs.id == rhs.id && lhs.status == rhs.status && lhs.isEdited == rhs.isEdited && lhs.isRetracted == rhs.isRetracted && lhs.isFirstMessage == rhs.isFirstMessage && lhs.text == rhs.text && lhs.attachments == rhs.attachments && lhs.replyMessage?.text == rhs.replyMessage?.text && lhs.replyMessage?.isRetracted == rhs.replyMessage?.isRetracted && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions && lhs.ephemeralExpireTime == rhs.ephemeralExpireTime
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -211,6 +227,10 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
|
|||
public var isFirstMessage: Bool
|
||||
public var text: String
|
||||
public var isOutgoing: Bool
|
||||
public var isEditable: Bool
|
||||
public var isRetractable: Bool
|
||||
public var isEdited: Bool
|
||||
public var isRetracted: Bool
|
||||
public var dateReceived: time_t
|
||||
public var attachmentsNames: String
|
||||
public var attachments: [Attachment]
|
||||
|
|
@ -221,6 +241,10 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
|
|||
isFirstMessage: Bool = false,
|
||||
text: String = "",
|
||||
isOutgoing: Bool,
|
||||
isEditable: Bool,
|
||||
isRetractable: Bool,
|
||||
isEdited: Bool,
|
||||
isRetracted: Bool,
|
||||
dateReceived: time_t,
|
||||
attachmentsNames: String = "",
|
||||
attachments: [Attachment] = [],
|
||||
|
|
@ -231,6 +255,10 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
|
|||
self.isFirstMessage = isFirstMessage
|
||||
self.text = text
|
||||
self.isOutgoing = isOutgoing
|
||||
self.isEditable = isEditable
|
||||
self.isRetractable = isRetractable
|
||||
self.isEdited = isEdited
|
||||
self.isRetracted = isRetracted
|
||||
self.dateReceived = dateReceived
|
||||
self.attachmentsNames = attachmentsNames
|
||||
self.attachments = attachments
|
||||
|
|
@ -238,20 +266,24 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
|
|||
}
|
||||
|
||||
func toMessage() -> Message {
|
||||
Message(id: id, isOutgoing: isOutgoing, dateReceived: dateReceived, address: address, isFirstMessage: isFirstMessage, text: text, attachments: attachments, recording: recording)
|
||||
Message(id: id, isOutgoing: isOutgoing, isEditable: isEditable, isRetractable: isRetractable, isEdited: isEdited, isRetracted: isRetracted, dateReceived: dateReceived, address: address, isFirstMessage: isFirstMessage, text: text, attachments: attachments, recording: recording)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Message {
|
||||
|
||||
func toReplyMessage() -> ReplyMessage {
|
||||
ReplyMessage(id: id, address: address, isFirstMessage: isFirstMessage, text: text, isOutgoing: isOutgoing, dateReceived: dateReceived, attachments: attachments, recording: recording)
|
||||
ReplyMessage(id: id, address: address, isFirstMessage: isFirstMessage, text: text, isOutgoing: isOutgoing, isEditable: isEditable, isRetractable: isRetractable, isEdited: isEdited, isRetracted: isRetracted, dateReceived: dateReceived, attachments: attachments, recording: recording)
|
||||
}
|
||||
}
|
||||
|
||||
public struct DraftMessage {
|
||||
public var id: String?
|
||||
public let isOutgoing: Bool
|
||||
public let isEditable: Bool
|
||||
public let isRetractable: Bool
|
||||
public let isEdited: Bool
|
||||
public let isRetracted: Bool
|
||||
public var dateReceived: time_t
|
||||
public let address: String
|
||||
public let isFirstMessage: Bool
|
||||
|
|
@ -265,6 +297,10 @@ public struct DraftMessage {
|
|||
|
||||
public init(id: String? = nil,
|
||||
isOutgoing: Bool,
|
||||
isEditable: Bool,
|
||||
isRetractable: Bool,
|
||||
isEdited: Bool,
|
||||
isRetracted: Bool,
|
||||
dateReceived: time_t,
|
||||
address: String,
|
||||
isFirstMessage: Bool,
|
||||
|
|
@ -278,6 +314,10 @@ public struct DraftMessage {
|
|||
) {
|
||||
self.id = id
|
||||
self.isOutgoing = isOutgoing
|
||||
self.isEditable = isEditable
|
||||
self.isRetractable = isRetractable
|
||||
self.isEdited = isEdited
|
||||
self.isRetracted = isRetracted
|
||||
self.dateReceived = dateReceived
|
||||
self.address = address
|
||||
self.isFirstMessage = isFirstMessage
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ class ConversationViewModel: ObservableObject {
|
|||
@Published var selectedMessageToPlayVoiceRecording: EventLogMessage?
|
||||
@Published var selectedMessage: EventLogMessage?
|
||||
@Published var messageToReply: EventLogMessage?
|
||||
@Published var messageToEdit: EventLogMessage?
|
||||
|
||||
@Published var sheetCategories: [SheetCategory] = []
|
||||
|
||||
|
|
@ -171,7 +172,152 @@ class ConversationViewModel: ObservableObject {
|
|||
self.getEventMessage(eventLog: eventLog)
|
||||
}, onEphemeralMessageDeleted: {(_: ChatRoom, eventLog: EventLog) in
|
||||
self.removeMessage(eventLog)
|
||||
}, onMessageContentEdited: {(chatRoom: ChatRoom, message: ChatMessage) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
|
||||
if let displayedConversation = self.sharedMainViewModel.displayedConversation {
|
||||
displayedConversation.getContentTextMessage(chatRoom: displayedConversation.chatRoom)
|
||||
}
|
||||
|
||||
var attachmentNameList: String = ""
|
||||
var attachmentList: [Attachment] = []
|
||||
var contentText = ""
|
||||
|
||||
if !message.contents.isEmpty {
|
||||
message.contents.forEach { content in
|
||||
if content.isText && content.name == nil {
|
||||
contentText = content.utf8Text ?? ""
|
||||
} else if content.name != nil && !content.name!.isEmpty {
|
||||
if content.filePath == nil || content.filePath!.isEmpty {
|
||||
let path = URL(string: self.getNewFilePath(name: content.name ?? ""))
|
||||
|
||||
if path != nil {
|
||||
let attachment =
|
||||
Attachment(
|
||||
id: UUID().uuidString,
|
||||
name: content.name!,
|
||||
url: path!,
|
||||
type: .fileTransfer,
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
}
|
||||
} else {
|
||||
if content.type != "video" {
|
||||
let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/")
|
||||
let path = URL(string: self.getNewFilePath(name: filePathSep[1]))
|
||||
|
||||
var typeTmp: AttachmentType = .other
|
||||
|
||||
switch content.type {
|
||||
case "image":
|
||||
typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
|
||||
case "audio":
|
||||
typeTmp = content.isVoiceRecording ? .voiceRecording : .audio
|
||||
case "application":
|
||||
typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other
|
||||
case "text":
|
||||
typeTmp = .text
|
||||
default:
|
||||
typeTmp = .other
|
||||
}
|
||||
|
||||
if path != nil {
|
||||
let attachment =
|
||||
Attachment(
|
||||
id: UUID().uuidString,
|
||||
name: content.name!,
|
||||
url: path!,
|
||||
type: typeTmp,
|
||||
duration: typeTmp == .voiceRecording ? content.fileDuration : 0,
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
if typeTmp != .voiceRecording {
|
||||
DispatchQueue.main.async {
|
||||
if !attachment.full.pathExtension.isEmpty {
|
||||
self.attachments.append(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if content.type == "video" {
|
||||
let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/")
|
||||
let path = URL(string: self.getNewFilePath(name: filePathSep[1]))
|
||||
let pathThumbnail = URL(string: self.generateThumbnail(name: filePathSep[1]))
|
||||
|
||||
if path != nil && pathThumbnail != nil {
|
||||
let attachment =
|
||||
Attachment(
|
||||
id: UUID().uuidString,
|
||||
name: content.name!,
|
||||
thumbnail: pathThumbnail!,
|
||||
full: path!,
|
||||
type: .video,
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
DispatchQueue.main.async {
|
||||
if !attachment.full.pathExtension.isEmpty {
|
||||
self.attachments.append(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !attachmentNameList.isEmpty {
|
||||
attachmentNameList = String(attachmentNameList.dropFirst(2))
|
||||
}
|
||||
|
||||
let indexReplyMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.replyMessage?.id == message.messageId})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.text = contentText
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.isEdited = true
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.attachments = attachmentList
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.attachmentsNames = attachmentNameList
|
||||
}
|
||||
|
||||
if indexReplyMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.text = contentText
|
||||
}
|
||||
}
|
||||
}, onMessageRetracted: {(chatRoom: ChatRoom, message: ChatMessage) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
let indexReplyMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.replyMessage?.id == message.messageId})
|
||||
|
||||
if let displayedConversation = self.sharedMainViewModel.displayedConversation {
|
||||
displayedConversation.getContentTextMessage(chatRoom: displayedConversation.chatRoom)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.text = ""
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.isRetracted = true
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.attachments = []
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.attachmentsNames = ""
|
||||
}
|
||||
|
||||
if indexReplyMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.text = ""
|
||||
self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.isRetracted = true
|
||||
self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.attachments = []
|
||||
self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.attachmentsNames = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.chatRoomDelegateHolder = ChatRoomDelegateHolder(chatroom: chatRoom, delegate: chatRoomDelegate)
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +366,12 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
self.coreContext.doOnCoreQueue { _ in
|
||||
let chatMessageDelegate = ChatMessageDelegateStub(onMsgStateChanged: { (message: ChatMessage, msgState: ChatMessage.State) in
|
||||
if msgState == .Queued || msgState == .PendingDelivery {
|
||||
if let eventLog = message.eventLog {
|
||||
self.getNewMessages(eventLogs: [eventLog])
|
||||
}
|
||||
return
|
||||
}
|
||||
var statusTmp: Message.Status?
|
||||
switch message.state {
|
||||
case .InProgress:
|
||||
|
|
@ -324,12 +476,12 @@ class ConversationViewModel: ObservableObject {
|
|||
if !self.conversationMessagesSection.isEmpty,
|
||||
!self.conversationMessagesSection[0].rows.isEmpty {
|
||||
let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false})
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let indexMessageEventLogId = indexMessageEventLogId, !self.conversationMessagesSection.isEmpty, !self.conversationMessagesSection[0].rows.isEmpty, self.conversationMessagesSection[0].rows.count > indexMessageEventLogId {
|
||||
self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId
|
||||
}
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
if let indexMessage = indexMessage, !self.conversationMessagesSection.isEmpty, !self.conversationMessagesSection[0].rows.isEmpty, self.conversationMessagesSection[0].rows.count > indexMessage {
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.status = statusTmp ?? .error
|
||||
}
|
||||
|
|
@ -378,16 +530,21 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
}, onEphemeralMessageTimerStarted: { (message: ChatMessage) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
let ephemeralExpireTimeTmp = message.ephemeralExpireTime
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
||||
if !self.conversationMessagesSection.isEmpty,
|
||||
!self.conversationMessagesSection[0].rows.isEmpty,
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: { $0.eventModel.eventLogId == message.messageId }),
|
||||
indexMessage < self.conversationMessagesSection[0].rows.count {
|
||||
|
||||
let ephemeralExpireTimeTmp = message.ephemeralExpireTime
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.chatMessageDelegateHolders.removeAll()
|
||||
|
||||
self.chatMessageDelegateHolders.append(ChatMessageDelegateHolder(message: message, delegate: chatMessageDelegate))
|
||||
}
|
||||
}
|
||||
|
|
@ -541,6 +698,10 @@ class ConversationViewModel: ObservableObject {
|
|||
id: UUID().uuidString,
|
||||
status: nil,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: false,
|
||||
dateReceived: 0,
|
||||
address: "",
|
||||
isFirstMessage: false,
|
||||
|
|
@ -701,6 +862,8 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
let contentReplyText = chatMessage.replyMessage?.utf8Text ?? ""
|
||||
|
||||
let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false
|
||||
|
||||
var attachmentNameReplyList: String = ""
|
||||
|
||||
chatMessage.replyMessage?.contents.forEach { content in
|
||||
|
|
@ -718,7 +881,11 @@ class ConversationViewModel: ObservableObject {
|
|||
address: addressReplyCleaned?.asStringUriOnly() ?? "",
|
||||
isFirstMessage: false,
|
||||
text: contentReplyText,
|
||||
isOutgoing: false,
|
||||
isOutgoing: chatMessage.replyMessage!.isOutgoing,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: isReplyRetracted,
|
||||
dateReceived: 0,
|
||||
attachmentsNames: attachmentNameReplyList,
|
||||
attachments: []
|
||||
|
|
@ -732,6 +899,10 @@ class ConversationViewModel: ObservableObject {
|
|||
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
||||
status: statusTmp,
|
||||
isOutgoing: chatMessage.isOutgoing,
|
||||
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||
isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false,
|
||||
isEdited: chatMessage.isEdited,
|
||||
isRetracted: chatMessage.isRetracted,
|
||||
dateReceived: chatMessage.time,
|
||||
address: addressCleaned?.asStringUriOnly() ?? "",
|
||||
isFirstMessage: isFirstMessageTmp,
|
||||
|
|
@ -785,6 +956,10 @@ class ConversationViewModel: ObservableObject {
|
|||
id: UUID().uuidString,
|
||||
status: nil,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: false,
|
||||
dateReceived: 0,
|
||||
address: "",
|
||||
isFirstMessage: false,
|
||||
|
|
@ -944,6 +1119,8 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
let contentReplyText = chatMessage.replyMessage?.utf8Text ?? ""
|
||||
|
||||
let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false
|
||||
|
||||
var attachmentNameReplyList: String = ""
|
||||
|
||||
chatMessage.replyMessage?.contents.forEach { content in
|
||||
|
|
@ -961,7 +1138,11 @@ class ConversationViewModel: ObservableObject {
|
|||
address: addressReplyCleaned?.asStringUriOnly() ?? "",
|
||||
isFirstMessage: false,
|
||||
text: contentReplyText,
|
||||
isOutgoing: false,
|
||||
isOutgoing: chatMessage.replyMessage!.isOutgoing,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: isReplyRetracted,
|
||||
dateReceived: 0,
|
||||
attachmentsNames: attachmentNameReplyList,
|
||||
attachments: []
|
||||
|
|
@ -975,6 +1156,10 @@ class ConversationViewModel: ObservableObject {
|
|||
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
||||
status: statusTmp,
|
||||
isOutgoing: chatMessage.isOutgoing,
|
||||
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||
isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false,
|
||||
isEdited: chatMessage.isEdited,
|
||||
isRetracted: chatMessage.isRetracted,
|
||||
dateReceived: chatMessage.time,
|
||||
address: addressCleaned?.asStringUriOnly() ?? "",
|
||||
isFirstMessage: isFirstMessageTmp,
|
||||
|
|
@ -1045,6 +1230,10 @@ class ConversationViewModel: ObservableObject {
|
|||
id: UUID().uuidString,
|
||||
status: nil,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: false,
|
||||
dateReceived: 0,
|
||||
address: "",
|
||||
isFirstMessage: false,
|
||||
|
|
@ -1218,6 +1407,8 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
let contentReplyText = chatMessage.replyMessage?.utf8Text ?? ""
|
||||
|
||||
let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false
|
||||
|
||||
var attachmentNameReplyList: String = ""
|
||||
|
||||
chatMessage.replyMessage?.contents.forEach { content in
|
||||
|
|
@ -1235,7 +1426,11 @@ class ConversationViewModel: ObservableObject {
|
|||
address: addressReplyCleaned != nil ? addressReplyCleaned!.asStringUriOnly() : "",
|
||||
isFirstMessage: false,
|
||||
text: contentReplyText,
|
||||
isOutgoing: false,
|
||||
isOutgoing: chatMessage.replyMessage!.isOutgoing,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: isReplyRetracted,
|
||||
dateReceived: 0,
|
||||
attachmentsNames: attachmentNameReplyList,
|
||||
attachments: []
|
||||
|
|
@ -1250,6 +1445,10 @@ class ConversationViewModel: ObservableObject {
|
|||
appData: chatMessage.appdata ?? "",
|
||||
status: statusTmp,
|
||||
isOutgoing: chatMessage.isOutgoing,
|
||||
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||
isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false,
|
||||
isEdited: chatMessage.isEdited,
|
||||
isRetracted: chatMessage.isRetracted,
|
||||
dateReceived: chatMessage.time,
|
||||
address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "",
|
||||
isFirstMessage: isFirstMessageTmp,
|
||||
|
|
@ -1450,6 +1649,8 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
let contentReplyText = chatMessage.replyMessage?.utf8Text ?? ""
|
||||
|
||||
let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false
|
||||
|
||||
var attachmentNameReplyList: String = ""
|
||||
|
||||
chatMessage.replyMessage?.contents.forEach { content in
|
||||
|
|
@ -1467,7 +1668,11 @@ class ConversationViewModel: ObservableObject {
|
|||
address: addressReplyCleaned != nil ? addressReplyCleaned!.asStringUriOnly() : "",
|
||||
isFirstMessage: false,
|
||||
text: contentReplyText,
|
||||
isOutgoing: false,
|
||||
isOutgoing: chatMessage.replyMessage!.isOutgoing,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: isReplyRetracted,
|
||||
dateReceived: 0,
|
||||
attachmentsNames: attachmentNameReplyList,
|
||||
attachments: []
|
||||
|
|
@ -1482,6 +1687,10 @@ class ConversationViewModel: ObservableObject {
|
|||
appData: chatMessage.appdata ?? "",
|
||||
status: statusTmp,
|
||||
isOutgoing: chatMessage.isOutgoing,
|
||||
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||
isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false,
|
||||
isEdited: chatMessage.isEdited,
|
||||
isRetracted: chatMessage.isRetracted,
|
||||
dateReceived: chatMessage.time,
|
||||
address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "",
|
||||
isFirstMessage: isFirstMessageTmp,
|
||||
|
|
@ -1523,6 +1732,10 @@ class ConversationViewModel: ObservableObject {
|
|||
id: UUID().uuidString,
|
||||
status: nil,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: false,
|
||||
dateReceived: 0,
|
||||
address: "",
|
||||
isFirstMessage: false,
|
||||
|
|
@ -1550,6 +1763,9 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func replyToMessage(index: Int, isMessageTextFocused: Binding<Bool>) {
|
||||
if self.messageToEdit != nil {
|
||||
self.messageToEdit = nil
|
||||
}
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
let messageToReplyTmp = self.conversationMessagesSection[0].rows[index]
|
||||
DispatchQueue.main.async {
|
||||
|
|
@ -1561,6 +1777,21 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func editMessage(chatMessage: EventLogMessage, isMessageTextFocused: Binding<Bool>) {
|
||||
if self.messageToReply != nil {
|
||||
self.messageToReply = nil
|
||||
}
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
let messageToEditTmp = chatMessage
|
||||
DispatchQueue.main.async {
|
||||
withAnimation(.linear(duration: 0.15)) {
|
||||
self.messageToEdit = messageToEditTmp
|
||||
}
|
||||
isMessageTextFocused.wrappedValue = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resendMessage(chatMessage: EventLogMessage) {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
if let message = chatMessage.eventModel.eventLog.chatMessage {
|
||||
|
|
@ -1616,6 +1847,10 @@ class ConversationViewModel: ObservableObject {
|
|||
id: UUID().uuidString,
|
||||
status: nil,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: false,
|
||||
dateReceived: 0,
|
||||
address: "",
|
||||
isFirstMessage: false,
|
||||
|
|
@ -1775,6 +2010,8 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
let contentReplyText = chatMessage.replyMessage?.utf8Text ?? ""
|
||||
|
||||
let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false
|
||||
|
||||
var attachmentNameReplyList: String = ""
|
||||
|
||||
chatMessage.replyMessage?.contents.forEach { content in
|
||||
|
|
@ -1792,7 +2029,11 @@ class ConversationViewModel: ObservableObject {
|
|||
address: addressReplyCleaned?.asStringUriOnly() ?? "",
|
||||
isFirstMessage: false,
|
||||
text: contentReplyText,
|
||||
isOutgoing: false,
|
||||
isOutgoing: chatMessage.replyMessage!.isOutgoing,
|
||||
isEditable: false,
|
||||
isRetractable: false,
|
||||
isEdited: false,
|
||||
isRetracted: isReplyRetracted,
|
||||
dateReceived: 0,
|
||||
attachmentsNames: attachmentNameReplyList,
|
||||
attachments: []
|
||||
|
|
@ -1806,6 +2047,10 @@ class ConversationViewModel: ObservableObject {
|
|||
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
||||
status: statusTmp,
|
||||
isOutgoing: chatMessage.isOutgoing,
|
||||
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||
isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false,
|
||||
isEdited: chatMessage.isEdited,
|
||||
isRetracted: chatMessage.isRetracted,
|
||||
dateReceived: chatMessage.time,
|
||||
address: addressCleaned?.asStringUriOnly() ?? "",
|
||||
isFirstMessage: isFirstMessageTmp,
|
||||
|
|
@ -1870,6 +2115,8 @@ class ConversationViewModel: ObservableObject {
|
|||
if chatMessageToReply != nil {
|
||||
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createReplyMessage(message: chatMessageToReply!)
|
||||
}
|
||||
} else if let chatMessage = self.messageToEdit?.eventModel.eventLog.chatMessage {
|
||||
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createReplacesMessage(message: chatMessage)
|
||||
} else {
|
||||
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createEmptyMessage()
|
||||
}
|
||||
|
|
@ -1942,15 +2189,20 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
if message != nil && !message!.contents.isEmpty {
|
||||
if let message = message , !message.contents.isEmpty {
|
||||
Log.info("[ConversationViewModel] Sending message")
|
||||
message!.send()
|
||||
|
||||
self.addChatMessageDelegate(message: message)
|
||||
message.send()
|
||||
|
||||
self.sharedMainViewModel.displayedConversation!.chatRoom.stopComposing()
|
||||
}
|
||||
|
||||
Log.info("[ConversationViewModel] Message sent, re-setting defaults")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.messageToReply = nil
|
||||
self.messageToEdit = nil
|
||||
withAnimation {
|
||||
self.mediasToSend.removeAll()
|
||||
}
|
||||
|
|
@ -2001,25 +2253,23 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func resetDisplayedChatRoom() {
|
||||
if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty {
|
||||
if let displayedConversation = self.sharedMainViewModel.displayedConversation {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
let nilParams: ConferenceParams? = nil
|
||||
if let newChatRoom = core.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: displayedConversation.chatRoom.peerAddress, participants: nil) {
|
||||
if LinphoneUtils.getChatRoomId(room: newChatRoom) == displayedConversation.id {
|
||||
self.addConversationDelegate(chatRoom: newChatRoom)
|
||||
let conversation = ConversationModel(chatRoom: newChatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.sharedMainViewModel.displayedConversation = conversation
|
||||
}
|
||||
self.computeComposingLabel()
|
||||
let historyEventsSizeTmp = newChatRoom.historyEventsSize
|
||||
if self.displayedConversationHistorySize < historyEventsSizeTmp {
|
||||
let eventLogList = newChatRoom.getHistoryRangeEvents(begin: 0, end: historyEventsSizeTmp - self.displayedConversationHistorySize)
|
||||
|
||||
if !eventLogList.isEmpty {
|
||||
self.getNewMessages(eventLogs: eventLogList)
|
||||
}
|
||||
if let displayedConversation = self.sharedMainViewModel.displayedConversation {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
let nilParams: ConferenceParams? = nil
|
||||
if let newChatRoom = core.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: displayedConversation.chatRoom.peerAddress, participants: nil) {
|
||||
if LinphoneUtils.getChatRoomId(room: newChatRoom) == displayedConversation.id {
|
||||
self.addConversationDelegate(chatRoom: newChatRoom)
|
||||
let conversation = ConversationModel(chatRoom: newChatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.sharedMainViewModel.displayedConversation = conversation
|
||||
}
|
||||
self.computeComposingLabel()
|
||||
let historyEventsSizeTmp = newChatRoom.historyEventsSize
|
||||
if self.displayedConversationHistorySize < historyEventsSizeTmp {
|
||||
let eventLogList = newChatRoom.getHistoryRangeEvents(begin: 0, end: historyEventsSizeTmp - self.displayedConversationHistorySize)
|
||||
|
||||
if !eventLogList.isEmpty {
|
||||
self.getNewMessages(eventLogs: eventLogList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2277,7 +2527,7 @@ class ConversationViewModel: ObservableObject {
|
|||
dispatchGroup.enter()
|
||||
ContactAvatarModel.getAvatarModelFromAddress(address: chatMessageReaction.fromAddress!) { avatarResult in
|
||||
if let account = core.defaultAccount,
|
||||
let contactAddress = account.contactAddress,
|
||||
let contactAddress = account.params?.identityAddress,
|
||||
contactAddress.asStringUriOnly().contains(avatarResult.address) {
|
||||
|
||||
let innerSheetCat = InnerSheetCategory(
|
||||
|
|
@ -2369,10 +2619,20 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func compose() {
|
||||
func compose(stop: Bool, cachedConversation: ConversationModel? = nil) {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
if self.sharedMainViewModel.displayedConversation != nil {
|
||||
self.sharedMainViewModel.displayedConversation!.chatRoom.compose()
|
||||
if let displayedConversation = self.sharedMainViewModel.displayedConversation {
|
||||
if stop {
|
||||
displayedConversation.chatRoom.stopComposing()
|
||||
} else {
|
||||
displayedConversation.chatRoom.composeTextMessage()
|
||||
}
|
||||
} else if let displayedConversation = cachedConversation {
|
||||
if stop {
|
||||
displayedConversation.chatRoom.stopComposing()
|
||||
} else {
|
||||
displayedConversation.chatRoom.composeTextMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2671,17 +2931,39 @@ class ConversationViewModel: ObservableObject {
|
|||
if let displayedConversation = self.sharedMainViewModel.displayedConversation,
|
||||
let selectedMessage = self.selectedMessage,
|
||||
let chatMessage = selectedMessage.eventModel.eventLog.chatMessage {
|
||||
|
||||
let indexReplyMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.replyMessage?.id == chatMessage.messageId})
|
||||
displayedConversation.chatRoom.deleteMessage(message: chatMessage)
|
||||
|
||||
displayedConversation.getContentTextMessage(chatRoom: displayedConversation.chatRoom)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let sectionIndex = self.conversationMessagesSection.firstIndex(where: { $0.chatRoomID == displayedConversation.id }),
|
||||
let rowIndex = self.conversationMessagesSection[sectionIndex].rows.firstIndex(of: selectedMessage) {
|
||||
self.conversationMessagesSection[sectionIndex].rows.remove(at: rowIndex)
|
||||
|
||||
if indexReplyMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage = nil
|
||||
}
|
||||
}
|
||||
self.selectedMessage = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteMessageForEveryone(){
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
if let displayedConversation = self.sharedMainViewModel.displayedConversation,
|
||||
let selectedMessage = self.selectedMessage,
|
||||
let chatMessage = selectedMessage.eventModel.eventLog.chatMessage {
|
||||
displayedConversation.chatRoom.retractMessage(message: chatMessage)
|
||||
DispatchQueue.main.async {
|
||||
self.selectedMessage = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// swiftlint:enable line_length
|
||||
// swiftlint:enable type_body_length
|
||||
|
|
@ -2780,6 +3062,21 @@ class VoiceRecordPlayerManager {
|
|||
}
|
||||
}
|
||||
|
||||
func seekVoiceRecordPlayer(percent: Double) {
|
||||
guard !isPlayerClosed(),
|
||||
let player = voiceRecordPlayer,
|
||||
player.duration > 0 else { return }
|
||||
|
||||
let clamped = max(0, min(percent, 100))
|
||||
|
||||
let ratio = clamped / 100.0
|
||||
|
||||
let timeMs = Int(Double(player.duration) * ratio)
|
||||
|
||||
print("Seek voice record to \(clamped)% (\(timeMs) ms)")
|
||||
try? player.seek(timeMs: timeMs)
|
||||
}
|
||||
|
||||
func getSpeakerSoundCard(core: Core) -> String? {
|
||||
var speakerCard: String? = nil
|
||||
var earpieceCard: String? = nil
|
||||
|
|
|
|||
|
|
@ -89,16 +89,16 @@ class ConversationsListViewModel: ObservableObject {
|
|||
fromAddressFriend = nil
|
||||
}
|
||||
|
||||
let lastMessageTextTmp = (fromAddressFriend ?? "") + (lastMessage.contents.first(where: { $0.isText })?.utf8Text ?? (lastMessage.contents.first(where: { $0.isFile || $0.isFileTransfer })?.name ?? ""))
|
||||
let lastMessagePrefixTextTmp = (fromAddressFriend ?? "")
|
||||
|
||||
if let index = self.conversationsList.firstIndex(where: { $0.chatRoom === conversationModel.chatRoom }) {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.lastMessageText = lastMessageTextTmp
|
||||
self.conversationsList[index].lastMessageText = lastMessageTextTmp
|
||||
conversationModel.lastMessagePrefixText = lastMessagePrefixTextTmp
|
||||
self.conversationsList[index].lastMessagePrefixText = lastMessagePrefixTextTmp
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.lastMessageText = lastMessageTextTmp
|
||||
conversationModel.lastMessagePrefixText = lastMessagePrefixTextTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -148,16 +148,16 @@ class ConversationsListViewModel: ObservableObject {
|
|||
fromAddressFriend = nil
|
||||
}
|
||||
|
||||
let lastMessageTextTmp = (fromAddressFriend ?? "") + (lastMessage.contents.first(where: { $0.isText })?.utf8Text ?? (lastMessage.contents.first(where: { $0.isFile || $0.isFileTransfer })?.name ?? ""))
|
||||
let lastMessagePrefixTextTmp = (fromAddressFriend ?? "")
|
||||
|
||||
if let index = self.conversationsList.firstIndex(where: { $0.chatRoom === conversationModel.chatRoom }) {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.lastMessageText = lastMessageTextTmp
|
||||
self.conversationsList[index].lastMessageText = lastMessageTextTmp
|
||||
conversationModel.lastMessagePrefixText = lastMessagePrefixTextTmp
|
||||
self.conversationsList[index].lastMessagePrefixText = lastMessagePrefixTextTmp
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
conversationModel.lastMessageText = lastMessageTextTmp
|
||||
conversationModel.lastMessagePrefixText = lastMessagePrefixTextTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -183,69 +183,76 @@ class ConversationsListViewModel: ObservableObject {
|
|||
|
||||
func addConversationDelegate() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
self.coreConversationDelegate = CoreDelegateStub(onMessagesReceived: { (core: Core, chatRoom: ChatRoom, _: [ChatMessage]) in
|
||||
if let defaultAddress = core.defaultAccount?.contactAddress,
|
||||
let localAddress = chatRoom.localAddress,
|
||||
defaultAddress.weakEqual(address2: localAddress) {
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom)
|
||||
model.getContentTextMessage(chatRoom: chatRoom)
|
||||
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
|
||||
DispatchQueue.main.async {
|
||||
if index != nil {
|
||||
self.conversationsList.remove(at: index!)
|
||||
}
|
||||
self.conversationsList.insert(model, at: 0)
|
||||
}
|
||||
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
||||
}
|
||||
}, onMessageSent: { (_: Core, chatRoom: ChatRoom, _: ChatMessage) in
|
||||
if let defaultAddress = core.defaultAccount?.contactAddress,
|
||||
let localAddress = chatRoom.localAddress,
|
||||
defaultAddress.weakEqual(address2: localAddress) {
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom)
|
||||
model.getContentTextMessage(chatRoom: chatRoom)
|
||||
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
|
||||
if index != nil {
|
||||
self.conversationsList[index!].chatMessageRemoveDelegate()
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if index != nil {
|
||||
self.conversationsList.remove(at: index!)
|
||||
}
|
||||
self.conversationsList.insert(model, at: 0)
|
||||
}
|
||||
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
||||
}
|
||||
}, onChatRoomRead: { (_: Core, chatRoom: ChatRoom) in
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom)
|
||||
model.getContentTextMessage(chatRoom: chatRoom)
|
||||
if let index = self.conversationsList.firstIndex(where: { $0.id == idTmp }) {
|
||||
DispatchQueue.main.async {
|
||||
self.conversationsList.remove(at: index)
|
||||
self.conversationsList.insert(model, at: index)
|
||||
self.coreConversationDelegate = CoreDelegateStub(
|
||||
onMessagesReceived: { (core: Core, chatRoom: ChatRoom, _: [ChatMessage]) in
|
||||
if let defaultAddress = core.defaultAccount?.params?.identityAddress,
|
||||
let localAddress = chatRoom.localAddress,
|
||||
defaultAddress.weakEqual(address2: localAddress) {
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom)
|
||||
model.getContentTextMessage(chatRoom: chatRoom)
|
||||
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
|
||||
DispatchQueue.main.async {
|
||||
if index != nil {
|
||||
self.conversationsList.remove(at: index!)
|
||||
}
|
||||
self.conversationsList.insert(model, at: 0)
|
||||
}
|
||||
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
||||
}
|
||||
}, onMessageSent: { (_: Core, chatRoom: ChatRoom, _: ChatMessage) in
|
||||
if let defaultAddress = core.defaultAccount?.params?.identityAddress,
|
||||
let localAddress = chatRoom.localAddress,
|
||||
defaultAddress.weakEqual(address2: localAddress) {
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom)
|
||||
model.getContentTextMessage(chatRoom: chatRoom)
|
||||
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
|
||||
if index != nil {
|
||||
self.conversationsList[index!].chatMessageRemoveDelegate()
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if index != nil {
|
||||
self.conversationsList.remove(at: index!)
|
||||
}
|
||||
self.conversationsList.insert(model, at: 0)
|
||||
}
|
||||
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
||||
}
|
||||
}, onChatRoomRead: { (_: Core, chatRoom: ChatRoom) in
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom)
|
||||
model.getContentTextMessage(chatRoom: chatRoom)
|
||||
if let index = self.conversationsList.firstIndex(where: { $0.id == idTmp }) {
|
||||
DispatchQueue.main.async {
|
||||
self.conversationsList.remove(at: index)
|
||||
self.conversationsList.insert(model, at: index)
|
||||
}
|
||||
}
|
||||
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
||||
}, onChatRoomStateChanged: { (core: Core, chatroom: ChatRoom, state: ChatRoom.State) in
|
||||
// Log.info("[ConversationsListViewModel] Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]")
|
||||
if let defaultAddress = core.defaultAccount?.params?.identityAddress,
|
||||
let localAddress = chatroom.localAddress,
|
||||
defaultAddress.weakEqual(address2: localAddress) {
|
||||
if core.globalState == .On {
|
||||
switch state {
|
||||
case .Created:
|
||||
self.addChatRoom(chatRoom: chatroom)
|
||||
case .Deleted:
|
||||
self.removeChatRoom(chatRoom: chatroom)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}, onMessageRetracted: { (core: Core, chatRoom: ChatRoom, message: ChatMessage) in
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom)
|
||||
model.getContentTextMessage(chatRoom: chatRoom)
|
||||
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
||||
}
|
||||
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
||||
}, onChatRoomStateChanged: { (core: Core, chatroom: ChatRoom, state: ChatRoom.State) in
|
||||
// Log.info("[ConversationsListViewModel] Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]")
|
||||
if let defaultAddress = core.defaultAccount?.contactAddress,
|
||||
let localAddress = chatroom.localAddress,
|
||||
defaultAddress.weakEqual(address2: localAddress) {
|
||||
if core.globalState == .On {
|
||||
switch state {
|
||||
case .Created:
|
||||
self.addChatRoom(chatRoom: chatroom)
|
||||
case .Deleted:
|
||||
self.removeChatRoom(chatRoom: chatroom)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
core.addDelegate(delegate: self.coreConversationDelegate!)
|
||||
}
|
||||
}
|
||||
|
|
@ -450,25 +457,25 @@ class ConversationsListViewModel: ObservableObject {
|
|||
func changeDisplayedChatRoom(conversationModel: ConversationModel) {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
let nilParams: ConferenceParams? = nil
|
||||
if let newChatRoom = core.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: conversationModel.chatRoom.peerAddress, participants: nil) {
|
||||
if LinphoneUtils.getChatRoomId(room: newChatRoom) == conversationModel.id {
|
||||
if self.sharedMainViewModel.displayedConversation == nil {
|
||||
DispatchQueue.main.async {
|
||||
withAnimation {
|
||||
self.sharedMainViewModel.displayedConversation = conversationModel
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.sharedMainViewModel.displayedConversation = nil
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
withAnimation {
|
||||
self.sharedMainViewModel.displayedConversation = conversationModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let newChatRoom = core.searchChatRoomByIdentifier(identifier: conversationModel.id) {
|
||||
if self.sharedMainViewModel.displayedConversation == nil {
|
||||
DispatchQueue.main.async {
|
||||
withAnimation {
|
||||
self.sharedMainViewModel.displayedConversation = conversationModel
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.sharedMainViewModel.displayedConversation = nil
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
withAnimation {
|
||||
self.sharedMainViewModel.displayedConversation = conversationModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.warn("\(ConversationsListViewModel.TAG) changeDisplayedChatRoom: no chat room found for identifier \(conversationModel.id)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,13 +86,3 @@ final class CustomHostingController<Content: View>: UIHostingController<Content>
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct LazyView<Content: View>: View {
|
||||
private let build: () -> Content
|
||||
public init(_ build: @autoclosure @escaping () -> Content) {
|
||||
self.build = build
|
||||
}
|
||||
public var body: Content {
|
||||
build()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ struct PopupUpdatePassword: View {
|
|||
updateAuthInfo()
|
||||
isShowUpdatePasswordPopup = false
|
||||
}, label: {
|
||||
Text("dialog_ok")
|
||||
Text("dialog_confirm")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ struct PopupView: View {
|
|||
var titleSecondButton: Text?
|
||||
var actionSecondButton: () -> Void
|
||||
|
||||
var titleThirdButton: Text?
|
||||
var actionThirdButton: () -> Void
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
VStack(alignment: .leading) {
|
||||
|
|
@ -49,40 +52,57 @@ struct PopupView: View {
|
|||
.padding(.bottom, 20)
|
||||
}
|
||||
|
||||
if titleFirstButton != nil {
|
||||
Button(action: {
|
||||
actionFirstButton()
|
||||
}, label: {
|
||||
titleFirstButton
|
||||
.default_text_style_orange_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
|
||||
if titleSecondButton != nil {
|
||||
Button(action: {
|
||||
actionSecondButton()
|
||||
}, label: {
|
||||
titleSecondButton
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
HStack {
|
||||
if titleFirstButton != nil {
|
||||
Button(action: {
|
||||
actionFirstButton()
|
||||
}, label: {
|
||||
titleFirstButton
|
||||
.default_text_style_white_600(styleSize: 14)
|
||||
.frame(height: 30)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal, 2)
|
||||
}
|
||||
|
||||
if titleSecondButton != nil {
|
||||
Button(action: {
|
||||
actionSecondButton()
|
||||
}, label: {
|
||||
titleSecondButton
|
||||
.default_text_style_white_600(styleSize: 14)
|
||||
.frame(height: 30)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color.orangeMain500)
|
||||
.cornerRadius(60)
|
||||
.padding(.horizontal, 2)
|
||||
}
|
||||
|
||||
if titleThirdButton != nil {
|
||||
Button(action: {
|
||||
actionThirdButton()
|
||||
}, label: {
|
||||
titleThirdButton
|
||||
.default_text_style_orange_600(styleSize: 14)
|
||||
.frame(height: 30)
|
||||
})
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
.cornerRadius(60)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 60)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.orangeMain500, lineWidth: 1)
|
||||
)
|
||||
.padding(.horizontal, 2)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 20)
|
||||
|
|
@ -101,9 +121,11 @@ struct PopupView: View {
|
|||
PopupView(isShowPopup: .constant(true),
|
||||
title: Text("Title"),
|
||||
content: Text("Content"),
|
||||
titleFirstButton: Text("Deny all"),
|
||||
titleFirstButton: Text("Accept all"),
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("Accept all"),
|
||||
actionSecondButton: {})
|
||||
titleSecondButton: Text("dialog_confirm"),
|
||||
actionSecondButton: {},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: {})
|
||||
.background(.black.opacity(0.65))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ struct PopupViewWithTextField: View {
|
|||
setNewChatRoomSubject()
|
||||
isShowConversationInfoPopup = false
|
||||
}, label: {
|
||||
Text("dialog_ok")
|
||||
Text("dialog_confirm")
|
||||
.default_text_style_white_600(styleSize: 20)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ struct SideMenu: View {
|
|||
@Binding var isShowLoginFragment: Bool
|
||||
@Binding var isShowAccountProfileFragment: Bool
|
||||
@Binding var isShowSettingsFragment: Bool
|
||||
@Binding var isShowRecordingsListFragment: Bool
|
||||
@Binding var isShowHelpFragment: Bool
|
||||
@State private var showHelp = false
|
||||
|
||||
|
|
@ -137,12 +138,15 @@ struct SideMenu: View {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SideMenuEntry(
|
||||
iconName: "record-fill",
|
||||
title: "recordings_title"
|
||||
)
|
||||
*/
|
||||
).onTapGesture {
|
||||
self.menuClose()
|
||||
withAnimation {
|
||||
isShowRecordingsListFragment = true
|
||||
}
|
||||
}
|
||||
|
||||
SideMenuEntry(
|
||||
iconName: "question",
|
||||
|
|
@ -152,7 +156,6 @@ struct SideMenu: View {
|
|||
withAnimation {
|
||||
isShowHelpFragment = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.padding(.bottom, safeAreaInsets.bottom + 13)
|
||||
|
|
@ -176,15 +179,15 @@ struct SideMenu: View {
|
|||
|
||||
#Preview {
|
||||
GeometryReader { geometry in
|
||||
@State var triggerNavigateToLogin: Bool = false
|
||||
SideMenu(
|
||||
width: geometry.size.width / 5 * 4,
|
||||
isOpen: .constant(true),
|
||||
menuClose: {},
|
||||
safeAreaInsets: geometry.safeAreaInsets,
|
||||
isShowLoginFragment: $triggerNavigateToLogin,
|
||||
isShowLoginFragment: .constant(false),
|
||||
isShowAccountProfileFragment: .constant(false),
|
||||
isShowSettingsFragment: .constant(false),
|
||||
isShowRecordingsListFragment: .constant(false),
|
||||
isShowHelpFragment: .constant(false)
|
||||
)
|
||||
.ignoresSafeArea(.all)
|
||||
|
|
|
|||
|
|
@ -90,15 +90,51 @@ struct SideMenuAccountRow: View {
|
|||
Spacer()
|
||||
|
||||
HStack {
|
||||
if model.voicemailCount > 0 {
|
||||
Button {
|
||||
model.callVoicemailUri()
|
||||
} label: {
|
||||
ZStack(alignment: .top) {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
Image("voicemail")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 22, height: 22)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text(String(model.voicemailCount))
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style_600(styleSize: 12)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.padding(.top, 1)
|
||||
}
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
.highPriorityGesture(
|
||||
TapGesture().onEnded {
|
||||
model.callVoicemailUri()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if model.notificationsCount > 0 && !CorePreferences.disableChatFeature {
|
||||
Text(String(model.notificationsCount))
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style(styleSize: 12)
|
||||
.lineLimit(1)
|
||||
.frame(width: 20, height: 20)
|
||||
.background(Color.redDanger500)
|
||||
.cornerRadius(50)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
VStack {
|
||||
Text(String(model.notificationsCount))
|
||||
.foregroundStyle(.white)
|
||||
.default_text_style(styleSize: 12)
|
||||
.lineLimit(1)
|
||||
.frame(width: 20, height: 20)
|
||||
.background(Color.redDanger500)
|
||||
.cornerRadius(50)
|
||||
}
|
||||
.frame(width: 30, height: 30)
|
||||
.padding(.trailing, -8)
|
||||
}
|
||||
|
||||
Menu {
|
||||
|
|
@ -112,15 +148,18 @@ struct SideMenuAccountRow: View {
|
|||
Label("drawer_menu_manage_account", systemImage: "arrow.right.circle")
|
||||
}
|
||||
} label: {
|
||||
Image("dots-three-vertical")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundColor(Color.gray)
|
||||
.scaledToFit()
|
||||
.frame(height: 30)
|
||||
VStack {
|
||||
Image("dots-three-vertical")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundColor(Color.grayMain2c500)
|
||||
.scaledToFit()
|
||||
.frame(height: 25)
|
||||
}
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
.frame(width: 64, alignment: .trailing)
|
||||
.frame(alignment: .trailing)
|
||||
.padding(.top, 12)
|
||||
.padding(.bottom, 12)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -315,6 +315,27 @@ struct ToastView: View {
|
|||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "Success_settings_contacts_carddav_sync_successful_toast":
|
||||
Text("settings_contacts_carddav_sync_successful_toast")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "settings_contacts_carddav_sync_error_toast":
|
||||
Text("settings_contacts_carddav_sync_error_toast")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
case "Success_settings_contacts_carddav_deleted_toast":
|
||||
Text("settings_contacts_carddav_deleted_toast")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(Color.greenSuccess500)
|
||||
.default_text_style(styleSize: 15)
|
||||
.padding(8)
|
||||
|
||||
default:
|
||||
Text("Error")
|
||||
|
|
|
|||
|
|
@ -25,18 +25,21 @@ struct HelpFragment: View {
|
|||
|
||||
@Binding var isShowHelpFragment: Bool
|
||||
|
||||
@State var advancedSettingsIsOpen: Bool = false
|
||||
|
||||
@FocusState var isVoicemailUriFocused: Bool
|
||||
var showAssistant: Bool {
|
||||
(CoreContext.shared.coreIsStarted && CoreContext.shared.accounts.isEmpty)
|
||||
|| SharedMainViewModel.shared.displayProfileMode
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
VStack(spacing: 1) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
if !showAssistant {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.frame(height: 0)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Image("caret-left")
|
||||
|
|
@ -70,11 +73,31 @@ struct HelpFragment: View {
|
|||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 20) {
|
||||
if let urlString = CorePreferences.themeAboutPictureUrl,
|
||||
let url = URL(string: urlString) {
|
||||
AsyncImage(url: url) { phase in
|
||||
switch phase {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity, minHeight: 100, maxHeight: 100)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: .infinity, maxHeight: 100, alignment: .center)
|
||||
case .failure:
|
||||
EmptyView()
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
Text("help_about_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.bottom, 5)
|
||||
|
||||
Button {
|
||||
if let url = URL(string: NSLocalizedString("website_user_guide_url", comment: "")) {
|
||||
UIApplication.shared.open(url)
|
||||
|
|
@ -154,7 +177,7 @@ struct HelpFragment: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
Text(helpViewModel.version)
|
||||
Text(helpViewModel.appVersion)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
|
@ -279,17 +302,17 @@ struct HelpFragment: View {
|
|||
isShowPopup: $helpViewModel.checkUpdateAvailable,
|
||||
title: Text("help_dialog_update_available_title"),
|
||||
content: Text(String(format: String(localized: "help_dialog_update_available_message"), helpViewModel.versionAvailable)),
|
||||
titleFirstButton: Text("dialog_cancel"),
|
||||
actionFirstButton: {
|
||||
helpViewModel.checkUpdateAvailable = false
|
||||
},
|
||||
titleFirstButton: nil,
|
||||
actionFirstButton: {},
|
||||
titleSecondButton: Text("dialog_install"),
|
||||
actionSecondButton: {
|
||||
helpViewModel.checkUpdateAvailable = false
|
||||
if let url = URL(string: helpViewModel.urlVersionAvailable) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
},
|
||||
titleThirdButton: Text("dialog_cancel"),
|
||||
actionThirdButton: { helpViewModel.checkUpdateAvailable = false }
|
||||
)
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
|
|
@ -302,5 +325,7 @@ struct HelpFragment: View {
|
|||
.navigationBarHidden(true)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,13 +38,20 @@ class HelpViewModel: ObservableObject {
|
|||
private var coreDelegate: CoreDelegate?
|
||||
|
||||
init() {
|
||||
let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String
|
||||
let versionTmp = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
|
||||
let appGitVersion = AppGitInfo.commit
|
||||
let appGitBranch = AppGitInfo.branch
|
||||
let appGitTag = AppGitInfo.tag
|
||||
let sdkGitVersion = linphonesw.sdkVersion
|
||||
var sdkGitBranch = linphonesw.sdkBranch
|
||||
|
||||
self.version = (versionTmp ?? "6.0.0")
|
||||
if sdkGitBranch.hasPrefix("remotes/origin/") {
|
||||
sdkGitBranch = String(sdkGitBranch.dropFirst("remotes/origin/".count))
|
||||
}
|
||||
|
||||
self.sdkVersion = Core.getVersion
|
||||
self.appVersion = appGitTag
|
||||
self.version = appGitTag + "-" + appGitVersion + "\n(\(appGitBranch))"
|
||||
|
||||
self.sdkVersion = sdkGitVersion + "\n(\(sdkGitBranch))"
|
||||
|
||||
if let path = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist"),
|
||||
let plist = NSDictionary(contentsOfFile: path) as? [String: Any],
|
||||
|
|
|
|||
|
|
@ -100,13 +100,9 @@ struct DialerBottomSheet: View {
|
|||
HStack {
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("1".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "1"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("1".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "1"
|
||||
} else {
|
||||
startCallViewModel.searchField += "1"
|
||||
}
|
||||
|
|
@ -125,13 +121,9 @@ struct DialerBottomSheet: View {
|
|||
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("2".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "2"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("2".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "2"
|
||||
} else {
|
||||
startCallViewModel.searchField += "2"
|
||||
}
|
||||
|
|
@ -150,13 +142,9 @@ struct DialerBottomSheet: View {
|
|||
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("3".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "3"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("3".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "3"
|
||||
} else {
|
||||
startCallViewModel.searchField += "3"
|
||||
}
|
||||
|
|
@ -177,13 +165,9 @@ struct DialerBottomSheet: View {
|
|||
HStack {
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("4".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "4"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("4".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "4"
|
||||
} else {
|
||||
startCallViewModel.searchField += "4"
|
||||
}
|
||||
|
|
@ -202,13 +186,9 @@ struct DialerBottomSheet: View {
|
|||
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("5".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "5"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("5".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "5"
|
||||
} else {
|
||||
startCallViewModel.searchField += "5"
|
||||
}
|
||||
|
|
@ -227,13 +207,9 @@ struct DialerBottomSheet: View {
|
|||
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("6".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "6"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("6".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "6"
|
||||
} else {
|
||||
startCallViewModel.searchField += "6"
|
||||
}
|
||||
|
|
@ -255,13 +231,9 @@ struct DialerBottomSheet: View {
|
|||
HStack {
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("7".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "7"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("7".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "7"
|
||||
} else {
|
||||
startCallViewModel.searchField += "7"
|
||||
}
|
||||
|
|
@ -280,13 +252,9 @@ struct DialerBottomSheet: View {
|
|||
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("8".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "8"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("8".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "8"
|
||||
} else {
|
||||
startCallViewModel.searchField += "8"
|
||||
}
|
||||
|
|
@ -305,13 +273,9 @@ struct DialerBottomSheet: View {
|
|||
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("9".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "9"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("9".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "9"
|
||||
} else {
|
||||
startCallViewModel.searchField += "9"
|
||||
}
|
||||
|
|
@ -333,13 +297,9 @@ struct DialerBottomSheet: View {
|
|||
HStack {
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("*".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "*"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("*".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "*"
|
||||
} else {
|
||||
startCallViewModel.searchField += "*"
|
||||
}
|
||||
|
|
@ -393,13 +353,9 @@ struct DialerBottomSheet: View {
|
|||
)
|
||||
} else {
|
||||
Button {
|
||||
do {
|
||||
let digit = ("0".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "0"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("0".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "0"
|
||||
} label: {
|
||||
Text("0")
|
||||
.foregroundStyle(currentCall != nil ? .white : Color.grayMain2c600)
|
||||
|
|
@ -416,13 +372,9 @@ struct DialerBottomSheet: View {
|
|||
|
||||
Button {
|
||||
if currentCall != nil {
|
||||
do {
|
||||
let digit = ("#".cString(using: String.Encoding.utf8)?[0])!
|
||||
try currentCall!.sendDtmf(dtmf: digit)
|
||||
dialerField += "#"
|
||||
} catch {
|
||||
|
||||
}
|
||||
let digit = ("#".cString(using: String.Encoding.utf8)?[0])!
|
||||
self.sendDtmf(dtmf: digit)
|
||||
dialerField += "#"
|
||||
} else {
|
||||
startCallViewModel.searchField += "#"
|
||||
}
|
||||
|
|
@ -534,6 +486,21 @@ struct DialerBottomSheet: View {
|
|||
orientation = newOrientation
|
||||
}
|
||||
}
|
||||
|
||||
func sendDtmf(dtmf: CChar) {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
guard let call = self.currentCall, call.state == .StreamsRunning else {
|
||||
Log.warn("Cannot send DTMF: call not active")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try call.sendDtmf(dtmf: dtmf)
|
||||
} catch {
|
||||
Log.error("Cannot send DTMF \(dtmf) to call \(call.callLog?.callId ?? ""): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,10 @@ struct HistoryContactFragment: View {
|
|||
Spacer()
|
||||
|
||||
Menu {
|
||||
if !historyModel.isConf {
|
||||
let disableAddContact = CorePreferences.disableAddContact
|
||||
let isFriend = historyModel.isFriend == true
|
||||
|
||||
if !historyModel.isConf && (!disableAddContact || (disableAddContact && isFriend)) {
|
||||
Button {
|
||||
isMenuOpen = false
|
||||
|
||||
|
|
@ -187,21 +190,17 @@ struct HistoryContactFragment: View {
|
|||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text(historyModel.address)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 5)
|
||||
|
||||
if let avatarModel = historyModel.avatarModel {
|
||||
Text(avatarModel.lastPresenceInfo)
|
||||
.foregroundStyle(avatarModel.lastPresenceInfo == "Online" ? Color.greenSuccess500 : Color.orangeWarning600)
|
||||
if !CorePreferences.hideSipAddresses {
|
||||
Text(historyModel.address)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 20)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
|
||||
if let avatar = historyModel.avatarModel {
|
||||
AvatarPresenceView(avatarModel: avatar)
|
||||
} else {
|
||||
Text("")
|
||||
.multilineTextAlignment(.center)
|
||||
|
|
@ -284,29 +283,31 @@ struct HistoryContactFragment: View {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
telecomManager.doCallOrJoinConf(address: historyModel.addressLinphone, isVideo: true)
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
if !SharedMainViewModel.shared.disableVideoCall {
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
telecomManager.doCallOrJoinConf(address: historyModel.addressLinphone, isVideo: true)
|
||||
}, label: {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Image("video-camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_video_call_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(minWidth: 80)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.grayMain2c200)
|
||||
.cornerRadius(40)
|
||||
|
||||
Text("contact_video_call_action")
|
||||
.default_text_style(styleSize: 14)
|
||||
.frame(minWidth: 80)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
|
|
@ -421,6 +422,21 @@ struct HistoryContactFragment: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct AvatarPresenceView: View {
|
||||
@ObservedObject var avatarModel: ContactAvatarModel
|
||||
|
||||
var body: some View {
|
||||
Text(avatarModel.lastPresenceInfo)
|
||||
.foregroundStyle(avatarModel.lastPresenceInfo == "Online" ? Color.greenSuccess500 : Color.orangeWarning600)
|
||||
.multilineTextAlignment(.center)
|
||||
.default_text_style_300(styleSize: 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 20)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#Preview {
|
||||
HistoryContactFragment(
|
||||
isShowDeleteAllHistoryPopup: .constant(false),
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ struct HistoryFragment: View {
|
|||
isShowEditContactFragment: $isShowEditContactFragment,
|
||||
isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress
|
||||
)
|
||||
.presentationDetents([.fraction(0.3)])
|
||||
.presentationDetents([.fraction(0.4)])
|
||||
}
|
||||
} else {
|
||||
HistoryListFragment(showingSheet: $showingSheet, text: $text)
|
||||
|
|
|
|||
|
|
@ -58,68 +58,74 @@ struct HistoryListBottomSheet: View {
|
|||
}
|
||||
|
||||
Spacer()
|
||||
Button {
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
|
||||
let disableAddContact = CorePreferences.disableAddContact
|
||||
let isFriend = historyListViewModel.selectedCall?.isFriend == true
|
||||
|
||||
if !disableAddContact || (disableAddContact && isFriend) {
|
||||
Button {
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
if idiom != .pad {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 0)
|
||||
|
||||
if let selectedCall = historyListViewModel.selectedCall, selectedCall.isFriend {
|
||||
let friendIndex = contactsManager.avatarListModel.first(where: {$0.addresses.contains(where: {$0 == selectedCall.address})})
|
||||
if friendIndex != nil {
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 0)
|
||||
|
||||
if let selectedCall = historyListViewModel.selectedCall, selectedCall.isFriend {
|
||||
let friendIndex = contactsManager.avatarListModel.first(where: {$0.addresses.contains(where: {$0 == selectedCall.address})})
|
||||
if friendIndex != nil {
|
||||
withAnimation {
|
||||
SharedMainViewModel.shared.displayedFriend = friendIndex
|
||||
}
|
||||
}
|
||||
} else if let selectedCall = historyListViewModel.selectedCall {
|
||||
withAnimation {
|
||||
SharedMainViewModel.shared.displayedFriend = friendIndex
|
||||
isShowEditContactFragment.toggle()
|
||||
isShowEditContactFragmentAddress = String(selectedCall.address.dropFirst(4))
|
||||
}
|
||||
}
|
||||
} else if let selectedCall = historyListViewModel.selectedCall {
|
||||
withAnimation {
|
||||
isShowEditContactFragment.toggle()
|
||||
isShowEditContactFragmentAddress = String(selectedCall.address.dropFirst(4))
|
||||
} label: {
|
||||
HStack {
|
||||
if let selectedCall = historyListViewModel.selectedCall, selectedCall.isFriend {
|
||||
Image("user-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
Text("menu_see_existing_contact")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
} else {
|
||||
Image("plus-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
Text("menu_add_address_to_contacts")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
if let selectedCall = historyListViewModel.selectedCall, selectedCall.isFriend {
|
||||
Image("user-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
Text("menu_see_existing_contact")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
} else {
|
||||
Image("plus-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
Text("menu_add_address_to_contacts")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if historyListViewModel.selectedCall != nil && historyListViewModel.selectedCall!.isOutgoing {
|
||||
|
|
|
|||
|
|
@ -152,8 +152,10 @@ struct HistoryRow: View {
|
|||
|
||||
if !historyModel.isConf {
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25)
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.padding(.all, 10)
|
||||
.padding(.trailing, 5)
|
||||
.highPriorityGesture(
|
||||
|
|
|
|||
|
|
@ -235,75 +235,83 @@ struct StartCallFragment: View {
|
|||
)
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
if !ContactsManager.shared.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
ZStack {
|
||||
ScrollView {
|
||||
if !ContactsManager.shared.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
ContactsListFragment(showingSheet: .constant(false)
|
||||
, startCallFunc: { addr in
|
||||
if callViewModel.isTransferInsteadCall {
|
||||
showingDialer = false
|
||||
|
||||
startCallViewModel.searchField = ""
|
||||
magicSearch.currentFilter = ""
|
||||
|
||||
magicSearch.searchForContacts()
|
||||
|
||||
if callViewModel.isTransferInsteadCall == true {
|
||||
callViewModel.isTransferInsteadCall = false
|
||||
|
||||
ContactsListFragment(showingSheet: .constant(false)
|
||||
, startCallFunc: { addr in
|
||||
if callViewModel.isTransferInsteadCall {
|
||||
showingDialer = false
|
||||
|
||||
startCallViewModel.searchField = ""
|
||||
magicSearch.currentFilter = ""
|
||||
|
||||
magicSearch.searchForContacts()
|
||||
|
||||
if callViewModel.isTransferInsteadCall == true {
|
||||
callViewModel.isTransferInsteadCall = false
|
||||
}
|
||||
|
||||
resetCallView()
|
||||
|
||||
delayColorDismiss()
|
||||
|
||||
withAnimation {
|
||||
isShowStartCallFragment.toggle()
|
||||
callViewModel.blindTransferCallTo(toAddress: addr)
|
||||
}
|
||||
} else {
|
||||
showingDialer = false
|
||||
|
||||
startCallViewModel.searchField = ""
|
||||
magicSearch.currentFilter = ""
|
||||
|
||||
magicSearch.searchForContacts()
|
||||
|
||||
if callViewModel.isTransferInsteadCall == true {
|
||||
callViewModel.isTransferInsteadCall = false
|
||||
}
|
||||
|
||||
resetCallView()
|
||||
|
||||
delayColorDismiss()
|
||||
|
||||
withAnimation {
|
||||
isShowStartCallFragment.toggle()
|
||||
telecomManager.doCallOrJoinConf(address: addr)
|
||||
}
|
||||
}
|
||||
|
||||
resetCallView()
|
||||
|
||||
delayColorDismiss()
|
||||
|
||||
withAnimation {
|
||||
isShowStartCallFragment.toggle()
|
||||
callViewModel.blindTransferCallTo(toAddress: addr)
|
||||
}
|
||||
} else {
|
||||
showingDialer = false
|
||||
|
||||
startCallViewModel.searchField = ""
|
||||
magicSearch.currentFilter = ""
|
||||
|
||||
magicSearch.searchForContacts()
|
||||
|
||||
if callViewModel.isTransferInsteadCall == true {
|
||||
callViewModel.isTransferInsteadCall = false
|
||||
}
|
||||
|
||||
resetCallView()
|
||||
|
||||
delayColorDismiss()
|
||||
|
||||
withAnimation {
|
||||
isShowStartCallFragment.toggle()
|
||||
telecomManager.doCallOrJoinConf(address: addr)
|
||||
}
|
||||
}
|
||||
})
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
if !contactsManager.lastSearchSuggestions.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
})
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
if !contactsManager.lastSearchSuggestions.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
}
|
||||
}
|
||||
|
||||
if magicSearch.isLoading {
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -385,6 +393,7 @@ struct StartCallFragment: View {
|
|||
HStack {
|
||||
if index < contactsManager.lastSearchSuggestions.count
|
||||
&& contactsManager.lastSearchSuggestions[index].address != nil {
|
||||
if contactsManager.lastSearchSuggestions[index].address!.domain != CorePreferences.defaultDomain {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)),
|
||||
lastName: ""))
|
||||
|
|
@ -394,9 +403,29 @@ struct StartCallFragment: View {
|
|||
|
||||
Text(String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)))
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
} else {
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
let nameTmp = address.displayName
|
||||
?? address.username
|
||||
?? String(address.asStringUriOnly().dropFirst(4))
|
||||
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: nameTmp,
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(nameTmp)
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
|
|
|
|||
|
|
@ -45,7 +45,12 @@ class HistoryListViewModel: ObservableObject {
|
|||
func computeCallLogsList() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let account = core.defaultAccount
|
||||
let logs = account?.callLogs != nil ? account!.callLogs : core.callLogs
|
||||
|
||||
// Fetch all call logs if only one account to workaround no history issue
|
||||
// TODO FIXME: remove workaround later
|
||||
let logs = (core.accountList.count > 1)
|
||||
? (account?.callLogs ?? core.callLogs)
|
||||
: core.callLogs
|
||||
|
||||
var callLogsBis: [HistoryModel] = []
|
||||
var callLogsTmpBis: [HistoryModel] = []
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class StartCallViewModel: ObservableObject {
|
|||
|
||||
func interpretAndStartCall() {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
let address = core.interpretUrl(url: self.searchField, applyInternationalPrefix: true)
|
||||
let address = core.interpretUrl(url: self.searchField, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core))
|
||||
if address != nil {
|
||||
TelecomManager.shared.doCallOrJoinConf(address: address!)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,57 +167,47 @@ struct AddParticipantsFragment: View {
|
|||
.padding(.bottom)
|
||||
.padding(.horizontal)
|
||||
|
||||
ScrollView {
|
||||
ForEach(0..<contactsManager.lastSearch.count, id: \.self) { index in
|
||||
HStack {
|
||||
ZStack {
|
||||
ScrollView {
|
||||
ForEach(0..<contactsManager.avatarListModel.count, id: \.self) { index in
|
||||
HStack {
|
||||
if index == 0
|
||||
|| contactsManager.lastSearch[index].friend?.name!.lowercased().folding(
|
||||
options: .diacriticInsensitive,
|
||||
locale: .current
|
||||
).first
|
||||
!= contactsManager.lastSearch[index-1].friend?.name!.lowercased().folding(
|
||||
options: .diacriticInsensitive,
|
||||
locale: .current
|
||||
).first {
|
||||
Text(
|
||||
String(
|
||||
(contactsManager.lastSearch[index].friend?.name!.uppercased().folding(
|
||||
options: .diacriticInsensitive,
|
||||
locale: .current
|
||||
).first)!))
|
||||
.contact_text_style_500(styleSize: 20)
|
||||
.frame(width: 18)
|
||||
.padding(.leading, 5)
|
||||
.padding(.trailing, 5)
|
||||
} else {
|
||||
Text("")
|
||||
HStack {
|
||||
if index == 0
|
||||
|| contactsManager.avatarListModel[index].name.lowercased().folding(
|
||||
options: .diacriticInsensitive,
|
||||
locale: .current
|
||||
).first
|
||||
!= contactsManager.avatarListModel[index-1].name.lowercased().folding(
|
||||
options: .diacriticInsensitive,
|
||||
locale: .current
|
||||
).first {
|
||||
Text(
|
||||
String(
|
||||
(contactsManager.avatarListModel[index].name.uppercased().folding(
|
||||
options: .diacriticInsensitive,
|
||||
locale: .current
|
||||
).first)!))
|
||||
.contact_text_style_500(styleSize: 20)
|
||||
.frame(width: 18)
|
||||
.padding(.leading, 5)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
|
||||
if index < contactsManager.avatarListModel.count,
|
||||
let friend = contactsManager.avatarListModel[index].friend,
|
||||
let photo = friend.photo,
|
||||
!photo.isEmpty {
|
||||
} else {
|
||||
Text("")
|
||||
.contact_text_style_500(styleSize: 20)
|
||||
.frame(width: 18)
|
||||
.padding(.leading, 5)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
|
||||
Avatar(contactAvatarModel: contactsManager.avatarListModel[index], avatarSize: 50)
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
Text((contactsManager.lastSearch[index].friend?.name ?? "")!)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
|
||||
if let searchAddress = contactsManager.lastSearch[index].friend?.address?.asStringUriOnly() {
|
||||
|
||||
Text(contactsManager.avatarListModel[index].name)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
|
||||
if addParticipantsViewModel.participantsToAdd.contains(where: {
|
||||
$0.address.asStringUriOnly() == searchAddress
|
||||
$0.address.asStringUriOnly() == contactsManager.avatarListModel[index].address
|
||||
}) {
|
||||
Image("check")
|
||||
.renderingMode(.template)
|
||||
|
|
@ -228,27 +218,33 @@ struct AddParticipantsFragment: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
if let addr = contactsManager.lastSearch[index].address {
|
||||
addParticipantsViewModel.selectParticipant(addr: addr)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
if let addr = try? Factory.Instance.createAddress(addr: contactsManager.avatarListModel[index].address) {
|
||||
addParticipantsViewModel.selectParticipant(addr: addr)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
suggestionsList
|
||||
if magicSearch.isLoading {
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
|
||||
}
|
||||
}
|
||||
}
|
||||
Button {
|
||||
|
|
@ -294,19 +290,39 @@ struct AddParticipantsFragment: View {
|
|||
HStack {
|
||||
if index < contactsManager.lastSearchSuggestions.count
|
||||
&& contactsManager.lastSearchSuggestions[index].address != nil {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)),
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)))
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
if contactsManager.lastSearchSuggestions[index].address!.domain != CorePreferences.defaultDomain {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)),
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)))
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
} else {
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
let nameTmp = address.displayName
|
||||
?? address.username
|
||||
?? String(address.asStringUriOnly().dropFirst(4))
|
||||
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: nameTmp,
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(nameTmp)
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
|
||||
if let searchAddress = contactsManager.lastSearchSuggestions[index].address?.asStringUriOnly() {
|
||||
if addParticipantsViewModel.participantsToAdd.contains(where: {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ struct MeetingsListBottomSheet: View {
|
|||
Button {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
if let organizerUri = self.meetingsListViewModel.selectedMeetingToDelete?.confInfo.organizer {
|
||||
if core.defaultAccount?.contactAddress?.weakEqual(address2: organizerUri) ?? false {
|
||||
if core.defaultAccount?.params?.identityAddress?.weakEqual(address2: organizerUri) ?? false {
|
||||
// If we are the organizer, display popup for sending
|
||||
DispatchQueue.main.async {
|
||||
self.isShowSendCancelMeetingNotificationPopup = true
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ struct ScheduleMeetingFragment: View {
|
|||
showDatePicker.toggle()
|
||||
}
|
||||
}
|
||||
Text("dialog_ok")
|
||||
Text("dialog_confirm")
|
||||
.default_text_style_orange_500(styleSize: 16)
|
||||
.onTapGesture {
|
||||
pickDate()
|
||||
|
|
|
|||