mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-21 13:08:08 +00:00
Compare commits
46 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df3904d76c | ||
|
|
a40c339ce9 | ||
|
|
955432c17f | ||
|
|
d993add9a7 | ||
|
|
dab585ccd5 | ||
|
|
3451a11970 | ||
|
|
28f9555aab | ||
|
|
38c50ec59c | ||
|
|
b822e7c552 | ||
|
|
695c7ef300 | ||
|
|
838f95f6e3 | ||
|
|
084c2821e2 | ||
|
|
e67c6b04a5 | ||
|
|
a4ddc70e1a | ||
|
|
4ab97cdb33 | ||
|
|
7b32b149f8 | ||
|
|
72a1dc8431 | ||
|
|
c8fbba99b0 | ||
|
|
f53348236e | ||
|
|
9a98190e86 | ||
|
|
974f1ca0fe | ||
|
|
93294b2a91 | ||
|
|
63c5fcb5fd | ||
|
|
f074caa49a | ||
|
|
924929a1ad | ||
|
|
9240dc352f | ||
|
|
482079d873 | ||
|
|
79ac4f0434 | ||
|
|
095d3e3551 | ||
|
|
e2644a2347 | ||
|
|
b0ee9e80d4 | ||
|
|
a211d0c994 | ||
|
|
7e2c429052 | ||
|
|
165b718514 | ||
|
|
af981603ab | ||
|
|
c5cef9119a | ||
|
|
68ed0905b9 | ||
|
|
bdc104c22f | ||
|
|
0c8c2d7ecb | ||
|
|
09e1ca1df5 | ||
|
|
bd4f59085d | ||
|
|
25d960c8a8 | ||
|
|
f79506590f | ||
|
|
295248455b | ||
|
|
a327d96900 | ||
|
|
5ca63b577a |
44 changed files with 1511 additions and 712 deletions
63
CHANGELOG.md
63
CHANGELOG.md
|
|
@ -10,51 +10,82 @@ Group changes to describe their impact on the project, as follows:
|
||||||
Fixed for any bug fixes.
|
Fixed for any bug fixes.
|
||||||
Security to invite users to upgrade in case of vulnerabilities.
|
Security to invite users to upgrade in case of vulnerabilities.
|
||||||
|
|
||||||
## [6.0.0] - 2025-03-11
|
|
||||||
|
## [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.
|
6.0.0 release is a complete rework of Linphone, with a fully redesigned UI, so it is impossible to list everything here.
|
||||||
|
|
||||||
### Changed
|
### 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.
|
- 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.
|
- 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.
|
- 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).
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- Minimum supported iOS version is now 15.
|
||||||
- Some settings have changed name and/or section in linphonerc file.
|
- Some settings have changed name and/or section in linphonerc file.
|
||||||
|
|
||||||
### Added
|
### 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.
|
- 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 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).
|
- 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.
|
- 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.
|
- 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).
|
- 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.
|
- 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).
|
- 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.
|
- Dialer & in-call numpad show letters under the digit.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Dialer: the previous home screen (dialer) has been removed, you'll find it as an input option in the new start call screen.
|
- 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.
|
- 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
|
## [5.2.0] - 2023-28-12
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
21
Linphone/Assets.xcassets/layout.imageset/Contents.json
vendored
Normal file
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
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
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
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 |
|
|
@ -154,25 +154,22 @@ final class ContactsManager: ObservableObject {
|
||||||
|
|
||||||
let imageThumbnail = UIImage(data: contact.thumbnailImageData ?? Data())
|
let imageThumbnail = UIImage(data: contact.thumbnailImageData ?? Data())
|
||||||
if let image = imageThumbnail {
|
if let image = imageThumbnail {
|
||||||
DispatchQueue.main.async {
|
self.saveImage(
|
||||||
self.saveImage(
|
image: image,
|
||||||
image: image,
|
name: contact.givenName + contact.familyName,
|
||||||
name: contact.givenName + contact.familyName,
|
prefix: "",
|
||||||
prefix: "",
|
contact: newContact, linphoneFriend: self.nativeAddressBookFriendList, existingFriend: nil) {
|
||||||
contact: newContact, linphoneFriend: self.nativeAddressBookFriendList, existingFriend: nil) {
|
dispatchGroup.leave()
|
||||||
dispatchGroup.leave()
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.textToImageInMainThread(firstName: contact.givenName, lastName: contact.familyName) { image in
|
let image = self.textToImage(firstName: contact.givenName, lastName: contact.familyName)
|
||||||
self.saveImage(
|
self.saveImage(
|
||||||
image: image,
|
image: image,
|
||||||
name: contact.givenName + contact.familyName,
|
name: contact.givenName + contact.familyName,
|
||||||
prefix: "-default",
|
prefix: "-default",
|
||||||
contact: newContact, linphoneFriend: self.nativeAddressBookFriendList, existingFriend: nil) {
|
contact: newContact, linphoneFriend: self.nativeAddressBookFriendList, existingFriend: nil) {
|
||||||
dispatchGroup.leave()
|
dispatchGroup.leave()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -197,154 +194,133 @@ final class ContactsManager: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func textToImageInMainThread(firstName: String, lastName: String, completion: @escaping (UIImage) -> Void) {
|
func textToImage(firstName: String?, lastName: String?) -> UIImage {
|
||||||
DispatchQueue.main.async {
|
let firstInitial = firstName?.first.map { String($0) } ?? ""
|
||||||
let lblNameInitialize = UILabel()
|
let lastInitial = lastName?.first.map { String($0) } ?? ""
|
||||||
lblNameInitialize.frame.size = CGSize(width: 200.0, height: 200.0)
|
let textToDisplay = (firstInitial + lastInitial).uppercased()
|
||||||
lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 80)
|
|
||||||
lblNameInitialize.textColor = UIColor(Color.grayMain2c600)
|
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()
|
let paragraph = NSMutableParagraphStyle()
|
||||||
lblNameInitialize.textAlignment = .center
|
paragraph.alignment = .center
|
||||||
lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200)
|
|
||||||
lblNameInitialize.layer.cornerRadius = 10.0
|
let attributes: [NSAttributedString.Key: Any] = [
|
||||||
lblNameInitialize.clipsToBounds = true
|
.font: UIFont(name: "NotoSans-ExtraBold", size: 80) ?? UIFont.boldSystemFont(ofSize: 80),
|
||||||
|
.foregroundColor: UIColor(Color.grayMain2c600),
|
||||||
UIGraphicsBeginImageContext(lblNameInitialize.frame.size)
|
.paragraphStyle: paragraph
|
||||||
defer { UIGraphicsEndImageContext() }
|
]
|
||||||
|
|
||||||
guard let context = UIGraphicsGetCurrentContext() else {
|
let textSize = textToDisplay.size(withAttributes: attributes)
|
||||||
completion(UIImage())
|
let textRect = CGRect(
|
||||||
return
|
x: (size.width - textSize.width) / 2,
|
||||||
}
|
y: (size.height - textSize.height) / 2,
|
||||||
|
width: textSize.width,
|
||||||
lblNameInitialize.layer.render(in: context)
|
height: textSize.height
|
||||||
let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
|
)
|
||||||
completion(image)
|
|
||||||
|
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!)
|
|
||||||
}
|
|
||||||
|
|
||||||
lblNameInitialize.text = textToDisplay.uppercased()
|
|
||||||
lblNameInitialize.textAlignment = .center
|
|
||||||
lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200)
|
|
||||||
lblNameInitialize.layer.cornerRadius = 10.0
|
|
||||||
|
|
||||||
var IBImgViewUserProfile = UIImage()
|
|
||||||
UIGraphicsBeginImageContext(lblNameInitialize.frame.size)
|
|
||||||
lblNameInitialize.layer.render(in: UIGraphicsGetCurrentContext()!)
|
|
||||||
IBImgViewUserProfile = UIGraphicsGetImageFromCurrentImageContext()!
|
|
||||||
UIGraphicsEndImageContext()
|
|
||||||
|
|
||||||
return IBImgViewUserProfile
|
|
||||||
}
|
|
||||||
|
|
||||||
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?, completion: @escaping () -> Void) {
|
||||||
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
|
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitDataWrite(data: data, name: name, prefix: prefix) { _, result in
|
awaitDataWrite(data: data, name: name, prefix: prefix) { result in
|
||||||
self.saveFriend(result: result, contact: contact, existingFriend: existingFriend) { resultFriend in
|
self.saveFriend(result: result, contact: contact, existingFriend: existingFriend) { resultFriend in
|
||||||
if resultFriend != nil {
|
self.coreContext.doOnCoreQueue { core in
|
||||||
if linphoneFriend != self.nativeAddressBookFriendList && existingFriend == nil {
|
if let friend = resultFriend {
|
||||||
if let linphoneFL = self.linphoneFriendList, linphoneFriend == linphoneFL.displayName {
|
if linphoneFriend != self.nativeAddressBookFriendList && existingFriend == nil {
|
||||||
_ = linphoneFL.addFriend(linphoneFriend: resultFriend!)
|
if let linphoneFL = self.linphoneFriendList, linphoneFriend == linphoneFL.displayName {
|
||||||
} else if let linphoneFL = self.tempRemoteFriendList {
|
_ = linphoneFL.addFriend(linphoneFriend: friend)
|
||||||
_ = linphoneFL.addFriend(linphoneFriend: resultFriend!)
|
} else if let linphoneFL = self.tempRemoteFriendList {
|
||||||
}
|
_ = linphoneFL.addFriend(linphoneFriend: friend)
|
||||||
} else if existingFriend == nil {
|
}
|
||||||
if let friendListTmp = self.friendList {
|
} else if existingFriend == nil {
|
||||||
_ = friendListTmp.addLocalFriend(linphoneFriend: resultFriend!)
|
if let friendListTmp = self.friendList {
|
||||||
|
_ = friendListTmp.addLocalFriend(linphoneFriend: friend)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
completion()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func saveFriend(result: String, contact: Contact, existingFriend: Friend?, completion: @escaping (Friend?) -> Void) {
|
func saveFriend(result: String, contact: Contact, existingFriend: Friend?, completion: @escaping (Friend?) -> Void) {
|
||||||
self.coreContext.doOnCoreQueue { core in
|
self.coreContext.doOnCoreQueue { core in
|
||||||
do {
|
do {
|
||||||
|
// Create or use existing friend
|
||||||
let friend = try existingFriend ?? core.createFriend()
|
let friend = try existingFriend ?? core.createFriend()
|
||||||
|
|
||||||
|
// Strong capture in closure to avoid threading issues
|
||||||
friend.edit()
|
friend.edit()
|
||||||
|
|
||||||
friend.nativeUri = contact.identifier
|
friend.nativeUri = contact.identifier
|
||||||
try friend.setName(newValue: contact.firstName + " " + contact.lastName)
|
try friend.setName(newValue: contact.firstName + " " + contact.lastName)
|
||||||
|
|
||||||
let friendvCard = friend.vcard
|
// Safely update vCard
|
||||||
|
if let vcard = friend.vcard {
|
||||||
if friendvCard != nil {
|
vcard.givenName = contact.firstName
|
||||||
friendvCard!.givenName = contact.firstName
|
vcard.familyName = contact.lastName
|
||||||
friendvCard!.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.organization = contact.organizationName
|
||||||
friend.jobTitle = contact.jobTitle
|
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: true),
|
||||||
|
!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 = "file:/" + result
|
||||||
|
|
||||||
|
// Linphone subscription settings
|
||||||
try friend.setSubscribesenabled(newValue: false)
|
try friend.setSubscribesenabled(newValue: false)
|
||||||
try friend.setIncsubscribepolicy(newValue: .SPDeny)
|
try friend.setIncsubscribepolicy(newValue: .SPDeny)
|
||||||
|
|
||||||
|
// Commit changes
|
||||||
friend.done()
|
friend.done()
|
||||||
|
|
||||||
|
// Notify completion safely
|
||||||
completion(friend)
|
completion(friend)
|
||||||
} catch let error {
|
} catch {
|
||||||
print("Failed to enumerate contact", error)
|
print("saveFriend - Failed to save friend:", error)
|
||||||
completion(nil)
|
completion(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func getImagePath(friendPhotoPath: String) -> URL {
|
func getImagePath(friendPhotoPath: String) -> URL {
|
||||||
let friendPath = String(friendPhotoPath.dropFirst(6))
|
let friendPath = String(friendPhotoPath.dropFirst(6))
|
||||||
|
|
@ -354,26 +330,21 @@ final class ContactsManager: ObservableObject {
|
||||||
return imagePath
|
return imagePath
|
||||||
}
|
}
|
||||||
|
|
||||||
func awaitDataWrite(data: Data, name: String, prefix: String, completion: @escaping ((), String) -> Void) {
|
func awaitDataWrite(data: Data, name: String, prefix: String, completion: @escaping (String) -> Void) {
|
||||||
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
|
guard let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||||
|
completion("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if directory != nil {
|
do {
|
||||||
DispatchQueue.main.async {
|
let fileName = name + prefix + ".png"
|
||||||
do {
|
let fileURL = directory.appendingPathComponent(fileName)
|
||||||
if let urlName = URL(string: name + prefix) {
|
|
||||||
let imagePath = urlName.absoluteString.replacingOccurrences(of: "%", with: "")
|
try data.write(to: fileURL)
|
||||||
|
completion(fileName)
|
||||||
let decodedData: () = try data.write(to: directory!.appendingPathComponent(imagePath + ".png"))
|
} catch {
|
||||||
|
print("Error writing image: \(error)")
|
||||||
completion(decodedData, imagePath + ".png")
|
completion("")
|
||||||
} else {
|
|
||||||
completion((), "")
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Error: ", error)
|
|
||||||
completion((), "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -440,8 +411,11 @@ final class ContactsManager: ObservableObject {
|
||||||
if status == .Successful {
|
if status == .Successful {
|
||||||
if friendList.displayName != self.nativeAddressBookFriendList && friendList.displayName != self.linphoneAddressBookFriendList {
|
if friendList.displayName != self.nativeAddressBookFriendList && friendList.displayName != self.linphoneAddressBookFriendList {
|
||||||
if let tempRemoteFriendList = self.tempRemoteFriendList {
|
if let tempRemoteFriendList = self.tempRemoteFriendList {
|
||||||
tempRemoteFriendList.friends.forEach { friend in
|
tempRemoteFriendList.friends.forEach { friend in
|
||||||
_ = tempRemoteFriendList.removeFriend(linphoneFriend: friend)
|
if let friendAddress = friend.address,
|
||||||
|
friendList.friends.contains(where: { $0.address?.weakEqual(address2: friendAddress) == true }) {
|
||||||
|
_ = tempRemoteFriendList.removeFriend(linphoneFriend: friend)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -464,27 +438,82 @@ final class ContactsManager: ObservableObject {
|
||||||
imageData: ""
|
imageData: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
self.textToImageInMainThread(firstName: friend.name ?? addressTmp, lastName: "") { image in
|
let image = self.textToImage(firstName: friend.name ?? addressTmp, lastName: "")
|
||||||
self.saveImage(
|
self.saveImage(
|
||||||
image: image,
|
image: image,
|
||||||
name: friend.name ?? addressTmp,
|
name: friend.name ?? addressTmp,
|
||||||
prefix: "-default",
|
prefix: "-default",
|
||||||
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: nil) {
|
contact: newContact, linphoneFriend: friendList.displayName ?? "No Display Name", existingFriend: nil) {
|
||||||
dispatchGroup.leave()
|
dispatchGroup.leave()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchGroup.notify(queue: .main) {
|
dispatchGroup.notify(queue: .main) {
|
||||||
MagicSearchSingleton.shared.searchForContacts()
|
self.coreContext.doOnCoreQueue { _ in
|
||||||
if let linphoneFL = self.tempRemoteFriendList {
|
MagicSearchSingleton.shared.searchForContacts()
|
||||||
linphoneFL.updateSubscriptions()
|
if let linphoneFL = self.tempRemoteFriendList {
|
||||||
}
|
linphoneFL.updateSubscriptions()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPresenceReceived: { (friendList: FriendList, friends: [Friend?]) in
|
onPresenceReceived: { (friendList: FriendList, friends: [Friend?]) in
|
||||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onPresenceReceived \(friends.count)")
|
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
|
onNewSipAddressDiscovered: { (friendList: FriendList, linphoneFriend: Friend, sipUri: String) in
|
||||||
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onNewSipAddressDiscovered \(linphoneFriend.name ?? "")")
|
Log.info("\(ContactsManager.TAG) FriendListDelegateStub onNewSipAddressDiscovered \(linphoneFriend.name ?? "")")
|
||||||
|
|
@ -522,6 +551,8 @@ final class ContactsManager: ObservableObject {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.friendListDelegate = friendListDelegateTmp
|
||||||
|
|
||||||
CoreContext.shared.mCore.friendsLists.forEach { friendList in
|
CoreContext.shared.mCore.friendsLists.forEach { friendList in
|
||||||
friendList.addDelegate(delegate: friendListDelegateTmp)
|
friendList.addDelegate(delegate: friendListDelegateTmp)
|
||||||
}
|
}
|
||||||
|
|
@ -558,15 +589,17 @@ final class ContactsManager: ObservableObject {
|
||||||
for contact in avatarListModel {
|
for contact in avatarListModel {
|
||||||
contact.$starred
|
contact.$starred
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
self?.starredChangeTrigger = UUID() // 🔁 Déclenche le refresh de la vue
|
self?.starredChangeTrigger = UUID()
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSubscriptionsLinphoneList() {
|
func updateSubscriptionsLinphoneList() {
|
||||||
if let linphoneFL = self.linphoneFriendList {
|
self.coreContext.doOnCoreQueue { _ in
|
||||||
linphoneFL.updateSubscriptions()
|
if let linphoneFL = self.linphoneFriendList {
|
||||||
|
linphoneFL.updateSubscriptions()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,8 @@ class CoreContext: ObservableObject {
|
||||||
self.mCore = try? Factory.Instance.createSharedCoreWithConfig(config: Config.get(), systemContext: Unmanaged.passUnretained(coreQueue).toOpaque(), appGroupId: Config.appGroupName, mainCore: true)
|
self.mCore = try? Factory.Instance.createSharedCoreWithConfig(config: Config.get(), systemContext: Unmanaged.passUnretained(coreQueue).toOpaque(), appGroupId: Config.appGroupName, mainCore: true)
|
||||||
|
|
||||||
self.mCore.callkitEnabled = true
|
self.mCore.callkitEnabled = true
|
||||||
self.mCore.pushNotificationEnabled = true
|
|
||||||
|
self.mCore.pushNotificationEnabled = self.mCore.defaultAccount?.params?.pushNotificationAllowed ?? false
|
||||||
|
|
||||||
let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String
|
let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String
|
||||||
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||||
|
|
@ -215,12 +216,6 @@ class CoreContext: ObservableObject {
|
||||||
newParams?.pushNotificationConfig?.provider = "apns" + pushEnvironment
|
newParams?.pushNotificationConfig?.provider = "apns" + pushEnvironment
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
account.params = newParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -424,26 +419,24 @@ class CoreContext: ObservableObject {
|
||||||
|
|
||||||
func onEnterForeground() {
|
func onEnterForeground() {
|
||||||
coreQueue.async {
|
coreQueue.async {
|
||||||
// We can't rely on defaultAccount?.params?.isPublishEnabled
|
Log.info("[onEnterForegroundOrBackground] Entering foreground")
|
||||||
// as it will be modified by the SDK when changing the presence status
|
|
||||||
|
|
||||||
try? self.mCore.start()
|
try? self.mCore.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func onEnterBackground() {
|
func onEnterBackground() {
|
||||||
coreQueue.async {
|
coreQueue.async {
|
||||||
// We can't rely on defaultAccount?.params?.isPublishEnabled
|
Log.info("[onEnterForegroundOrBackground] Entering background, un-PUBLISHING presence info")
|
||||||
// as it will be modified by the SDK when changing the presence status
|
|
||||||
Log.info("App is in background, un-PUBLISHING presence info")
|
|
||||||
|
|
||||||
// We don't use ConsolidatedPresence.Busy but Offline to do an unsubscribe,
|
self.updatePresence(core: self.mCore, presence: .Offline)
|
||||||
// Flexisip will handle the Busy status depending on other devices
|
|
||||||
self.updatePresence(core: self.mCore, presence: ConsolidatedPresence.Offline)
|
|
||||||
self.mCore.iterate()
|
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()
|
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 {
|
static var checkForUpdateServerUrl: String {
|
||||||
get {
|
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 {
|
set {
|
||||||
Config.get().setString(section: "misc", key: "version_check_url_root", value: newValue)
|
Config.get().setString(section: "misc", key: "version_check_url_root", value: newValue)
|
||||||
|
|
@ -86,7 +87,8 @@ class CorePreferences {
|
||||||
|
|
||||||
static var deviceName: String {
|
static var deviceName: String {
|
||||||
get {
|
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 {
|
set {
|
||||||
Config.get().setString(section: "app", key: "device", value: newValue.trimmingCharacters(in: .whitespaces))
|
Config.get().setString(section: "app", key: "device", value: newValue.trimmingCharacters(in: .whitespaces))
|
||||||
|
|
@ -131,7 +133,8 @@ class CorePreferences {
|
||||||
|
|
||||||
static var contactsFilter: String {
|
static var contactsFilter: String {
|
||||||
get {
|
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 {
|
set {
|
||||||
Config.get().setString(section: "ui", key: "contacts_filter", value: newValue)
|
Config.get().setString(section: "ui", key: "contacts_filter", value: newValue)
|
||||||
|
|
@ -177,7 +180,8 @@ class CorePreferences {
|
||||||
|
|
||||||
static var themeMainColor: String {
|
static var themeMainColor: String {
|
||||||
get {
|
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 {
|
set {
|
||||||
Config.get().setString(section: "ui", key: "theme_main_color", value: newValue)
|
Config.get().setString(section: "ui", key: "theme_main_color", value: newValue)
|
||||||
|
|
@ -244,7 +248,8 @@ class CorePreferences {
|
||||||
|
|
||||||
static var defaultDomain: String {
|
static var defaultDomain: String {
|
||||||
get {
|
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 {
|
set {
|
||||||
Config.get().setString(section: "app", key: "default_domain", value: newValue)
|
Config.get().setString(section: "app", key: "default_domain", value: newValue)
|
||||||
|
|
@ -283,4 +288,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), let s = String(data: data, encoding: .utf8) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
"assistant_account_login_forbidden_error" = "Falscher Benutzername oder Passwort";
|
"assistant_account_login_forbidden_error" = "Falscher Benutzername oder Passwort";
|
||||||
"assistant_account_register" = "Registrieren";
|
"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_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_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_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";
|
"assistant_dialog_confirm_phone_number_title" = "Telefonnummer bestätigen";
|
||||||
|
|
@ -318,7 +318,7 @@
|
||||||
"settings_calls_auto_record_title" = "Automatische Anrufaufzeichnung starten";
|
"settings_calls_auto_record_title" = "Automatische Anrufaufzeichnung starten";
|
||||||
"settings_calls_calibrate_echo_canceller_title" = "Echokompensator kalibrieren";
|
"settings_calls_calibrate_echo_canceller_title" = "Echokompensator kalibrieren";
|
||||||
"settings_calls_change_ringtone_title" = "Klingelton ändern";
|
"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_echo_canceller_title" = "Verwenden Sie die Software Echounterdrückung";
|
||||||
"settings_calls_enable_fec_title" = "Video FEC aktivieren";
|
"settings_calls_enable_fec_title" = "Video FEC aktivieren";
|
||||||
"settings_calls_enable_video_title" = "Video aktivieren";
|
"settings_calls_enable_video_title" = "Video aktivieren";
|
||||||
|
|
@ -363,3 +363,147 @@
|
||||||
"welcome_page_title" = "Willkommen";
|
"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_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";
|
"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" = "%@ sind zusammengestellt…";
|
||||||
|
"conversation_composing_label_single" = "%@ ist zusammengestellt…";
|
||||||
|
"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" = "Gruppe Teilnehmer (%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" = "Erhalten";
|
||||||
|
"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" = "Authentifizierungs-Realm";
|
||||||
|
"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" = "Filtern";
|
||||||
|
"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";
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,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_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_third_party_sip_account_warning_ok" = "I understand";
|
||||||
"assistant_web_platform_link" = "subscribe.linphone.org";
|
"assistant_web_platform_link" = "subscribe.linphone.org";
|
||||||
|
"authentication_id" = "Authentication ID (if different)";
|
||||||
"bottom_navigation_calls_label" = "Calls";
|
"bottom_navigation_calls_label" = "Calls";
|
||||||
"bottom_navigation_contacts_label" = "Contacts";
|
"bottom_navigation_contacts_label" = "Contacts";
|
||||||
"bottom_navigation_conversations_label" = "Conversations";
|
"bottom_navigation_conversations_label" = "Conversations";
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,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_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_third_party_sip_account_warning_ok" = "J’ai compris";
|
||||||
"assistant_web_platform_link" = "subscribe.linphone.org";
|
"assistant_web_platform_link" = "subscribe.linphone.org";
|
||||||
|
"authentication_id" = "Identifiant de connexion (si différent)";
|
||||||
"bottom_navigation_calls_label" = "Appels";
|
"bottom_navigation_calls_label" = "Appels";
|
||||||
"bottom_navigation_contacts_label" = "Contacts";
|
"bottom_navigation_contacts_label" = "Contacts";
|
||||||
"bottom_navigation_conversations_label" = "Conversations";
|
"bottom_navigation_conversations_label" = "Conversations";
|
||||||
|
|
@ -337,7 +338,7 @@
|
||||||
"manage_account_international_prefix" = "Indicatif international";
|
"manage_account_international_prefix" = "Indicatif international";
|
||||||
"manage_account_no_device" = "Aucun appareil n'a été trouvé…";
|
"manage_account_no_device" = "Aucun appareil n'a été trouvé…";
|
||||||
"manage_account_remove_picture" = "Supprimer";
|
"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_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_connected_summary" = "Vous êtes en ligne, on peut vous joindre.";
|
||||||
"manage_account_status_failed_summary" = "Erreur de connexion, vérifiez vos paramètres.";
|
"manage_account_status_failed_summary" = "Erreur de connexion, vérifiez vos paramètres.";
|
||||||
|
|
|
||||||
229
Linphone/Localizable/sk.lproj/Localizable.strings
Normal file
229
Linphone/Localizable/sk.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
"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" = "Nahrať";
|
||||||
|
"dialog_no" = "Nie";
|
||||||
|
"dialog_ok" = "OK";
|
||||||
|
"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)";
|
||||||
|
|
@ -30,10 +30,7 @@
|
||||||
<entry name="protocols" overwrite="true">stun,ice</entry>
|
<entry name="protocols" overwrite="true">stun,ice</entry>
|
||||||
</section>
|
</section>
|
||||||
<section name="sip">
|
<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>
|
<entry name="media_encryption_mandatory">1</entry>
|
||||||
</section>
|
</section>
|
||||||
<section name="net">
|
|
||||||
<entry name="friendlist_subscription_enabled" overwrite="true">1</entry>
|
|
||||||
</section>
|
|
||||||
</config>
|
</config>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
<entry name="conference_factory_uri" overwrite="true"></entry>
|
<entry name="conference_factory_uri" overwrite="true"></entry>
|
||||||
<entry name="audio_video_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="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="cpim_in_basic_chat_rooms_enabled" overwrite="true">0</entry>
|
||||||
<entry name="rtp_bundle" overwrite="true">0</entry>
|
<entry name="rtp_bundle" overwrite="true">0</entry>
|
||||||
<entry name="lime_server_url" overwrite="true"></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
|
update_presence_model_timestamp_before_publish_expires_refresh=1
|
||||||
use_rfc2833=1
|
use_rfc2833=1
|
||||||
use_info=1
|
use_info=1
|
||||||
|
rls_uri=sips:rls@sip.linphone.org
|
||||||
|
|
||||||
[net]
|
[net]
|
||||||
#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit"
|
#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_delay=1000
|
||||||
chat_messages_aggregation=1
|
chat_messages_aggregation=1
|
||||||
update_presence_model_timestamp_before_publish_expires_refresh=1
|
update_presence_model_timestamp_before_publish_expires_refresh=1
|
||||||
rls_uri=sips:rls@sip.linphone.org
|
|
||||||
|
|
||||||
[sound]
|
[sound]
|
||||||
#remove this property for any application that is not Linphone public version itself
|
#remove this property for any application that is not Linphone public version itself
|
||||||
ec_calibrator_cool_tones=1
|
ec_calibrator_cool_tones=1
|
||||||
disable_ringing=1
|
disable_ringing=0
|
||||||
|
|
||||||
[audio]
|
[audio]
|
||||||
|
|
||||||
[video]
|
[video]
|
||||||
auto_resize_preview_to_keep_ratio=1
|
auto_resize_preview_to_keep_ratio=1
|
||||||
max_conference_size=vga
|
max_conference_size=vga
|
||||||
automatically_accept=1
|
|
||||||
automatically_initiate=0
|
|
||||||
|
|
||||||
[misc]
|
[misc]
|
||||||
enable_basic_to_client_group_chat_room_migration=0
|
enable_basic_to_client_group_chat_room_migration=0
|
||||||
|
|
@ -49,6 +46,7 @@ record_aware=1
|
||||||
|
|
||||||
[account_creator]
|
[account_creator]
|
||||||
url=https://subscribe.linphone.org/api/
|
url=https://subscribe.linphone.org/api/
|
||||||
|
backend=1
|
||||||
|
|
||||||
[lime]
|
[lime]
|
||||||
lime_update_threshold=86400
|
lime_update_threshold=86400
|
||||||
|
|
|
||||||
|
|
@ -342,11 +342,13 @@ class TelecomManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func terminateCall(call: Call) {
|
func terminateCall(call: Call) {
|
||||||
do {
|
CoreContext.shared.doOnCoreQueue { _ in
|
||||||
try call.terminate()
|
do {
|
||||||
Log.info("Call terminated")
|
try call.terminate()
|
||||||
} catch {
|
Log.info("Call terminated")
|
||||||
Log.error("Failed to terminate call failed because \(error)")
|
} catch {
|
||||||
|
Log.error("Failed to terminate call failed because \(error)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
struct LoginFragment: View {
|
struct LoginFragment: View {
|
||||||
|
|
||||||
@ObservedObject private var coreContext = CoreContext.shared
|
@ObservedObject private var coreContext = CoreContext.shared
|
||||||
|
|
||||||
@StateObject private var accountLoginViewModel = AccountLoginViewModel()
|
@StateObject private var accountLoginViewModel = AccountLoginViewModel()
|
||||||
|
@StateObject private var keyboard = KeyboardResponder()
|
||||||
|
|
||||||
@State private var isSecured: Bool = true
|
@State private var isSecured: Bool = true
|
||||||
|
|
||||||
|
|
@ -37,6 +39,8 @@ struct LoginFragment: View {
|
||||||
@State private var isLinkSIPActive = false
|
@State private var isLinkSIPActive = false
|
||||||
@State private var isLinkREGActive = false
|
@State private var isLinkREGActive = false
|
||||||
|
|
||||||
|
@State var isShowHelpFragment = false
|
||||||
|
|
||||||
var isShowBack = false
|
var isShowBack = false
|
||||||
|
|
||||||
var onBackPressed: (() -> Void)?
|
var onBackPressed: (() -> Void)?
|
||||||
|
|
@ -93,6 +97,14 @@ struct LoginFragment: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowHelpFragment {
|
||||||
|
HelpFragment(
|
||||||
|
isShowHelpFragment: $isShowHelpFragment
|
||||||
|
)
|
||||||
|
.transition(.move(edge: .trailing))
|
||||||
|
.zIndex(3)
|
||||||
|
}
|
||||||
|
|
||||||
if coreContext.loggingInProgress {
|
if coreContext.loggingInProgress {
|
||||||
PopupLoadingView()
|
PopupLoadingView()
|
||||||
.background(.black.opacity(0.65))
|
.background(.black.opacity(0.65))
|
||||||
|
|
@ -129,6 +141,26 @@ struct LoginFragment: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
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")
|
Text("assistant_account_login")
|
||||||
|
|
@ -313,7 +345,7 @@ struct LoginFragment: View {
|
||||||
.foregroundStyle(Color.grayMain2c700)
|
.foregroundStyle(Color.grayMain2c700)
|
||||||
.padding(.horizontal, 10)
|
.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)
|
.default_text_style_white_600(styleSize: 20)
|
||||||
.frame(height: 35)
|
.frame(height: 35)
|
||||||
})
|
})
|
||||||
|
|
@ -347,6 +379,7 @@ struct LoginFragment: View {
|
||||||
.clipped()
|
.clipped()
|
||||||
}
|
}
|
||||||
.frame(minHeight: geometry.size.height)
|
.frame(minHeight: geometry.size.height)
|
||||||
|
.padding(.bottom, keyboard.currentHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
func acceptGeneralTerms() {
|
func acceptGeneralTerms() {
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,12 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct RegisterFragment: View {
|
struct RegisterFragment: View {
|
||||||
|
|
||||||
@ObservedObject var registerViewModel: RegisterViewModel
|
|
||||||
@ObservedObject var sharedMainViewModel = SharedMainViewModel.shared
|
@ObservedObject var sharedMainViewModel = SharedMainViewModel.shared
|
||||||
|
|
||||||
|
@StateObject private var registerViewModel = RegisterViewModel()
|
||||||
|
|
||||||
|
@StateObject private var keyboard = KeyboardResponder()
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
@State private var isSecured: Bool = true
|
@State private var isSecured: Bool = true
|
||||||
|
|
@ -269,13 +271,13 @@ struct RegisterFragment: View {
|
||||||
})
|
})
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.padding(.vertical, 10)
|
.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)
|
.cornerRadius(60)
|
||||||
.disabled(!registerViewModel.isLinkActive)
|
.disabled(!registerViewModel.isLinkActive)
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
.simultaneousGesture(
|
.simultaneousGesture(
|
||||||
TapGesture().onEnded {
|
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 {
|
withAnimation {
|
||||||
self.isShowPopup = true
|
self.isShowPopup = true
|
||||||
}
|
}
|
||||||
|
|
@ -347,11 +349,8 @@ struct RegisterFragment: View {
|
||||||
.clipped()
|
.clipped()
|
||||||
}
|
}
|
||||||
.frame(minHeight: geometry.size.height)
|
.frame(minHeight: geometry.size.height)
|
||||||
|
.padding(.bottom, keyboard.currentHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
|
||||||
RegisterFragment(registerViewModel: RegisterViewModel())
|
|
||||||
}
|
|
||||||
|
|
||||||
// swiftlint:enable line_length
|
// swiftlint:enable line_length
|
||||||
|
|
|
||||||
|
|
@ -24,25 +24,61 @@ struct ThirdPartySipAccountLoginFragment: View {
|
||||||
@ObservedObject private var coreContext = CoreContext.shared
|
@ObservedObject private var coreContext = CoreContext.shared
|
||||||
@ObservedObject var accountLoginViewModel: AccountLoginViewModel
|
@ObservedObject var accountLoginViewModel: AccountLoginViewModel
|
||||||
|
|
||||||
|
@StateObject private var keyboard = KeyboardResponder()
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
@State private var isSecured: Bool = true
|
@State private var isSecured: Bool = true
|
||||||
|
@State private var advancedSettingsIsOpen: Bool = false
|
||||||
|
|
||||||
@FocusState var isNameFocused: Bool
|
@FocusState var isNameFocused: Bool
|
||||||
@FocusState var isPasswordFocused: Bool
|
@FocusState var isPasswordFocused: Bool
|
||||||
@FocusState var isDomainFocused: Bool
|
@FocusState var isDomainFocused: Bool
|
||||||
@FocusState var isDisplayNameFocused: Bool
|
@FocusState var isDisplayNameFocused: Bool
|
||||||
|
@FocusState var isSipProxyUrlFocused: Bool
|
||||||
|
@FocusState var isAuthIdFocused: Bool
|
||||||
|
@FocusState var isOutboundProxyFocused: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
if #available(iOS 16.4, *) {
|
ScrollViewReader { proxy in
|
||||||
ScrollView(.vertical) {
|
if #available(iOS 16.4, *) {
|
||||||
innerScrollView(geometry: geometry)
|
ScrollView(.vertical) {
|
||||||
}
|
innerScrollView(geometry: geometry)
|
||||||
.scrollBounceBehavior(.basedOnSize)
|
}
|
||||||
} else {
|
.scrollBounceBehavior(.basedOnSize)
|
||||||
ScrollView(.vertical) {
|
.onChange(of: isAuthIdFocused) { field in
|
||||||
innerScrollView(geometry: geometry)
|
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)
|
.stroke(Color.gray200, lineWidth: 1)
|
||||||
)
|
)
|
||||||
.padding(.bottom)
|
.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)
|
.frame(maxWidth: SharedMainViewModel.shared.maxWidth)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
|
@ -241,6 +345,7 @@ struct ThirdPartySipAccountLoginFragment: View {
|
||||||
.clipped()
|
.clipped()
|
||||||
}
|
}
|
||||||
.frame(minHeight: geometry.size.height)
|
.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 domain: String = "sip.linphone.org"
|
||||||
@Published var displayName: String = ""
|
@Published var displayName: String = ""
|
||||||
@Published var transportType: String = "TLS"
|
@Published var transportType: String = "TLS"
|
||||||
|
@Published var authId: String = ""
|
||||||
|
@Published var outboundProxy: String = ""
|
||||||
|
|
||||||
private var mCoreDelegate: CoreDelegate!
|
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
|
// The realm will be determined automatically from the first register, as well as the algorithm
|
||||||
let authInfo = try Factory.Instance.createAuthInfo(
|
let authInfo = try Factory.Instance.createAuthInfo(
|
||||||
username: self.username,
|
username: self.username,
|
||||||
userid: "",
|
userid: self.authId,
|
||||||
passwd: self.passwd,
|
passwd: self.passwd,
|
||||||
ha1: "",
|
ha1: "",
|
||||||
realm: "",
|
realm: "",
|
||||||
|
|
@ -100,15 +102,26 @@ class AccountLoginViewModel: ObservableObject {
|
||||||
try accountParams.setIdentityaddress(newValue: identity)
|
try accountParams.setIdentityaddress(newValue: identity)
|
||||||
|
|
||||||
// We also need to configure where the proxy server is located
|
// 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
|
// We use the Address object to easily set the transport protocol
|
||||||
try address.setTransport(newValue: transport)
|
try address.setTransport(newValue: transport)
|
||||||
try accountParams.setServeraddress(newValue: address)
|
try accountParams.setServeraddress(newValue: address)
|
||||||
// And we ensure the account will start the registration process
|
// And we ensure the account will start the registration process
|
||||||
accountParams.registerEnabled = true
|
accountParams.registerEnabled = true
|
||||||
accountParams.pushNotificationAllowed = true
|
|
||||||
accountParams.remotePushNotificationAllowed = true
|
if accountParams.pushNotificationAllowed {
|
||||||
|
accountParams.pushNotificationAllowed = true
|
||||||
|
accountParams.remotePushNotificationAllowed = true
|
||||||
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
let pushEnvironment = ".dev"
|
let pushEnvironment = ".dev"
|
||||||
#else
|
#else
|
||||||
|
|
@ -116,10 +129,6 @@ class AccountLoginViewModel: ObservableObject {
|
||||||
#endif
|
#endif
|
||||||
accountParams.pushNotificationConfig?.provider = "apns" + pushEnvironment
|
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
|
self.mCoreDelegate = CoreDelegateStub(onAccountRegistrationStateChanged: { (core: Core, account: Account, state: RegistrationState, message: String) in
|
||||||
|
|
||||||
Log.info("New registration state is \(state) for user id " +
|
Log.info("New registration state is \(state) for user id " +
|
||||||
|
|
@ -153,6 +162,8 @@ class AccountLoginViewModel: ObservableObject {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.domain = "sip.linphone.org"
|
self.domain = "sip.linphone.org"
|
||||||
self.transportType = "TLS"
|
self.transportType = "TLS"
|
||||||
|
self.authId = ""
|
||||||
|
self.outboundProxy = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch { NSLog(error.localizedDescription) }
|
} catch { NSLog(error.localizedDescription) }
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class RegisterViewModel: ObservableObject {
|
||||||
@Published var displayName: String = ""
|
@Published var displayName: String = ""
|
||||||
@Published var transportType: String = "TLS"
|
@Published var transportType: String = "TLS"
|
||||||
|
|
||||||
@Published var dialPlanValueSelected: String = "🇫🇷 +33"
|
@Published var dialPlanValueSelected: String = "---"
|
||||||
|
|
||||||
private let HASHALGORITHM = "SHA-256"
|
private let HASHALGORITHM = "SHA-256"
|
||||||
|
|
||||||
|
|
@ -257,7 +257,7 @@ class RegisterViewModel: ObservableObject {
|
||||||
|
|
||||||
SharedMainViewModel.shared.dialPlansList.forEach { dial in
|
SharedMainViewModel.shared.dialPlansList.forEach { dial in
|
||||||
let countryCode = dialPlanValueSelected.components(separatedBy: "+")
|
let countryCode = dialPlanValueSelected.components(separatedBy: "+")
|
||||||
if dial.countryCallingCode == countryCode[1] {
|
if dial?.countryCallingCode == countryCode[1] {
|
||||||
dialPlan = dial
|
dialPlan = dial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -412,7 +412,7 @@ class RegisterViewModel: ObservableObject {
|
||||||
|
|
||||||
for dial in SharedMainViewModel.shared.dialPlansList {
|
for dial in SharedMainViewModel.shared.dialPlansList {
|
||||||
let countryCode = self.dialPlanValueSelected.components(separatedBy: "+")
|
let countryCode = self.dialPlanValueSelected.components(separatedBy: "+")
|
||||||
if dial.countryCallingCode == countryCode[1] {
|
if dial?.countryCallingCode == countryCode[1] {
|
||||||
dialPlan = dial
|
dialPlan = dial
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ struct CallView: View {
|
||||||
.padding(.leading, 50)
|
.padding(.leading, 50)
|
||||||
.padding(.top, 35)
|
.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)
|
.foregroundStyle(Color.blueInfo500)
|
||||||
.default_text_style_white(styleSize: 12)
|
.default_text_style_white(styleSize: 12)
|
||||||
.padding(.top, 35)
|
.padding(.top, 35)
|
||||||
|
|
@ -558,6 +558,10 @@ struct CallView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
|
coreContext.doOnCoreQueue { core in
|
||||||
|
core.nativeVideoWindow = nil
|
||||||
|
}
|
||||||
|
|
||||||
if callViewModel.videoDisplayed {
|
if callViewModel.videoDisplayed {
|
||||||
if !callViewModel.isPaused && TelecomManager.shared.callInProgress
|
if !callViewModel.isPaused && TelecomManager.shared.callInProgress
|
||||||
&& !(coreContext.pipViewModel.pipController?.isPictureInPictureActive ?? false) {
|
&& !(coreContext.pipViewModel.pipController?.isPictureInPictureActive ?? false) {
|
||||||
|
|
@ -585,6 +589,11 @@ struct CallView: View {
|
||||||
core.nativePreviewWindow = view
|
core.nativePreviewWindow = view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
coreContext.doOnCoreQueue { core in
|
||||||
|
core.nativePreviewWindow = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
.aspectRatio(callViewModel.callStatsModel.sentVideoWindow.widthFactor/callViewModel.callStatsModel.sentVideoWindow.heightFactor, contentMode: .fill)
|
.aspectRatio(callViewModel.callStatsModel.sentVideoWindow.widthFactor/callViewModel.callStatsModel.sentVideoWindow.heightFactor, contentMode: .fill)
|
||||||
.frame(maxWidth: callViewModel.callStatsModel.sentVideoWindow.widthFactor * 256,
|
.frame(maxWidth: callViewModel.callStatsModel.sentVideoWindow.widthFactor * 256,
|
||||||
maxHeight: callViewModel.callStatsModel.sentVideoWindow.heightFactor * 256)
|
maxHeight: callViewModel.callStatsModel.sentVideoWindow.heightFactor * 256)
|
||||||
|
|
@ -703,6 +712,11 @@ struct CallView: View {
|
||||||
core.nativePreviewWindow = view
|
core.nativePreviewWindow = view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
coreContext.doOnCoreQueue { core in
|
||||||
|
core.nativePreviewWindow = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
.aspectRatio(callViewModel.callStatsModel.sentVideoWindow.widthFactor/callViewModel.callStatsModel.sentVideoWindow.heightFactor, contentMode: .fill)
|
.aspectRatio(callViewModel.callStatsModel.sentVideoWindow.widthFactor/callViewModel.callStatsModel.sentVideoWindow.heightFactor, contentMode: .fill)
|
||||||
.frame(maxWidth: callViewModel.callStatsModel.sentVideoWindow.widthFactor * 256,
|
.frame(maxWidth: callViewModel.callStatsModel.sentVideoWindow.widthFactor * 256,
|
||||||
maxHeight: callViewModel.callStatsModel.sentVideoWindow.heightFactor * 256)
|
maxHeight: callViewModel.callStatsModel.sentVideoWindow.heightFactor * 256)
|
||||||
|
|
@ -897,6 +911,9 @@ struct CallView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
|
coreContext.doOnCoreQueue { core in
|
||||||
|
core.nativeVideoWindow = nil
|
||||||
|
}
|
||||||
if !callViewModel.isPaused && TelecomManager.shared.callInProgress
|
if !callViewModel.isPaused && TelecomManager.shared.callInProgress
|
||||||
&& !(coreContext.pipViewModel.pipController?.isPictureInPictureActive ?? false) {
|
&& !(coreContext.pipViewModel.pipController?.isPictureInPictureActive ?? false) {
|
||||||
// TODO: Enable PIP in 6.1
|
// TODO: Enable PIP in 6.1
|
||||||
|
|
@ -978,6 +995,11 @@ struct CallView: View {
|
||||||
core.nativePreviewWindow = 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)
|
.frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2)
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.clipped()
|
.clipped()
|
||||||
|
|
@ -1143,6 +1165,11 @@ struct CallView: View {
|
||||||
core.nativePreviewWindow = 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)
|
.frame(width: angleDegree == 0 ? 120*1.2 : 160*1.2, height: angleDegree == 0 ? 160*1.2 : 120*1.2)
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.clipped()
|
.clipped()
|
||||||
|
|
@ -1362,6 +1389,11 @@ struct CallView: View {
|
||||||
core.nativePreviewWindow = view
|
core.nativePreviewWindow = view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
coreContext.doOnCoreQueue { core in
|
||||||
|
core.nativePreviewWindow = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
.frame(
|
.frame(
|
||||||
width: 120 * ceil(maxValue / 120),
|
width: 120 * ceil(maxValue / 120),
|
||||||
height: 160 * ceil(maxValue / 120)
|
height: 160 * ceil(maxValue / 120)
|
||||||
|
|
@ -1598,6 +1630,11 @@ struct CallView: View {
|
||||||
core.nativePreviewWindow = view
|
core.nativePreviewWindow = view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
coreContext.doOnCoreQueue { core in
|
||||||
|
core.nativePreviewWindow = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
.frame(
|
.frame(
|
||||||
width: 160 * ceil(maxValue / 120),
|
width: 160 * ceil(maxValue / 120),
|
||||||
height: 120 * ceil(maxValue / 120)
|
height: 120 * ceil(maxValue / 120)
|
||||||
|
|
@ -1938,9 +1975,9 @@ struct CallView: View {
|
||||||
.frame(width: buttonSize, height: buttonSize)
|
.frame(width: buttonSize, height: buttonSize)
|
||||||
.background(Color.gray500)
|
.background(Color.gray500)
|
||||||
.cornerRadius(40)
|
.cornerRadius(40)
|
||||||
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote)
|
.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote || telecomManager.outgoingCallStarted)
|
||||||
|
|
||||||
if callViewModel.isPaused || telecomManager.isPausedByRemote {
|
if callViewModel.isPaused || telecomManager.isPausedByRemote || telecomManager.outgoingCallStarted {
|
||||||
Color.gray600.opacity(0.8)
|
Color.gray600.opacity(0.8)
|
||||||
.cornerRadius(40)
|
.cornerRadius(40)
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
|
|
@ -2219,7 +2256,7 @@ struct CallView: View {
|
||||||
changeLayoutSheet = true
|
changeLayoutSheet = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Image("notebook")
|
Image("layout")
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
@ -2648,7 +2685,7 @@ struct CallView: View {
|
||||||
changeLayoutSheet = true
|
changeLayoutSheet = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Image("notebook")
|
Image("layout")
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
|
||||||
|
|
@ -713,44 +713,45 @@ class CallViewModel: ObservableObject {
|
||||||
|
|
||||||
func displayMyVideo() {
|
func displayMyVideo() {
|
||||||
coreContext.doOnCoreQueue { core in
|
coreContext.doOnCoreQueue { core in
|
||||||
if self.currentCall != nil {
|
guard let call = self.currentCall else { return }
|
||||||
do {
|
|
||||||
let params = try core.createCallParams(call: self.currentCall)
|
guard call.state == .StreamsRunning else {
|
||||||
|
Log.warn("\(CallViewModel.TAG) displayMyVideo called in invalid state: \(call.state), skipping update")
|
||||||
if (params.videoEnabled == false) {
|
return
|
||||||
Log.info("\(CallViewModel.TAG) Conference found and video disabled in params, enabling it")
|
}
|
||||||
params.videoEnabled = true
|
|
||||||
params.videoDirection = MediaDirection.SendRecv
|
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 {
|
} else {
|
||||||
if (params.videoDirection == MediaDirection.SendRecv || params.videoDirection == MediaDirection.SendOnly) {
|
Log.info("\(CallViewModel.TAG) Video already enabled, switching to send & recv")
|
||||||
Log.info(
|
params.videoDirection = .SendRecv
|
||||||
"\(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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func toggleVideoMode(isAudioOnlyMode: Bool) {
|
||||||
coreContext.doOnCoreQueue { core in
|
coreContext.doOnCoreQueue { core in
|
||||||
|
|
@ -985,14 +986,11 @@ class CallViewModel: ObservableObject {
|
||||||
self.isNotEncrypted = false
|
self.isNotEncrypted = false
|
||||||
}
|
}
|
||||||
case MediaEncryption.None:
|
case MediaEncryption.None:
|
||||||
|
let isNotEncryptedTmp = self.currentCall?.state == .StreamsRunning
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.isMediaEncrypted = false
|
self.isMediaEncrypted = false
|
||||||
self.isZrtp = false
|
self.isZrtp = false
|
||||||
if self.currentCall!.state == .StreamsRunning {
|
self.isNotEncrypted = isNotEncryptedTmp
|
||||||
self.isNotEncrypted = true
|
|
||||||
} else {
|
|
||||||
self.isNotEncrypted = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ struct EditContactFragment: View {
|
||||||
@State private var orientation = UIDevice.current.orientation
|
@State private var orientation = UIDevice.current.orientation
|
||||||
|
|
||||||
@StateObject private var editContactViewModel: EditContactViewModel
|
@StateObject private var editContactViewModel: EditContactViewModel
|
||||||
|
@StateObject private var keyboard = KeyboardResponder()
|
||||||
|
|
||||||
@Binding var isShowEditContactFragment: Bool
|
@Binding var isShowEditContactFragment: Bool
|
||||||
@Binding var isShowDismissPopup: Bool
|
@Binding var isShowDismissPopup: Bool
|
||||||
|
|
@ -100,8 +101,8 @@ struct EditContactFragment: View {
|
||||||
if editContactViewModel.selectedEditFriend == nil
|
if editContactViewModel.selectedEditFriend == nil
|
||||||
&& editContactViewModel.firstName.isEmpty
|
&& editContactViewModel.firstName.isEmpty
|
||||||
&& editContactViewModel.lastName.isEmpty
|
&& editContactViewModel.lastName.isEmpty
|
||||||
&& editContactViewModel.sipAddresses.first!.isEmpty
|
&& editContactViewModel.sipAddresses.first?.isEmpty ?? true
|
||||||
&& editContactViewModel.phoneNumbers.first!.isEmpty
|
&& editContactViewModel.phoneNumbers.first?.isEmpty ?? true
|
||||||
&& editContactViewModel.company.isEmpty
|
&& editContactViewModel.company.isEmpty
|
||||||
&& editContactViewModel.jobTitle.isEmpty {
|
&& editContactViewModel.jobTitle.isEmpty {
|
||||||
delayColorDismiss()
|
delayColorDismiss()
|
||||||
|
|
@ -113,8 +114,8 @@ struct EditContactFragment: View {
|
||||||
} else {
|
} else {
|
||||||
if editContactViewModel.firstName.isEmpty
|
if editContactViewModel.firstName.isEmpty
|
||||||
&& editContactViewModel.lastName.isEmpty
|
&& editContactViewModel.lastName.isEmpty
|
||||||
&& editContactViewModel.sipAddresses.first!.isEmpty
|
&& editContactViewModel.sipAddresses.first?.isEmpty ?? true
|
||||||
&& editContactViewModel.phoneNumbers.first!.isEmpty
|
&& editContactViewModel.phoneNumbers.first?.isEmpty ?? true
|
||||||
&& editContactViewModel.company.isEmpty
|
&& editContactViewModel.company.isEmpty
|
||||||
&& editContactViewModel.jobTitle.isEmpty {
|
&& editContactViewModel.jobTitle.isEmpty {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
|
|
@ -318,7 +319,6 @@ struct EditContactFragment: View {
|
||||||
.padding(.bottom, -5)
|
.padding(.bottom, -5)
|
||||||
|
|
||||||
ForEach(editContactViewModel.sipAddresses.indices, id: \.self) { index in
|
ForEach(editContactViewModel.sipAddresses.indices, id: \.self) { index in
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
TextField("sip_address", text: $editContactViewModel.sipAddresses[index])
|
TextField("sip_address", text: $editContactViewModel.sipAddresses[index])
|
||||||
.default_text_style(styleSize: 15)
|
.default_text_style(styleSize: 15)
|
||||||
|
|
@ -336,27 +336,27 @@ struct EditContactFragment: View {
|
||||||
)
|
)
|
||||||
.focused($isSIPAddressFocused, equals: index)
|
.focused($isSIPAddressFocused, equals: index)
|
||||||
.onChange(of: editContactViewModel.sipAddresses[index]) { newValue in
|
.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("")
|
editContactViewModel.sipAddresses.append("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
guard editContactViewModel.sipAddresses.indices.contains(index) else { return }
|
||||||
editContactViewModel.sipAddresses.remove(at: index)
|
editContactViewModel.sipAddresses.remove(at: index)
|
||||||
}, label: {
|
}) {
|
||||||
Image("x")
|
Image("x")
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundStyle(
|
.foregroundStyle(
|
||||||
editContactViewModel.sipAddresses[index].isEmpty && editContactViewModel.sipAddresses.count == index + 1
|
editContactViewModel.sipAddresses[index].isEmpty && index == editContactViewModel.sipAddresses.count - 1
|
||||||
? Color.gray100
|
? Color.gray100
|
||||||
: Color.grayMain2c600
|
: Color.grayMain2c600
|
||||||
)
|
)
|
||||||
.frame(width: 25, height: 25)
|
.frame(width: 25, height: 25)
|
||||||
.padding(.all, 10)
|
.padding(.all, 10)
|
||||||
})
|
}
|
||||||
.disabled(editContactViewModel.sipAddresses[index].isEmpty && editContactViewModel.sipAddresses.count == index + 1)
|
.disabled(editContactViewModel.sipAddresses[index].isEmpty && index == editContactViewModel.sipAddresses.count - 1)
|
||||||
.frame(maxHeight: .infinity)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -367,12 +367,12 @@ struct EditContactFragment: View {
|
||||||
.default_text_style_700(styleSize: 15)
|
.default_text_style_700(styleSize: 15)
|
||||||
.padding(.bottom, -5)
|
.padding(.bottom, -5)
|
||||||
|
|
||||||
ForEach(0..<editContactViewModel.phoneNumbers.count, id: \.self) { index in
|
ForEach(editContactViewModel.phoneNumbers.indices, id: \.self) { index in
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
TextField("phone_number", text: $editContactViewModel.phoneNumbers[index])
|
TextField("phone_number", text: $editContactViewModel.phoneNumbers[index])
|
||||||
.default_text_style(styleSize: 15)
|
.default_text_style(styleSize: 15)
|
||||||
.textContentType(.oneTimeCode)
|
.textContentType(.oneTimeCode)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.phonePad)
|
||||||
.frame(height: 25)
|
.frame(height: 25)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.padding(.vertical, 15)
|
.padding(.vertical, 15)
|
||||||
|
|
@ -385,7 +385,7 @@ struct EditContactFragment: View {
|
||||||
)
|
)
|
||||||
.focused($isPhoneNumberFocused, equals: index)
|
.focused($isPhoneNumberFocused, equals: index)
|
||||||
.onChange(of: editContactViewModel.phoneNumbers[index]) { newValue in
|
.onChange(of: editContactViewModel.phoneNumbers[index]) { newValue in
|
||||||
if !newValue.isEmpty && index + 1 == editContactViewModel.phoneNumbers.count {
|
if !newValue.isEmpty && index == editContactViewModel.phoneNumbers.count - 1 {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
editContactViewModel.phoneNumbers.append("")
|
editContactViewModel.phoneNumbers.append("")
|
||||||
}
|
}
|
||||||
|
|
@ -393,21 +393,21 @@ struct EditContactFragment: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
guard editContactViewModel.phoneNumbers.indices.contains(index) else { return }
|
||||||
editContactViewModel.phoneNumbers.remove(at: index)
|
editContactViewModel.phoneNumbers.remove(at: index)
|
||||||
}, label: {
|
}) {
|
||||||
Image("x")
|
Image("x")
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundStyle(
|
.foregroundStyle(
|
||||||
editContactViewModel.phoneNumbers[index].isEmpty && editContactViewModel.phoneNumbers.count == index + 1
|
editContactViewModel.phoneNumbers[index].isEmpty && index == editContactViewModel.phoneNumbers.count - 1
|
||||||
? Color.gray100
|
? Color.gray100
|
||||||
: Color.grayMain2c600
|
: Color.grayMain2c600
|
||||||
)
|
)
|
||||||
.frame(width: 25, height: 25)
|
.frame(width: 25, height: 25)
|
||||||
.padding(.all, 10)
|
.padding(.all, 10)
|
||||||
})
|
}
|
||||||
.disabled(editContactViewModel.phoneNumbers[index].isEmpty && editContactViewModel.phoneNumbers.count == index + 1)
|
.disabled(editContactViewModel.phoneNumbers[index].isEmpty && index == editContactViewModel.phoneNumbers.count - 1)
|
||||||
.frame(maxHeight: .infinity)
|
|
||||||
}
|
}
|
||||||
.zIndex(isPhoneNumberFocused == index ? 1 : 0)
|
.zIndex(isPhoneNumberFocused == index ? 1 : 0)
|
||||||
.transition(.move(edge: .top))
|
.transition(.move(edge: .top))
|
||||||
|
|
@ -510,83 +510,90 @@ struct EditContactFragment: View {
|
||||||
organizationName: editContactViewModel.company,
|
organizationName: editContactViewModel.company,
|
||||||
jobTitle: editContactViewModel.jobTitle,
|
jobTitle: editContactViewModel.jobTitle,
|
||||||
displayName: "",
|
displayName: "",
|
||||||
sipAddresses: editContactViewModel.sipAddresses.map { $0 },
|
sipAddresses: editContactViewModel.sipAddresses,
|
||||||
phoneNumbers: editContactViewModel.phoneNumbers.map { PhoneNumber(numLabel: "", num: $0)},
|
phoneNumbers: editContactViewModel.phoneNumbers.map { PhoneNumber(numLabel: "", num: $0) },
|
||||||
imageData: ""
|
imageData: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
if editContactViewModel.selectedEditFriend != nil && editContactViewModel.selectedEditFriend!.friend != nil && selectedImage == nil &&
|
let existingFriend = editContactViewModel.selectedEditFriend?.friend
|
||||||
!removedImage && editContactViewModel.selectedEditFriend!.friend!.photo!.suffix(11) != "default.png" {
|
let friendHasCustomPhoto = existingFriend?.photo?.suffix(11) != "default.png"
|
||||||
ContactsManager.shared.saveFriend(
|
|
||||||
result: String(editContactViewModel.selectedEditFriend!.friend!.photo!.dropFirst(6)),
|
// Case: editing existing friend without changing the image
|
||||||
contact: newContact,
|
if let existingFriend = existingFriend,
|
||||||
existingFriend: editContactViewModel.selectedEditFriend!.friend, completion: {_ in
|
selectedImage == nil,
|
||||||
if let selectedFriendTmp = editContactViewModel.selectedEditFriend?.friend {
|
!removedImage,
|
||||||
let addressTmp = selectedFriendTmp.address?.clone()?.asStringUriOnly() ?? ""
|
friendHasCustomPhoto,
|
||||||
SharedMainViewModel.shared.displayedFriend?.resetContactAvatarModel(
|
let photo = existingFriend.photo {
|
||||||
friend: selectedFriendTmp,
|
|
||||||
name: selectedFriendTmp.name ?? "",
|
let resultPhoto = String(photo.dropFirst(6))
|
||||||
address: addressTmp,
|
ContactsManager.shared.saveFriend(result: resultPhoto, contact: newContact, existingFriend: existingFriend) { _ in
|
||||||
withPresence: SharedMainViewModel.shared.displayedFriend?.withPresence
|
self.updateAvatar(for: existingFriend)
|
||||||
)
|
self.finishUIUpdate(existingFriend: existingFriend)
|
||||||
}
|
}
|
||||||
let friendIsNil = editContactViewModel.selectedEditFriend?.friend == nil
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
delayColorDismiss()
|
|
||||||
if friendIsNil {
|
|
||||||
withAnimation {
|
|
||||||
isShowEditContactFragment.toggle()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withAnimation {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editContactViewModel.resetValues()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
ContactsManager.shared.saveImage(
|
// Case: creating new friend or updating with a new image
|
||||||
image: selectedImage
|
let imageToSave = selectedImage ?? ContactsManager.shared.textToImage(
|
||||||
?? ContactsManager.shared.textToImage(
|
firstName: editContactViewModel.firstName,
|
||||||
firstName: editContactViewModel.firstName, lastName: editContactViewModel.lastName),
|
lastName: editContactViewModel.lastName
|
||||||
name: editContactViewModel.firstName
|
)
|
||||||
+ editContactViewModel.lastName,
|
let prefix = selectedImage == nil ? "-default" : ""
|
||||||
prefix: ((selectedImage == nil) ? "-default" : ""),
|
|
||||||
contact: newContact, linphoneFriend: "Linphone address-book", existingFriend: editContactViewModel.selectedEditFriend?.friend) {
|
saveImageThreadSafe(
|
||||||
if let selectedFriendTmp = editContactViewModel.selectedEditFriend?.friend {
|
image: imageToSave,
|
||||||
let addressTmp = selectedFriendTmp.address?.clone()?.asStringUriOnly() ?? ""
|
name: editContactViewModel.firstName + editContactViewModel.lastName,
|
||||||
SharedMainViewModel.shared.displayedFriend?.resetContactAvatarModel(
|
prefix: prefix,
|
||||||
friend: selectedFriendTmp,
|
contact: newContact,
|
||||||
name: selectedFriendTmp.name ?? "",
|
existingFriend: existingFriend,
|
||||||
address: addressTmp,
|
linphoneFriend: "Linphone address-book"
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
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 {
|
#Preview {
|
||||||
|
|
|
||||||
|
|
@ -346,32 +346,34 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
.frame(height: geometry.size.height/4)
|
.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)
|
.frame(width: 75, height: geometry.size.height)
|
||||||
|
|
@ -405,6 +407,17 @@ struct ContentView: View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
if searchIsActive == false {
|
if searchIsActive == false {
|
||||||
HStack {
|
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,
|
if let index = accountProfileViewModel.defaultAccountModelIndex,
|
||||||
index < coreContext.accounts.count {
|
index < coreContext.accounts.count {
|
||||||
|
|
||||||
|
|
@ -473,7 +486,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"))))
|
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)
|
.default_text_style_white_800(styleSize: 20)
|
||||||
.padding(.leading, 10)
|
.padding(.leading, 2)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|
@ -895,31 +908,33 @@ struct ContentView: View {
|
||||||
.frame(width: 66)
|
.frame(width: 66)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
if !sharedMainViewModel.disableMeetingFeature {
|
||||||
Button(action: {
|
Spacer()
|
||||||
sharedMainViewModel.changeIndexView(indexViewInt: 3)
|
Button(action: {
|
||||||
sharedMainViewModel.displayedFriend = nil
|
sharedMainViewModel.changeIndexView(indexViewInt: 3)
|
||||||
sharedMainViewModel.displayedCall = nil
|
sharedMainViewModel.displayedFriend = nil
|
||||||
sharedMainViewModel.displayedConversation = nil
|
sharedMainViewModel.displayedCall = nil
|
||||||
}, label: {
|
sharedMainViewModel.displayedConversation = nil
|
||||||
VStack {
|
}, label: {
|
||||||
Image("video-conference")
|
VStack {
|
||||||
.renderingMode(.template)
|
Image("video-conference")
|
||||||
.resizable()
|
.renderingMode(.template)
|
||||||
.foregroundStyle(sharedMainViewModel.indexView == 3 ? Color.orangeMain500 : Color.grayMain2c600)
|
.resizable()
|
||||||
.frame(width: 25, height: 25)
|
.foregroundStyle(sharedMainViewModel.indexView == 3 ? Color.orangeMain500 : Color.grayMain2c600)
|
||||||
if sharedMainViewModel.indexView == 3 {
|
.frame(width: 25, height: 25)
|
||||||
Text("bottom_navigation_meetings_label")
|
if sharedMainViewModel.indexView == 3 {
|
||||||
.default_text_style_700(styleSize: 9)
|
Text("bottom_navigation_meetings_label")
|
||||||
} else {
|
.default_text_style_700(styleSize: 9)
|
||||||
Text("bottom_navigation_meetings_label")
|
} else {
|
||||||
.default_text_style(styleSize: 9)
|
Text("bottom_navigation_meetings_label")
|
||||||
|
.default_text_style(styleSize: 9)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.padding(.top)
|
||||||
.padding(.top)
|
.frame(width: 66)
|
||||||
.frame(width: 66)
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
@ -1047,17 +1062,22 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if isShowEditContactFragment {
|
if isShowEditContactFragment {
|
||||||
EditContactFragment(
|
VStack {
|
||||||
isShowEditContactFragment: $isShowEditContactFragment,
|
EditContactFragment(
|
||||||
isShowDismissPopup: $isShowDismissPopup,
|
isShowEditContactFragment: $isShowEditContactFragment,
|
||||||
isShowEditContactFragmentAddress: isShowEditContactFragmentAddress
|
isShowDismissPopup: $isShowDismissPopup,
|
||||||
)
|
isShowEditContactFragmentAddress: isShowEditContactFragmentAddress
|
||||||
|
)
|
||||||
|
.frame(height: geometry.size.height)
|
||||||
|
.onAppear {
|
||||||
|
sharedMainViewModel.displayedFriend = nil
|
||||||
|
isShowEditContactFragmentAddress = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
.zIndex(3)
|
.zIndex(3)
|
||||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||||
.onAppear {
|
|
||||||
sharedMainViewModel.displayedFriend = nil
|
|
||||||
isShowEditContactFragmentAddress = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isShowStartCallFragment {
|
if isShowStartCallFragment {
|
||||||
|
|
|
||||||
|
|
@ -378,12 +378,15 @@ class ConversationViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, onEphemeralMessageTimerStarted: { (message: ChatMessage) in
|
}, onEphemeralMessageTimerStarted: { (message: ChatMessage) in
|
||||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
if !self.conversationMessagesSection.isEmpty,
|
||||||
let ephemeralExpireTimeTmp = message.ephemeralExpireTime
|
!self.conversationMessagesSection[0].rows.isEmpty,
|
||||||
|
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: { $0.eventModel.eventLogId == message.messageId }),
|
||||||
DispatchQueue.main.async {
|
indexMessage < self.conversationMessagesSection[0].rows.count {
|
||||||
if indexMessage != nil {
|
|
||||||
self.conversationMessagesSection[0].rows[indexMessage!].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
let ephemeralExpireTimeTmp = message.ephemeralExpireTime
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.conversationMessagesSection[0].rows[indexMessage].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -2001,25 +2004,23 @@ class ConversationViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetDisplayedChatRoom() {
|
func resetDisplayedChatRoom() {
|
||||||
if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty {
|
if let displayedConversation = self.sharedMainViewModel.displayedConversation {
|
||||||
if let displayedConversation = self.sharedMainViewModel.displayedConversation {
|
CoreContext.shared.doOnCoreQueue { core in
|
||||||
CoreContext.shared.doOnCoreQueue { core in
|
let nilParams: ConferenceParams? = nil
|
||||||
let nilParams: ConferenceParams? = nil
|
if let newChatRoom = core.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: displayedConversation.chatRoom.peerAddress, participants: nil) {
|
||||||
if let newChatRoom = core.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: displayedConversation.chatRoom.peerAddress, participants: nil) {
|
if LinphoneUtils.getChatRoomId(room: newChatRoom) == displayedConversation.id {
|
||||||
if LinphoneUtils.getChatRoomId(room: newChatRoom) == displayedConversation.id {
|
self.addConversationDelegate(chatRoom: newChatRoom)
|
||||||
self.addConversationDelegate(chatRoom: newChatRoom)
|
let conversation = ConversationModel(chatRoom: newChatRoom)
|
||||||
let conversation = ConversationModel(chatRoom: newChatRoom)
|
DispatchQueue.main.async {
|
||||||
DispatchQueue.main.async {
|
self.sharedMainViewModel.displayedConversation = conversation
|
||||||
self.sharedMainViewModel.displayedConversation = conversation
|
}
|
||||||
}
|
self.computeComposingLabel()
|
||||||
self.computeComposingLabel()
|
let historyEventsSizeTmp = newChatRoom.historyEventsSize
|
||||||
let historyEventsSizeTmp = newChatRoom.historyEventsSize
|
if self.displayedConversationHistorySize < historyEventsSizeTmp {
|
||||||
if self.displayedConversationHistorySize < historyEventsSizeTmp {
|
let eventLogList = newChatRoom.getHistoryRangeEvents(begin: 0, end: historyEventsSizeTmp - self.displayedConversationHistorySize)
|
||||||
let eventLogList = newChatRoom.getHistoryRangeEvents(begin: 0, end: historyEventsSizeTmp - self.displayedConversationHistorySize)
|
|
||||||
|
if !eventLogList.isEmpty {
|
||||||
if !eventLogList.isEmpty {
|
self.getNewMessages(eventLogs: eventLogList)
|
||||||
self.getNewMessages(eventLogs: eventLogList)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,21 @@ struct HelpFragment: View {
|
||||||
|
|
||||||
@FocusState var isVoicemailUriFocused: Bool
|
@FocusState var isVoicemailUriFocused: Bool
|
||||||
|
|
||||||
|
var showAssistant: Bool {
|
||||||
|
(CoreContext.shared.coreIsStarted && CoreContext.shared.accounts.isEmpty)
|
||||||
|
|| SharedMainViewModel.shared.displayProfileMode
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ZStack {
|
ZStack {
|
||||||
VStack(spacing: 1) {
|
VStack(spacing: 1) {
|
||||||
Rectangle()
|
if !showAssistant {
|
||||||
.foregroundColor(Color.orangeMain500)
|
Rectangle()
|
||||||
.edgesIgnoringSafeArea(.top)
|
.foregroundColor(Color.orangeMain500)
|
||||||
.frame(height: 0)
|
.edgesIgnoringSafeArea(.top)
|
||||||
|
.frame(height: 0)
|
||||||
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Image("caret-left")
|
Image("caret-left")
|
||||||
|
|
@ -302,5 +309,7 @@ struct HelpFragment: View {
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
}
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
.navigationTitle("")
|
||||||
|
.navigationBarHidden(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,13 +100,9 @@ struct DialerBottomSheet: View {
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("1".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("1".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "1"
|
||||||
dialerField += "1"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "1"
|
startCallViewModel.searchField += "1"
|
||||||
}
|
}
|
||||||
|
|
@ -125,13 +121,9 @@ struct DialerBottomSheet: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("2".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("2".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "2"
|
||||||
dialerField += "2"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "2"
|
startCallViewModel.searchField += "2"
|
||||||
}
|
}
|
||||||
|
|
@ -150,13 +142,9 @@ struct DialerBottomSheet: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("3".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("3".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "3"
|
||||||
dialerField += "3"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "3"
|
startCallViewModel.searchField += "3"
|
||||||
}
|
}
|
||||||
|
|
@ -177,13 +165,9 @@ struct DialerBottomSheet: View {
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("4".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("4".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "4"
|
||||||
dialerField += "4"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "4"
|
startCallViewModel.searchField += "4"
|
||||||
}
|
}
|
||||||
|
|
@ -202,13 +186,9 @@ struct DialerBottomSheet: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("5".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("5".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "5"
|
||||||
dialerField += "5"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "5"
|
startCallViewModel.searchField += "5"
|
||||||
}
|
}
|
||||||
|
|
@ -227,13 +207,9 @@ struct DialerBottomSheet: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("6".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("6".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "6"
|
||||||
dialerField += "6"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "6"
|
startCallViewModel.searchField += "6"
|
||||||
}
|
}
|
||||||
|
|
@ -255,13 +231,9 @@ struct DialerBottomSheet: View {
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("7".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("7".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "7"
|
||||||
dialerField += "7"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "7"
|
startCallViewModel.searchField += "7"
|
||||||
}
|
}
|
||||||
|
|
@ -280,13 +252,9 @@ struct DialerBottomSheet: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("8".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("8".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "8"
|
||||||
dialerField += "8"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "8"
|
startCallViewModel.searchField += "8"
|
||||||
}
|
}
|
||||||
|
|
@ -305,13 +273,9 @@ struct DialerBottomSheet: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("9".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("9".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "9"
|
||||||
dialerField += "9"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "9"
|
startCallViewModel.searchField += "9"
|
||||||
}
|
}
|
||||||
|
|
@ -333,13 +297,9 @@ struct DialerBottomSheet: View {
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("*".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("*".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "*"
|
||||||
dialerField += "*"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "*"
|
startCallViewModel.searchField += "*"
|
||||||
}
|
}
|
||||||
|
|
@ -393,13 +353,9 @@ struct DialerBottomSheet: View {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Button {
|
Button {
|
||||||
do {
|
let digit = ("0".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("0".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "0"
|
||||||
dialerField += "0"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} label: {
|
} label: {
|
||||||
Text("0")
|
Text("0")
|
||||||
.foregroundStyle(currentCall != nil ? .white : Color.grayMain2c600)
|
.foregroundStyle(currentCall != nil ? .white : Color.grayMain2c600)
|
||||||
|
|
@ -416,13 +372,9 @@ struct DialerBottomSheet: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if currentCall != nil {
|
if currentCall != nil {
|
||||||
do {
|
let digit = ("#".cString(using: String.Encoding.utf8)?[0])!
|
||||||
let digit = ("#".cString(using: String.Encoding.utf8)?[0])!
|
self.sendDtmf(dtmf: digit)
|
||||||
try currentCall!.sendDtmf(dtmf: digit)
|
dialerField += "#"
|
||||||
dialerField += "#"
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startCallViewModel.searchField += "#"
|
startCallViewModel.searchField += "#"
|
||||||
}
|
}
|
||||||
|
|
@ -534,6 +486,21 @@ struct DialerBottomSheet: View {
|
||||||
orientation = newOrientation
|
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 {
|
#Preview {
|
||||||
|
|
|
||||||
|
|
@ -168,21 +168,21 @@ struct AddParticipantsFragment: View {
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
ForEach(0..<contactsManager.lastSearch.count, id: \.self) { index in
|
ForEach(0..<contactsManager.avatarListModel.count, id: \.self) { index in
|
||||||
HStack {
|
HStack {
|
||||||
HStack {
|
HStack {
|
||||||
if index == 0
|
if index == 0
|
||||||
|| contactsManager.lastSearch[index].friend?.name!.lowercased().folding(
|
|| contactsManager.avatarListModel[index].name.lowercased().folding(
|
||||||
options: .diacriticInsensitive,
|
options: .diacriticInsensitive,
|
||||||
locale: .current
|
locale: .current
|
||||||
).first
|
).first
|
||||||
!= contactsManager.lastSearch[index-1].friend?.name!.lowercased().folding(
|
!= contactsManager.avatarListModel[index-1].name.lowercased().folding(
|
||||||
options: .diacriticInsensitive,
|
options: .diacriticInsensitive,
|
||||||
locale: .current
|
locale: .current
|
||||||
).first {
|
).first {
|
||||||
Text(
|
Text(
|
||||||
String(
|
String(
|
||||||
(contactsManager.lastSearch[index].friend?.name!.uppercased().folding(
|
(contactsManager.avatarListModel[index].name.uppercased().folding(
|
||||||
options: .diacriticInsensitive,
|
options: .diacriticInsensitive,
|
||||||
locale: .current
|
locale: .current
|
||||||
).first)!))
|
).first)!))
|
||||||
|
|
@ -198,40 +198,28 @@ struct AddParticipantsFragment: View {
|
||||||
.padding(.trailing, 5)
|
.padding(.trailing, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
if index < contactsManager.avatarListModel.count,
|
Avatar(contactAvatarModel: contactsManager.avatarListModel[index], avatarSize: 50)
|
||||||
let friend = contactsManager.avatarListModel[index].friend,
|
|
||||||
let photo = friend.photo,
|
|
||||||
!photo.isEmpty {
|
|
||||||
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 ?? "")!)
|
Text(contactsManager.avatarListModel[index].name)
|
||||||
.default_text_style(styleSize: 16)
|
.default_text_style(styleSize: 16)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.foregroundStyle(Color.orangeMain500)
|
.foregroundStyle(Color.orangeMain500)
|
||||||
|
|
||||||
if let searchAddress = contactsManager.lastSearch[index].friend?.address?.asStringUriOnly() {
|
if addParticipantsViewModel.participantsToAdd.contains(where: {
|
||||||
if addParticipantsViewModel.participantsToAdd.contains(where: {
|
$0.address.asStringUriOnly() == contactsManager.avatarListModel[index].address
|
||||||
$0.address.asStringUriOnly() == searchAddress
|
}) {
|
||||||
}) {
|
Image("check")
|
||||||
Image("check")
|
.renderingMode(.template)
|
||||||
.renderingMode(.template)
|
.resizable()
|
||||||
.resizable()
|
.foregroundStyle(Color.orangeMain500)
|
||||||
.foregroundStyle(Color.orangeMain500)
|
.frame(width: 25, height: 25)
|
||||||
.frame(width: 25, height: 25)
|
.padding(.horizontal)
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(.white)
|
.background(.white)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if let addr = contactsManager.lastSearch[index].address {
|
if let addr = try? Factory.Instance.createAddress(addr: contactsManager.avatarListModel[index].address) {
|
||||||
addParticipantsViewModel.selectParticipant(addr: addr)
|
addParticipantsViewModel.selectParticipant(addr: addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,18 @@ class MeetingViewModel: ObservableObject {
|
||||||
chatRoomParams.backend = ChatRoom.Backend.FlexisipChat
|
chatRoomParams.backend = ChatRoom.Backend.FlexisipChat
|
||||||
chatRoomParams.encryptionEnabled = true
|
chatRoomParams.encryptionEnabled = true
|
||||||
chatRoomParams.subject = "Meeting ics"
|
chatRoomParams.subject = "Meeting ics"
|
||||||
self.conferenceScheduler?.sendInvitations(chatRoomParams: chatRoomParams)
|
|
||||||
|
if self.conferenceScheduler == nil {
|
||||||
|
Log.info("\(MeetingViewModel.TAG) ConferenceScheduler is nil, resetting...")
|
||||||
|
self.resetConferenceSchedulerAndListeners(core: core)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let scheduler = self.conferenceScheduler else {
|
||||||
|
Log.error("\(MeetingViewModel.TAG) ConferenceScheduler still nil after reset")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler.sendInvitations(chatRoomParams: chatRoomParams)
|
||||||
} else {
|
} else {
|
||||||
Log.error("\(MeetingViewModel.TAG) Failed to create default chatroom parameters. This should not happen")
|
Log.error("\(MeetingViewModel.TAG) Failed to create default chatroom parameters. This should not happen")
|
||||||
}
|
}
|
||||||
|
|
@ -178,8 +189,12 @@ class MeetingViewModel: ObservableObject {
|
||||||
|
|
||||||
private func resetConferenceSchedulerAndListeners(core: Core) {
|
private func resetConferenceSchedulerAndListeners(core: Core) {
|
||||||
self.mSchedulerDelegate = nil
|
self.mSchedulerDelegate = nil
|
||||||
self.conferenceScheduler = try? core.createConferenceScheduler()
|
self.conferenceScheduler = LinphoneUtils.createConferenceScheduler(core: core)
|
||||||
|
|
||||||
|
guard let scheduler = self.conferenceScheduler else {
|
||||||
|
Log.info("\(MeetingViewModel.TAG) ConferenceScheduler is nil after reset, nothing to cancel")
|
||||||
|
return
|
||||||
|
}
|
||||||
self.mSchedulerDelegate = ConferenceSchedulerDelegateStub(onStateChanged: { (_: ConferenceScheduler, state: ConferenceScheduler.State) in
|
self.mSchedulerDelegate = ConferenceSchedulerDelegateStub(onStateChanged: { (_: ConferenceScheduler, state: ConferenceScheduler.State) in
|
||||||
Log.info("\(MeetingViewModel.TAG) Conference state changed \(state)")
|
Log.info("\(MeetingViewModel.TAG) Conference state changed \(state)")
|
||||||
if state == ConferenceScheduler.State.Error {
|
if state == ConferenceScheduler.State.Error {
|
||||||
|
|
@ -190,8 +205,10 @@ class MeetingViewModel: ObservableObject {
|
||||||
ToastViewModel.shared.displayToast = true
|
ToastViewModel.shared.displayToast = true
|
||||||
}
|
}
|
||||||
} else if state == ConferenceScheduler.State.Ready {
|
} else if state == ConferenceScheduler.State.Ready {
|
||||||
let conferenceAddress = self.conferenceScheduler?.info?.uri
|
if let confInfo = scheduler.info, let conferenceAddress = confInfo.uri {
|
||||||
Log.info("\(MeetingViewModel.TAG) Conference info created, address will be \(conferenceAddress?.asStringUriOnly() ?? "'nil'")")
|
Log.info("\(MeetingViewModel.TAG) Conference info created, address will be \(conferenceAddress.asStringUriOnly())")
|
||||||
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
ToastViewModel.shared.toastMessage = "Success_meeting_info_created_toast"
|
ToastViewModel.shared.toastMessage = "Success_meeting_info_created_toast"
|
||||||
ToastViewModel.shared.displayToast = true
|
ToastViewModel.shared.displayToast = true
|
||||||
|
|
@ -249,7 +266,7 @@ class MeetingViewModel: ObservableObject {
|
||||||
self.conferenceCreatedEvent = true
|
self.conferenceCreatedEvent = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.conferenceScheduler?.addDelegate(delegate: self.mSchedulerDelegate!)
|
scheduler.addDelegate(delegate: self.mSchedulerDelegate!)
|
||||||
}
|
}
|
||||||
|
|
||||||
func schedule() {
|
func schedule() {
|
||||||
|
|
@ -263,41 +280,69 @@ class MeetingViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
guard CoreContext.shared.networkStatusIsConnected else {
|
guard CoreContext.shared.networkStatusIsConnected else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
ToastViewModel.shared.toastMessage = "Unavailable_network"
|
ToastViewModel.shared.toastMessage = "Unavailable_network"
|
||||||
ToastViewModel.shared.displayToast = true
|
ToastViewModel.shared.displayToast = true
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
operationInProgress = true
|
operationInProgress = true
|
||||||
CoreContext.shared.doOnCoreQueue { core in
|
CoreContext.shared.doOnCoreQueue { core in
|
||||||
Log.info("\(MeetingViewModel.TAG) Scheduling \(self.isBroadcastSelected ? "broadcast" : "meeting")")
|
Log.info("\(MeetingViewModel.TAG) Scheduling \(self.isBroadcastSelected ? "broadcast" : "meeting")")
|
||||||
|
|
||||||
if let conferenceInfo = (SharedMainViewModel.shared.displayedMeeting != nil ? SharedMainViewModel.shared.displayedMeeting!.confInfo : try? Factory.Instance.createConferenceInfo()) {
|
let conferenceInfo: ConferenceInfo?
|
||||||
let localAccount = core.defaultAccount
|
if let displayedMeeting = SharedMainViewModel.shared.displayedMeeting {
|
||||||
conferenceInfo.organizer = localAccount?.params?.identityAddress
|
conferenceInfo = displayedMeeting.confInfo
|
||||||
|
} else {
|
||||||
// Allows to have a chat room within the conference
|
conferenceInfo = try? Factory.Instance.createConferenceInfo()
|
||||||
conferenceInfo.setCapability(streamType: StreamType.Text, enable: true)
|
|
||||||
|
|
||||||
// Enable end-to-end encryption if client supports it
|
|
||||||
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
|
|
||||||
Log.info("\(MeetingViewModel.TAG) Requesting EndToEnd security level for conference")
|
|
||||||
conferenceInfo.securityLevel = Conference.SecurityLevel.EndToEnd
|
|
||||||
} else {
|
|
||||||
Log.info("\(MeetingViewModel.TAG) Requesting PointToPoint security level for conference")
|
|
||||||
conferenceInfo.securityLevel = Conference.SecurityLevel.PointToPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fillConferenceInfo(confInfo: conferenceInfo)
|
|
||||||
self.resetConferenceSchedulerAndListeners(core: core)
|
|
||||||
self.conferenceScheduler?.account = localAccount
|
|
||||||
// Will trigger the conference creation automatically
|
|
||||||
self.conferenceScheduler?.info = conferenceInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let confInfo = conferenceInfo else {
|
||||||
|
Log.error("\(MeetingViewModel.TAG) Failed to create conference info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let localAccount = core.defaultAccount else {
|
||||||
|
Log.error("\(MeetingViewModel.TAG) Default account is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let organizer = localAccount.params?.identityAddress else {
|
||||||
|
Log.error("\(MeetingViewModel.TAG) Account params or identityAddress is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
confInfo.organizer = organizer
|
||||||
|
|
||||||
|
confInfo.setCapability(streamType: .Text, enable: true)
|
||||||
|
|
||||||
|
// Enable end-to-end encryption if client supports it
|
||||||
|
//if isEndToEndEncryptedChatAvailable(core: core) {
|
||||||
|
if false {
|
||||||
|
Log.info("\(MeetingViewModel.TAG) Requesting EndToEnd security level for conference")
|
||||||
|
confInfo.securityLevel = .EndToEnd
|
||||||
|
} else {
|
||||||
|
Log.info("\(MeetingViewModel.TAG) Requesting PointToPoint security level for conference")
|
||||||
|
confInfo.securityLevel = .PointToPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.conferenceScheduler == nil {
|
||||||
|
Log.info("\(MeetingViewModel.TAG) ConferenceScheduler is nil, resetting...")
|
||||||
|
self.resetConferenceSchedulerAndListeners(core: core)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let scheduler = self.conferenceScheduler else {
|
||||||
|
Log.error("\(MeetingViewModel.TAG) ConferenceScheduler still nil after reset")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fillConferenceInfo(confInfo: confInfo)
|
||||||
|
scheduler.account = localAccount
|
||||||
|
scheduler.info = confInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Warning: must be called from core queue. Removed the dispatchQueue.main.async in order to have the animation properly trigger.
|
// Warning: must be called from core queue. Removed the dispatchQueue.main.async in order to have the animation properly trigger.
|
||||||
func loadExistingMeeting(meeting: MeetingModel) {
|
func loadExistingMeeting(meeting: MeetingModel) {
|
||||||
|
|
@ -352,7 +397,13 @@ class MeetingViewModel: ObservableObject {
|
||||||
func cancelMeetingWithNotifications(meeting: MeetingModel) {
|
func cancelMeetingWithNotifications(meeting: MeetingModel) {
|
||||||
CoreContext.shared.doOnCoreQueue { core in
|
CoreContext.shared.doOnCoreQueue { core in
|
||||||
self.resetConferenceSchedulerAndListeners(core: core)
|
self.resetConferenceSchedulerAndListeners(core: core)
|
||||||
self.conferenceScheduler?.cancelConference(conferenceInfo: meeting.confInfo)
|
|
||||||
|
guard let scheduler = self.conferenceScheduler else {
|
||||||
|
Log.info("\(MeetingViewModel.TAG) ConferenceScheduler is nil after reset, nothing to cancel")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler.cancelConference(conferenceInfo: meeting.confInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,17 +133,16 @@ class MeetingsListViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let index = self.meetingsList.firstIndex(where: { $0.model?.address == meetingToDelete.address }) {
|
if let index = self.meetingsList.firstIndex(where: { $0.model?.address == meetingToDelete.address }) {
|
||||||
if self.todayIdx > index {
|
|
||||||
// bump todayIdx one place up
|
|
||||||
self.todayIdx -= 1
|
|
||||||
}
|
|
||||||
self.meetingsList.remove(at: index)
|
|
||||||
if self.meetingsList.count == 1 && self.meetingsList[0].model == nil {
|
|
||||||
// Only remaining meeting is the fake TodayMeeting, remove it too
|
|
||||||
meetingsList.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
if self.todayIdx > index {
|
||||||
|
// bump todayIdx one place up
|
||||||
|
self.todayIdx -= 1
|
||||||
|
}
|
||||||
|
self.meetingsList.remove(at: index)
|
||||||
|
if self.meetingsList.count == 1 && self.meetingsList[0].model == nil {
|
||||||
|
// Only remaining meeting is the fake TodayMeeting, remove it too
|
||||||
|
self.meetingsList.removeAll()
|
||||||
|
}
|
||||||
ToastViewModel.shared.toastMessage = "Success_toast_meeting_deleted"
|
ToastViewModel.shared.toastMessage = "Success_toast_meeting_deleted"
|
||||||
ToastViewModel.shared.displayToast = true
|
ToastViewModel.shared.displayToast = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,8 @@ struct SettingsFragment: View {
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
// TODO: Wait for VFS fix
|
||||||
|
/*
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Text("settings_security_title")
|
Text("settings_security_title")
|
||||||
.default_text_style_800(styleSize: 18)
|
.default_text_style_800(styleSize: 18)
|
||||||
|
|
@ -134,6 +136,7 @@ struct SettingsFragment: View {
|
||||||
.zIndex(-1)
|
.zIndex(-1)
|
||||||
.transition(.move(edge: .top))
|
.transition(.move(edge: .top))
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Text("settings_calls_title")
|
Text("settings_calls_title")
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,8 @@ class AccountProfileViewModel: ObservableObject {
|
||||||
|
|
||||||
static let TAG = "[AccountProfileViewModel]"
|
static let TAG = "[AccountProfileViewModel]"
|
||||||
|
|
||||||
@Published var dialPlanValueSelected: String = "🇫🇷 France | +33"
|
@Published var dialPlanValueSelected: String = ""
|
||||||
var dialPlanSelected: DialPlan?
|
var dialPlanSelected: DialPlan?
|
||||||
var dialPlansList: [DialPlan] = []
|
|
||||||
|
|
||||||
@Published var accountModelIndex: Int? = 0
|
@Published var accountModelIndex: Int? = 0
|
||||||
@Published var defaultAccountModelIndex: Int? = 0
|
@Published var defaultAccountModelIndex: Int? = 0
|
||||||
|
|
@ -52,13 +51,11 @@ class AccountProfileViewModel: ObservableObject {
|
||||||
if self.getImagePath().lastPathComponent.contains("-default") || self.getImagePath().lastPathComponent == "Documents" {
|
if self.getImagePath().lastPathComponent.contains("-default") || self.getImagePath().lastPathComponent == "Documents" {
|
||||||
let usernameTmp = CoreContext.shared.accounts[self.accountModelIndex!].usernaneAvatar
|
let usernameTmp = CoreContext.shared.accounts[self.accountModelIndex!].usernaneAvatar
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
self.saveImage(
|
||||||
self.saveImage(
|
image: ContactsManager.shared.textToImage(
|
||||||
image: ContactsManager.shared.textToImage(
|
firstName: displayNameAccountModel.isEmpty ? usernameTmp : displayNameAccountModel, lastName: ""),
|
||||||
firstName: displayNameAccountModel.isEmpty ? usernameTmp : displayNameAccountModel, lastName: ""),
|
name: usernameTmp,
|
||||||
name: usernameTmp,
|
prefix: "-default")
|
||||||
prefix: "-default")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,6 +64,10 @@ class AccountProfileViewModel: ObservableObject {
|
||||||
newParams?.internationalPrefix = self.dialPlanSelected?.countryCallingCode
|
newParams?.internationalPrefix = self.dialPlanSelected?.countryCallingCode
|
||||||
newParams?.internationalPrefixIsoCountryCode = self.dialPlanSelected?.isoCountryCode
|
newParams?.internationalPrefixIsoCountryCode = self.dialPlanSelected?.isoCountryCode
|
||||||
newParams?.useInternationalPrefixForCallsAndChats = true
|
newParams?.useInternationalPrefixForCallsAndChats = true
|
||||||
|
} else if newParams?.useInternationalPrefixForCallsAndChats == true {
|
||||||
|
newParams?.internationalPrefix = nil
|
||||||
|
newParams?.internationalPrefixIsoCountryCode = nil
|
||||||
|
newParams?.useInternationalPrefixForCallsAndChats = false
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreContext.shared.accounts[self.accountModelIndex!].account.params = newParams
|
CoreContext.shared.accounts[self.accountModelIndex!].account.params = newParams
|
||||||
|
|
@ -86,16 +87,20 @@ class AccountProfileViewModel: ObservableObject {
|
||||||
|
|
||||||
var dialPlanValueSelectedTmp = ""
|
var dialPlanValueSelectedTmp = ""
|
||||||
if !prefix.isEmpty || !isoCountryCode.isEmpty {
|
if !prefix.isEmpty || !isoCountryCode.isEmpty {
|
||||||
Log.info(
|
Log.info(
|
||||||
"\(AccountProfileViewModel.TAG) Account \(accountTmp.account.params?.identityAddress?.asStringUriOnly() ?? "") prefix is \(prefix) \(isoCountryCode)"
|
"\(AccountProfileViewModel.TAG) Account \(accountTmp.account.params?.identityAddress?.asStringUriOnly() ?? "") prefix is \(prefix) \(isoCountryCode)"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.dialPlansList = Factory.Instance.dialPlans
|
let dialPlansList = SharedMainViewModel.shared.dialPlansList
|
||||||
if let dialPlan = self.dialPlansList.first(where: { $0.isoCountryCode == isoCountryCode }) ??
|
if let dialPlan = dialPlansList.first(where: { $0?.isoCountryCode == isoCountryCode }) ??
|
||||||
self.dialPlansList.first(where: { $0.countryCallingCode == prefix }) {
|
dialPlansList.first(where: { $0?.countryCallingCode == prefix }) {
|
||||||
dialPlanValueSelectedTmp = "\(dialPlan.flag) \(dialPlan.country) | +\(dialPlan.countryCallingCode)"
|
dialPlanValueSelectedTmp = "\(dialPlan?.flag ?? "") \(dialPlan?.country ?? "") | +\(dialPlan?.countryCallingCode ?? "")"
|
||||||
}
|
} else {
|
||||||
}
|
dialPlanValueSelectedTmp = "No country code"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dialPlanValueSelectedTmp = "No country code"
|
||||||
|
}
|
||||||
|
|
||||||
let accountDisplayName = accountTmp.account.displayName()
|
let accountDisplayName = accountTmp.account.displayName()
|
||||||
|
|
||||||
|
|
@ -118,8 +123,9 @@ class AccountProfileViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateDialPlan(newDialPlan: String) {
|
func updateDialPlan(newDialPlan: String) {
|
||||||
if let dialPlan = self.dialPlansList.first(where: { newDialPlan.contains($0.isoCountryCode) }) ??
|
let dialPlansList = SharedMainViewModel.shared.dialPlansList
|
||||||
self.dialPlansList.first(where: { newDialPlan.contains($0.countryCallingCode) }) {
|
if let dialPlan = dialPlansList.first(where: { newDialPlan.contains($0?.isoCountryCode ?? "") }) ??
|
||||||
|
dialPlansList.first(where: { newDialPlan.contains($0?.countryCallingCode ?? "") }) {
|
||||||
self.dialPlanSelected = dialPlan
|
self.dialPlanSelected = dialPlan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,12 +137,14 @@ class AccountProfileViewModel: ObservableObject {
|
||||||
|
|
||||||
let photoAvatarModelKey = CoreContext.shared.accounts[self.accountModelIndex!].usernaneAvatar
|
let photoAvatarModelKey = CoreContext.shared.accounts[self.accountModelIndex!].usernaneAvatar
|
||||||
|
|
||||||
ContactsManager.shared.awaitDataWrite(data: data, name: name, prefix: prefix) { _, result in
|
ContactsManager.shared.awaitDataWrite(data: data, name: name, prefix: prefix) { result in
|
||||||
UserDefaults.standard.set(result, forKey: photoAvatarModelKey)
|
UserDefaults.standard.set(result, forKey: photoAvatarModelKey)
|
||||||
|
|
||||||
CoreContext.shared.accounts[self.accountModelIndex ?? 0].photoAvatarModel = ""
|
DispatchQueue.main.async {
|
||||||
CoreContext.shared.accounts[self.accountModelIndex ?? 0].imagePathAvatar = nil
|
CoreContext.shared.accounts[self.accountModelIndex ?? 0].photoAvatarModel = ""
|
||||||
NotificationCenter.default.post(name: NSNotification.Name("ImageChanged"), object: nil)
|
CoreContext.shared.accounts[self.accountModelIndex ?? 0].imagePathAvatar = nil
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name("ImageChanged"), object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
CoreContext.shared.accounts[self.accountModelIndex ?? 0].photoAvatarModel = result
|
CoreContext.shared.accounts[self.accountModelIndex ?? 0].photoAvatarModel = result
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,9 @@ class AccountSettingsViewModel: ObservableObject {
|
||||||
newParams.limeServerUrl = self.limeServerUrl
|
newParams.limeServerUrl = self.limeServerUrl
|
||||||
|
|
||||||
self.accountModel.account.params = newParams
|
self.accountModel.account.params = newParams
|
||||||
|
|
||||||
|
SharedMainViewModel.shared.updateDisableMeetingFeature()
|
||||||
|
|
||||||
print("\(AccountSettingsViewModel.TAG) Changes have been saved")
|
print("\(AccountSettingsViewModel.TAG) Changes have been saved")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,6 @@ class AccountModel: ObservableObject {
|
||||||
let displayName = account.displayName()
|
let displayName = account.displayName()
|
||||||
let address = account.params?.identityAddress?.asString()
|
let address = account.params?.identityAddress?.asString()
|
||||||
|
|
||||||
self.requestDevicesList()
|
|
||||||
|
|
||||||
let displayNameTmp = account.params?.identityAddress?.displayName ?? displayName
|
let displayNameTmp = account.params?.identityAddress?.displayName ?? displayName
|
||||||
let usernaneAvatarTmp = account.contactAddress?.username ?? displayName
|
let usernaneAvatarTmp = account.contactAddress?.username ?? displayName
|
||||||
var photoAvatarModelTmp = ""
|
var photoAvatarModelTmp = ""
|
||||||
|
|
@ -105,13 +103,11 @@ class AccountModel: ObservableObject {
|
||||||
|
|
||||||
if !photoAvatarModelKey.isEmpty {
|
if !photoAvatarModelKey.isEmpty {
|
||||||
if preferences.object(forKey: photoAvatarModelKey) == nil {
|
if preferences.object(forKey: photoAvatarModelKey) == nil {
|
||||||
DispatchQueue.main.async {
|
self.saveImage(
|
||||||
self.saveImage(
|
image: ContactsManager.shared.textToImage(
|
||||||
image: ContactsManager.shared.textToImage(
|
firstName: usernaneAvatarTmp, lastName: ""),
|
||||||
firstName: usernaneAvatarTmp, lastName: ""),
|
name: usernaneAvatarTmp,
|
||||||
name: usernaneAvatarTmp,
|
prefix: "-default")
|
||||||
prefix: "-default")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
photoAvatarModelTmp = preferences.string(forKey: photoAvatarModelKey)!
|
photoAvatarModelTmp = preferences.string(forKey: photoAvatarModelKey)!
|
||||||
}
|
}
|
||||||
|
|
@ -156,11 +152,14 @@ class AccountModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func computeNotificationsCount() {
|
private func computeNotificationsCount() {
|
||||||
let count = account.unreadChatMessageCount + account.missedCallsCount
|
CoreContext.shared.doOnCoreQueue { core in
|
||||||
SharedMainViewModel.shared.updateMissedCallsCount()
|
let count = self.account.unreadChatMessageCount + self.account.missedCallsCount
|
||||||
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
SharedMainViewModel.shared.updateMissedCallsCount()
|
||||||
DispatchQueue.main.async { [self] in
|
SharedMainViewModel.shared.updateUnreadMessagesCount()
|
||||||
notificationsCount = count
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.notificationsCount = count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,7 +270,7 @@ class AccountModel: ObservableObject {
|
||||||
|
|
||||||
let photoAvatarModelKey = name
|
let photoAvatarModelKey = name
|
||||||
|
|
||||||
ContactsManager.shared.awaitDataWrite(data: data, name: name, prefix: prefix) { _, result in
|
ContactsManager.shared.awaitDataWrite(data: data, name: name, prefix: prefix) { result in
|
||||||
UserDefaults.standard.set(result, forKey: photoAvatarModelKey)
|
UserDefaults.standard.set(result, forKey: photoAvatarModelKey)
|
||||||
|
|
||||||
self.photoAvatarModel = ""
|
self.photoAvatarModel = ""
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class SharedMainViewModel: ObservableObject {
|
||||||
@Published var displayedConversation: ConversationModel?
|
@Published var displayedConversation: ConversationModel?
|
||||||
@Published var displayedMeeting: MeetingModel?
|
@Published var displayedMeeting: MeetingModel?
|
||||||
|
|
||||||
@Published var dialPlansList: [DialPlan] = []
|
@Published var dialPlansList: [DialPlan?] = []
|
||||||
@Published var dialPlansLabelList: [String] = []
|
@Published var dialPlansLabelList: [String] = []
|
||||||
@Published var dialPlansShortLabelList: [String] = []
|
@Published var dialPlansShortLabelList: [String] = []
|
||||||
|
|
||||||
|
|
@ -47,6 +47,7 @@ class SharedMainViewModel: ObservableObject {
|
||||||
@Published var missedCallsCount: Int = 0
|
@Published var missedCallsCount: Int = 0
|
||||||
|
|
||||||
@Published var disableChatFeature: Bool = false
|
@Published var disableChatFeature: Bool = false
|
||||||
|
@Published var disableMeetingFeature: Bool = false
|
||||||
|
|
||||||
let welcomeViewKey = "welcome_view"
|
let welcomeViewKey = "welcome_view"
|
||||||
let generalTermsKey = "general_terms"
|
let generalTermsKey = "general_terms"
|
||||||
|
|
@ -94,6 +95,7 @@ class SharedMainViewModel: ObservableObject {
|
||||||
updateMissedCallsCount()
|
updateMissedCallsCount()
|
||||||
updateUnreadMessagesCount()
|
updateUnreadMessagesCount()
|
||||||
updateDisableChatFeature()
|
updateDisableChatFeature()
|
||||||
|
updateDisableMeetingFeature()
|
||||||
}
|
}
|
||||||
|
|
||||||
func changeWelcomeView() {
|
func changeWelcomeView() {
|
||||||
|
|
@ -141,10 +143,14 @@ class SharedMainViewModel: ObservableObject {
|
||||||
func getDialPlansList() {
|
func getDialPlansList() {
|
||||||
CoreContext.shared.doOnCoreQueue { _ in
|
CoreContext.shared.doOnCoreQueue { _ in
|
||||||
let dialPlans = Factory.Instance.dialPlans
|
let dialPlans = Factory.Instance.dialPlans
|
||||||
var dialPlansListTmp: [DialPlan] = []
|
var dialPlansListTmp: [DialPlan?] = []
|
||||||
var dialPlansLabelListTmp: [String] = []
|
var dialPlansLabelListTmp: [String] = []
|
||||||
var dialPlansShortLabelListTmp: [String] = []
|
var dialPlansShortLabelListTmp: [String] = []
|
||||||
|
|
||||||
|
dialPlansListTmp.append(nil)
|
||||||
|
dialPlansLabelListTmp.append("No country code")
|
||||||
|
dialPlansShortLabelListTmp.append("---")
|
||||||
|
|
||||||
dialPlans.forEach { dialPlan in
|
dialPlans.forEach { dialPlan in
|
||||||
dialPlansListTmp.append(dialPlan)
|
dialPlansListTmp.append(dialPlan)
|
||||||
dialPlansLabelListTmp.append(
|
dialPlansLabelListTmp.append(
|
||||||
|
|
@ -224,4 +230,14 @@ class SharedMainViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateDisableMeetingFeature() {
|
||||||
|
CoreContext.shared.doOnCoreQueue { core in
|
||||||
|
let disableMeetingFeatureTmp = CorePreferences.disableMeetings ||
|
||||||
|
!LinphoneUtils.isRemoteConferencingAvailable(core: core)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.disableMeetingFeature = disableMeetingFeatureTmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,38 +26,36 @@ struct EditContactView: UIViewControllerRepresentable {
|
||||||
class Coordinator: NSObject, CNContactViewControllerDelegate, UINavigationControllerDelegate {
|
class Coordinator: NSObject, CNContactViewControllerDelegate, UINavigationControllerDelegate {
|
||||||
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
|
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
|
||||||
if let cnc = contact {
|
if let cnc = contact {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
self.parent.contact = cnc
|
||||||
self.parent.contact = cnc
|
|
||||||
|
let newContact = Contact(
|
||||||
let newContact = Contact(
|
identifier: cnc.identifier,
|
||||||
identifier: cnc.identifier,
|
firstName: cnc.givenName,
|
||||||
firstName: cnc.givenName,
|
lastName: cnc.familyName,
|
||||||
lastName: cnc.familyName,
|
organizationName: cnc.organizationName,
|
||||||
organizationName: cnc.organizationName,
|
jobTitle: "",
|
||||||
jobTitle: "",
|
displayName: cnc.nickname,
|
||||||
displayName: cnc.nickname,
|
sipAddresses: cnc.instantMessageAddresses.map { $0.value.service == "SIP" ? $0.value.username : "" },
|
||||||
sipAddresses: cnc.instantMessageAddresses.map { $0.value.service == "SIP" ? $0.value.username : "" },
|
phoneNumbers: cnc.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)},
|
||||||
phoneNumbers: cnc.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)},
|
imageData: ""
|
||||||
imageData: ""
|
)
|
||||||
)
|
|
||||||
|
let imageThumbnail = UIImage(data: contact!.thumbnailImageData ?? Data())
|
||||||
let imageThumbnail = UIImage(data: contact!.thumbnailImageData ?? Data())
|
ContactsManager.shared.saveImage(
|
||||||
ContactsManager.shared.saveImage(
|
image: imageThumbnail
|
||||||
image: imageThumbnail
|
?? ContactsManager.shared.textToImage(
|
||||||
?? ContactsManager.shared.textToImage(
|
firstName: cnc.givenName.isEmpty
|
||||||
firstName: cnc.givenName.isEmpty
|
&& cnc.familyName.isEmpty
|
||||||
&& cnc.familyName.isEmpty
|
&& cnc.phoneNumbers.first?.value.stringValue != nil
|
||||||
&& cnc.phoneNumbers.first?.value.stringValue != nil
|
? cnc.phoneNumbers.first!.value.stringValue
|
||||||
? cnc.phoneNumbers.first!.value.stringValue
|
: cnc.givenName, lastName: cnc.familyName),
|
||||||
: cnc.givenName, lastName: cnc.familyName),
|
name: cnc.givenName + cnc.familyName,
|
||||||
name: cnc.givenName + cnc.familyName,
|
prefix: ((imageThumbnail == nil) ? "-default" : ""),
|
||||||
prefix: ((imageThumbnail == nil) ? "-default" : ""),
|
contact: newContact,
|
||||||
contact: newContact,
|
linphoneFriend: "Native address-book",
|
||||||
linphoneFriend: "Native address-book",
|
existingFriend: ContactsManager.shared.getFriendWithContact(contact: newContact)) {
|
||||||
existingFriend: ContactsManager.shared.getFriendWithContact(contact: newContact)) {
|
MagicSearchSingleton.shared.searchForContacts()
|
||||||
MagicSearchSingleton.shared.searchForContacts()
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
viewController.dismiss(animated: true, completion: {})
|
viewController.dismiss(animated: true, completion: {})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
Linphone/Utils/KeyboardResponder.swift
Normal file
24
Linphone/Utils/KeyboardResponder.swift
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
final class KeyboardResponder: ObservableObject {
|
||||||
|
@Published var currentHeight: CGFloat = 0
|
||||||
|
|
||||||
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let willShow = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
|
||||||
|
.map { notification -> CGFloat in
|
||||||
|
(notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let willHide = NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
|
||||||
|
.map { _ in CGFloat(0) }
|
||||||
|
|
||||||
|
Publishers.Merge(willShow, willHide)
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.assign(to: \.currentHeight, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,6 +72,32 @@ class LinphoneUtils: NSObject {
|
||||||
core.defaultAccount?.params?.conferenceFactoryUri != nil
|
core.defaultAccount?.params?.conferenceFactoryUri != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class func createConferenceScheduler(core: Core) -> ConferenceScheduler? {
|
||||||
|
let account = LinphoneUtils.getDefaultAccount()
|
||||||
|
if let url = account?.params?.ccmpServerUrl, !url.isEmpty {
|
||||||
|
Log.info(
|
||||||
|
"CCMP server URL has been set in Account's params, using CCMP conference scheduler"
|
||||||
|
)
|
||||||
|
|
||||||
|
let conferenceScheduler = try? core.createConferenceSchedulerWithType(
|
||||||
|
account: account,
|
||||||
|
schedulingType: .CCMP
|
||||||
|
)
|
||||||
|
|
||||||
|
return conferenceScheduler
|
||||||
|
}
|
||||||
|
Log.info(
|
||||||
|
"CCMP server URL hasn't been set in Account's params, using SIP conference scheduler"
|
||||||
|
)
|
||||||
|
|
||||||
|
let conferenceScheduler = try? core.createConferenceSchedulerWithType(
|
||||||
|
account: account,
|
||||||
|
schedulingType: .SIP
|
||||||
|
)
|
||||||
|
|
||||||
|
return conferenceScheduler
|
||||||
|
}
|
||||||
|
|
||||||
public class func createGroupCall(core: Core, account: Account?, subject: String) -> Conference? {
|
public class func createGroupCall(core: Core, account: Account?, subject: String) -> Conference? {
|
||||||
do {
|
do {
|
||||||
let conferenceParams = try core.createConferenceParams(conference: nil)
|
let conferenceParams = try core.createConferenceParams(conference: nil)
|
||||||
|
|
|
||||||
|
|
@ -94,11 +94,13 @@ final class MagicSearchSingleton: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sortedLastSearch = lastSearchFriend.sorted(by: {
|
let sortedLastSearch = lastSearchFriend.sorted {
|
||||||
$0.friend!.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current)
|
let name1 = $0.friend?.name?.lowercased()
|
||||||
<
|
.folding(options: .diacriticInsensitive, locale: .current) ?? ""
|
||||||
$1.friend!.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current)
|
let name2 = $1.friend?.name?.lowercased()
|
||||||
})
|
.folding(options: .diacriticInsensitive, locale: .current) ?? ""
|
||||||
|
return name1 < name2
|
||||||
|
}
|
||||||
|
|
||||||
var addedAvatarListModel: [ContactAvatarModel] = []
|
var addedAvatarListModel: [ContactAvatarModel] = []
|
||||||
sortedLastSearch.forEach { searchResult in
|
sortedLastSearch.forEach { searchResult in
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@
|
||||||
D73449992BC6932A00778C56 /* MeetingWaitingRoomFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73449982BC6932A00778C56 /* MeetingWaitingRoomFragment.swift */; };
|
D73449992BC6932A00778C56 /* MeetingWaitingRoomFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73449982BC6932A00778C56 /* MeetingWaitingRoomFragment.swift */; };
|
||||||
D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */; };
|
D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */; };
|
||||||
D737AEEF2DA011F2005C1280 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D737AEED2DA011F2005C1280 /* Localizable.strings */; };
|
D737AEEF2DA011F2005C1280 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D737AEED2DA011F2005C1280 /* Localizable.strings */; };
|
||||||
|
D738ACEE2E857BF10039F7D1 /* KeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D738ACED2E857BEF0039F7D1 /* KeyboardResponder.swift */; };
|
||||||
D7458F392E0BDCF4000C957A /* linphoneExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D7458F392E0BDCF4000C957A /* linphoneExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */; };
|
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */; };
|
||||||
D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */; };
|
D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */; };
|
||||||
|
|
@ -306,6 +307,7 @@
|
||||||
D719ABCB2ABC769C00B41C10 /* AssistantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssistantView.swift; sourceTree = "<group>"; };
|
D719ABCB2ABC769C00B41C10 /* AssistantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssistantView.swift; sourceTree = "<group>"; };
|
||||||
D719ABCE2ABC779A00B41C10 /* AccountLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLoginViewModel.swift; sourceTree = "<group>"; };
|
D719ABCE2ABC779A00B41C10 /* AccountLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLoginViewModel.swift; sourceTree = "<group>"; };
|
||||||
D71A0E182B485ADF0002C6CD /* ViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtension.swift; sourceTree = "<group>"; };
|
D71A0E182B485ADF0002C6CD /* ViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtension.swift; sourceTree = "<group>"; };
|
||||||
|
D71C266F2E819A0D001A7F92 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = Localizable/sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListFragment.swift; sourceTree = "<group>"; };
|
D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListFragment.swift; sourceTree = "<group>"; };
|
||||||
D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFragment.swift; sourceTree = "<group>"; };
|
D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFragment.swift; sourceTree = "<group>"; };
|
||||||
D720E6AC2BAD822000DDFD87 /* ParticipantModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantModel.swift; sourceTree = "<group>"; };
|
D720E6AC2BAD822000DDFD87 /* ParticipantModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantModel.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -333,6 +335,7 @@
|
||||||
D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingWaitingRoomViewModel.swift; sourceTree = "<group>"; };
|
D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingWaitingRoomViewModel.swift; sourceTree = "<group>"; };
|
||||||
D737AEEE2DA011F2005C1280 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable/en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
D737AEEE2DA011F2005C1280 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable/en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
D737AEF02DA01203005C1280 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Localizable/fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
D737AEF02DA01203005C1280 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Localizable/fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
D738ACED2E857BEF0039F7D1 /* KeyboardResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardResponder.swift; sourceTree = "<group>"; };
|
||||||
D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = linphoneExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = linphoneExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountLoginFragment.swift; sourceTree = "<group>"; };
|
D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountLoginFragment.swift; sourceTree = "<group>"; };
|
||||||
D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountWarningFragment.swift; sourceTree = "<group>"; };
|
D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountWarningFragment.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -582,6 +585,7 @@
|
||||||
D717071C2AC591EF0037746F /* Utils */ = {
|
D717071C2AC591EF0037746F /* Utils */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D738ACED2E857BEF0039F7D1 /* KeyboardResponder.swift */,
|
||||||
D7DF8BE82E2104E5003A3BC7 /* EmojiPickerView.swift */,
|
D7DF8BE82E2104E5003A3BC7 /* EmojiPickerView.swift */,
|
||||||
D703F7072DC8C5FF005B8F75 /* FilePicker.swift */,
|
D703F7072DC8C5FF005B8F75 /* FilePicker.swift */,
|
||||||
D717A10D2CEB770D00849D92 /* ShareSheetController.swift */,
|
D717A10D2CEB770D00849D92 /* ShareSheetController.swift */,
|
||||||
|
|
@ -1153,6 +1157,7 @@
|
||||||
de,
|
de,
|
||||||
ru,
|
ru,
|
||||||
"zh-Hans",
|
"zh-Hans",
|
||||||
|
sk,
|
||||||
);
|
);
|
||||||
mainGroup = D719ABAA2ABC67BF00B41C10;
|
mainGroup = D719ABAA2ABC67BF00B41C10;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
|
|
@ -1277,6 +1282,7 @@
|
||||||
D7DC096F2CFA1D7600A6D47C /* AccountProfileFragment.swift in Sources */,
|
D7DC096F2CFA1D7600A6D47C /* AccountProfileFragment.swift in Sources */,
|
||||||
D717A10E2CEB772300849D92 /* ShareSheetController.swift in Sources */,
|
D717A10E2CEB772300849D92 /* ShareSheetController.swift in Sources */,
|
||||||
66C491FD2B24D36500CEA16D /* AudioRouteUtils.swift in Sources */,
|
66C491FD2B24D36500CEA16D /* AudioRouteUtils.swift in Sources */,
|
||||||
|
D738ACEE2E857BF10039F7D1 /* KeyboardResponder.swift in Sources */,
|
||||||
D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */,
|
D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */,
|
||||||
C6DC4E3D2C199C4E009096FD /* BundleExtenion.swift in Sources */,
|
C6DC4E3D2C199C4E009096FD /* BundleExtenion.swift in Sources */,
|
||||||
D7343FEF2D3FE16C0059D784 /* HelpViewModel.swift in Sources */,
|
D7343FEF2D3FE16C0059D784 /* HelpViewModel.swift in Sources */,
|
||||||
|
|
@ -1445,6 +1451,7 @@
|
||||||
D7F372C22E65C5190008B863 /* de */,
|
D7F372C22E65C5190008B863 /* de */,
|
||||||
D7F372C32E65C51B0008B863 /* ru */,
|
D7F372C32E65C51B0008B863 /* ru */,
|
||||||
D7F372C42E65C51E0008B863 /* zh-Hans */,
|
D7F372C42E65C51E0008B863 /* zh-Hans */,
|
||||||
|
D71C266F2E819A0D001A7F92 /* sk */,
|
||||||
);
|
);
|
||||||
name = Localizable.strings;
|
name = Localizable.strings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1460,7 +1467,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 101;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = Z2V957B3D6;
|
DEVELOPMENT_TEAM = Z2V957B3D6;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
|
@ -1480,7 +1487,7 @@
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 6.0.0;
|
MARKETING_VERSION = 6.0.2;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
@ -1503,7 +1510,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 101;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = Z2V957B3D6;
|
DEVELOPMENT_TEAM = Z2V957B3D6;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
|
@ -1522,7 +1529,7 @@
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 6.0.0;
|
MARKETING_VERSION = 6.0.2;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
@ -1662,7 +1669,7 @@
|
||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||||
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 101;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";
|
||||||
|
|
@ -1701,7 +1708,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.3;
|
MACOSX_DEPLOYMENT_TARGET = 13.3;
|
||||||
MARKETING_VERSION = 6.0.0;
|
MARKETING_VERSION = 6.0.2;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
|
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
@ -1722,7 +1729,7 @@
|
||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||||
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 101;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = Z2V957B3D6;
|
DEVELOPMENT_TEAM = Z2V957B3D6;
|
||||||
|
|
@ -1759,7 +1766,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.3;
|
MACOSX_DEPLOYMENT_TARGET = 13.3;
|
||||||
MARKETING_VERSION = 6.0.0;
|
MARKETING_VERSION = 6.0.2;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
|
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
@ -1778,7 +1785,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = linphoneExtension/linphoneExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = linphoneExtension/linphoneExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 101;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = Z2V957B3D6;
|
DEVELOPMENT_TEAM = Z2V957B3D6;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
|
@ -1793,7 +1800,7 @@
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 6.0.0;
|
MARKETING_VERSION = 6.0.2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.linphoneExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.linphoneExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|
@ -1812,7 +1819,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = linphoneExtension/linphoneExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = linphoneExtension/linphoneExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 101;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = Z2V957B3D6;
|
DEVELOPMENT_TEAM = Z2V957B3D6;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
|
@ -1827,7 +1834,7 @@
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 6.0.0;
|
MARKETING_VERSION = 6.0.2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.linphoneExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.linphoneExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
"location" : "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git",
|
"location" : "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"branch" : "stable",
|
"branch" : "stable",
|
||||||
"revision" : "a4f6e33f8b44044e642d7a6c220af07cbc2b4e4f"
|
"revision" : "4d51a91278236d1a22d880b769397c94a2bb7b3e"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -150,8 +150,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "e3f69fd321d0c9fcdc16fb576a0cdd956675face",
|
"revision" : "2547102afd04fe49f1b286090f13ebce07284980",
|
||||||
"version" : "1.31.0"
|
"version" : "1.31.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ various ways:
|
||||||
|
|
||||||
## Help on translations
|
## Help on translations
|
||||||
|
|
||||||
We no longer use transifex for the translation process, instead we have deployed our own instance of [Weblate](https://weblate.linphone.org/projects/linphone-iphone/).
|
We no longer use transifex for the translation process, instead we have deployed our own instance of [Weblate](https://weblate.linphone.org/projects/linphone/).
|
||||||
|
|
||||||
Due to the full app rewrite we can't re-use previous translations, so we'll be very happy if you want to contribute.
|
Due to the full app rewrite we can't re-use previous translations, so we'll be very happy if you want to contribute.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue