diff --git a/Linphone/Assets.xcassets/lock-key.imageset/Contents.json b/Linphone/Assets.xcassets/lock-key.imageset/Contents.json
new file mode 100644
index 000000000..309129c73
--- /dev/null
+++ b/Linphone/Assets.xcassets/lock-key.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "lock-key.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Linphone/Assets.xcassets/lock-key.imageset/lock-key.svg b/Linphone/Assets.xcassets/lock-key.imageset/lock-key.svg
new file mode 100644
index 000000000..e033ef858
--- /dev/null
+++ b/Linphone/Assets.xcassets/lock-key.imageset/lock-key.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Linphone/Assets.xcassets/security.imageset/Contents.json b/Linphone/Assets.xcassets/security.imageset/Contents.json
new file mode 100644
index 000000000..ef6708f15
--- /dev/null
+++ b/Linphone/Assets.xcassets/security.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "security.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Linphone/Assets.xcassets/security.imageset/security.svg b/Linphone/Assets.xcassets/security.imageset/security.svg
new file mode 100644
index 000000000..00fa4a741
--- /dev/null
+++ b/Linphone/Assets.xcassets/security.imageset/security.svg
@@ -0,0 +1,6 @@
+
diff --git a/Linphone/Assets.xcassets/shield-warning.imageset/Contents.json b/Linphone/Assets.xcassets/shield-warning.imageset/Contents.json
new file mode 100644
index 000000000..84d820c30
--- /dev/null
+++ b/Linphone/Assets.xcassets/shield-warning.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "shield-warning.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Linphone/Assets.xcassets/shield-warning.imageset/shield-warning.svg b/Linphone/Assets.xcassets/shield-warning.imageset/shield-warning.svg
new file mode 100644
index 000000000..dae911b15
--- /dev/null
+++ b/Linphone/Assets.xcassets/shield-warning.imageset/shield-warning.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings
index c61bc684d..5730da51f 100644
--- a/Linphone/Localizable.xcstrings
+++ b/Linphone/Localizable.xcstrings
@@ -204,9 +204,6 @@
},
"Appel" : {
- },
- "Appel chiffré de bout en bout" : {
-
},
"assistant_account_create" : {
"localizations" : {
@@ -421,6 +418,278 @@
},
"Call transfer failed!" : {
+ },
+ "call_action_hang_up" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hang up"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Raccrocher"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_security_alert_message" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "This call confidentiality may be compromise!"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La confidentialité de votre appel peut être compromise !"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_security_alert_title" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Security alert"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Alerte de sécurité"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_security_alert_try_again" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Try again"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Réessayer"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_validate_trust_letters_do_not_match" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nothing matches"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aucune correspondance"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_validate_trust_local_code_label" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Your code:"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Votre code :"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_validate_trust_message" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "For your safety, we need to authenticate your correspondent device.
Please exchange your codes:"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pour garantir le chiffrement, nous avons besoin d’authentifier l’appareil de votre correspondant.
Veuillez échanger vos codes :"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_validate_trust_remote_code_label" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Correspondent code:"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Code correspondant :"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_validate_trust_title" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Validate the device"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vérification de sécurité"
+ }
+ }
+ }
+ },
+ "call_dialog_zrtp_validate_trust_warning_message" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "For your safety, we need to re-authenticate your correspondent device.
Please re-exchange your codes:"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pour garantir le chiffrement, nous avons besoin de réauthentifier l’appareil de votre correspondant.
Veuillez ré-échanger vos codes :"
+ }
+ }
+ }
+ },
+ "call_not_encrypted" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Call is not encrypted"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Appel non chiffré"
+ }
+ }
+ }
+ },
+ "call_srtp_point_to_point_encrypted" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Point-to-point encrypted by SRTP"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Appel chiffré de point à point"
+ }
+ }
+ }
+ },
+ "call_waiting_for_encryption_info" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Waiting for encryption…"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "En attente du chiffrement…"
+ }
+ }
+ }
+ },
+ "call_zrtp_end_to_end_encrypted" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "End-to-end encrypted by ZRTP"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Appel chiffré de bout en bout"
+ }
+ }
+ }
+ },
+ "call_zrtp_sas_validation_required" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Validation required"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "Vérification nécessaire"
+ }
+ }
+ }
+ },
+ "call_zrtp_sas_validation_skip" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Skip"
+ }
+ },
+ "fr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Passer"
+ }
+ }
+ }
},
"Calls" : {
@@ -889,21 +1158,9 @@
},
"Joining..." : {
- },
- "Key" : {
- "extractionState" : "manual"
- },
- "Key 1" : {
- "extractionState" : "manual"
- },
- "Key 2" : {
- "extractionState" : "manual"
},
"Last name" : {
- },
- "Letters don't match!" : {
-
},
"Linphone" : {
@@ -1101,9 +1358,6 @@
},
"Resuming" : {
- },
- "Say %@ and click on the letters given by your correspondent:" : {
-
},
"Say something..." : {
@@ -1283,9 +1537,6 @@
},
"Username error" : {
- },
- "Validate the device" : {
-
},
"Vidéo" : {
diff --git a/Linphone/TelecomManager/ProviderDelegate.swift b/Linphone/TelecomManager/ProviderDelegate.swift
index 7a56be324..727c81dcb 100644
--- a/Linphone/TelecomManager/ProviderDelegate.swift
+++ b/Linphone/TelecomManager/ProviderDelegate.swift
@@ -225,7 +225,12 @@ extension ProviderDelegate: CXProviderDelegate {
DispatchQueue.main.async {
if UIApplication.shared.applicationState != .active {
TelecomManager.shared.backgroundContextCall = call
- TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
+ if call?.callLog != nil {
+ TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
+ } else {
+ TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true
+ }
+
if #available(iOS 16.0, *) {
if call?.cameraEnabled == true {
call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported
diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift
index 1cb34c1dc..59f282a6d 100644
--- a/Linphone/TelecomManager/TelecomManager.swift
+++ b/Linphone/TelecomManager/TelecomManager.swift
@@ -44,6 +44,7 @@ class TelecomManager: ObservableObject {
@Published var callInProgress: Bool = false
@Published var callDisplayed: Bool = true
@Published var callStarted: Bool = false
+ @Published var isNotVerifiedCounter: Int = 0
@Published var outgoingCallStarted: Bool = false
@Published var remoteConfVideo: Bool = false
@Published var isRecordingByRemote: Bool = false
@@ -269,6 +270,7 @@ class TelecomManager: ObservableObject {
DispatchQueue.main.async {
self.outgoingCallStarted = true
self.callStarted = true
+ self.isNotVerifiedCounter = 0
if self.callInProgress == false {
withAnimation {
self.callInProgress = true
@@ -316,6 +318,7 @@ class TelecomManager: ObservableObject {
DispatchQueue.main.async {
self.callStarted = true
+ self.isNotVerifiedCounter = 0
}
} catch {
Log.error("accept call failed \(error)")
diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift
index 2d74a788b..051f0f8e6 100644
--- a/Linphone/UI/Call/CallView.swift
+++ b/Linphone/UI/Call/CallView.swift
@@ -177,11 +177,19 @@ struct CallView: View {
}
if callViewModel.zrtpPopupDisplayed == true {
- ZRTPPopup(callViewModel: callViewModel)
- .background(.black.opacity(0.65))
- .onTapGesture {
- callViewModel.zrtpPopupDisplayed = false
- }
+ if idiom != .pad
+ && (orientation == .landscapeLeft
+ || orientation == .landscapeRight
+ || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
+ && buttonSize != 45 {
+ ZRTPPopup(callViewModel: callViewModel, resizeView: 1.5)
+ .background(.black.opacity(0.65))
+ .frame(maxHeight: geo.size.height)
+ } else {
+ ZRTPPopup(callViewModel: callViewModel, resizeView: buttonSize == 45 ? 1.5 : 1)
+ .background(.black.opacity(0.65))
+ .frame(maxHeight: geo.size.height)
+ }
}
if telecomManager.remainingCall {
@@ -226,7 +234,7 @@ struct CallView: View {
}
Text(callViewModel.displayName)
- .default_text_style_white_800(styleSize: 16)
+ .default_text_style_white_800(styleSize: 16)
if !telecomManager.outgoingCallStarted && telecomManager.callInProgress {
Text("|")
@@ -281,26 +289,114 @@ struct CallView: View {
.frame(height: 40)
.zIndex(1)
- if callViewModel.isMediaEncrypted {
- HStack {
- Image("lock_simple")
- .resizable()
- .frame(width: 15, height: 15, alignment: .leading)
- .padding(.leading, 50)
- .padding(.top, 35)
-
- Text("Appel chiffré de bout en bout")
- .foregroundStyle(Color.blueInfo500)
- .default_text_style_white(styleSize: 12)
- .padding(.top, 35)
-
- Spacer()
+ if !telecomManager.outgoingCallStarted && telecomManager.callInProgress {
+ if callViewModel.isMediaEncrypted && callViewModel.isRemoteDeviceTrusted && callViewModel.isZrtp {
+ HStack {
+ Image("lock-key")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(Color.blueInfo500)
+ .frame(width: 15, height: 15, alignment: .leading)
+ .padding(.leading, 50)
+ .padding(.top, 35)
+
+ Text("call_zrtp_end_to_end_encrypted")
+ .foregroundStyle(Color.blueInfo500)
+ .default_text_style_white(styleSize: 12)
+ .padding(.top, 35)
+
+ Spacer()
+ }
+ .onTapGesture {
+ mediaEncryptedSheet = true
+ }
+ .frame(height: 40)
+ .zIndex(1)
+ } else if callViewModel.isMediaEncrypted && !callViewModel.isZrtp {
+ HStack {
+ Image("lock_simple")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(Color.blueInfo500)
+ .frame(width: 15, height: 15, alignment: .leading)
+ .padding(.leading, 50)
+ .padding(.top, 35)
+
+ Text("call_srtp_point_to_point_encrypted")
+ .foregroundStyle(Color.blueInfo500)
+ .default_text_style_white(styleSize: 12)
+ .padding(.top, 35)
+
+ Spacer()
+ }
+ .onTapGesture {
+ mediaEncryptedSheet = true
+ }
+ .frame(height: 40)
+ .zIndex(1)
+ } else if callViewModel.isMediaEncrypted && (!callViewModel.isRemoteDeviceTrusted && callViewModel.isZrtp) || callViewModel.cacheMismatch {
+ HStack {
+ Image("warning-circle")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(Color.orangeWarning600)
+ .frame(width: 15, height: 15, alignment: .leading)
+ .padding(.leading, 50)
+ .padding(.top, 35)
+
+ Text("call_zrtp_sas_validation_required")
+ .foregroundStyle(Color.orangeWarning600)
+ .default_text_style_white(styleSize: 12)
+ .padding(.top, 35)
+
+ Spacer()
+ }
+ .onTapGesture {
+ mediaEncryptedSheet = true
+ }
+ .frame(height: 40)
+ .zIndex(1)
+ } else if callViewModel.isNotEncrypted {
+ HStack {
+ Image("lock_simple")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 15, height: 15, alignment: .leading)
+ .padding(.leading, 50)
+ .padding(.top, 35)
+
+ Text("call_not_encrypted")
+ .foregroundStyle(.white)
+ .default_text_style_white(styleSize: 12)
+ .padding(.top, 35)
+
+ Spacer()
+ }
+ .onTapGesture {
+ mediaEncryptedSheet = true
+ }
+ .frame(height: 40)
+ .zIndex(1)
+ } else {
+ HStack {
+ ProgressView()
+ .controlSize(.mini)
+ .progressViewStyle(CircularProgressViewStyle(tint: .white))
+ .frame(width: 15, height: 15, alignment: .leading)
+ .padding(.leading, 50)
+ .padding(.top, 35)
+
+ Text("call_waiting_for_encryption_info")
+ .foregroundStyle(.white)
+ .default_text_style_white(styleSize: 12)
+ .padding(.top, 35)
+
+ Spacer()
+ }
+ .frame(height: 40)
+ .zIndex(1)
}
- .onTapGesture {
- mediaEncryptedSheet = true
- }
- .frame(height: 40)
- .zIndex(1)
}
}
}
diff --git a/Linphone/UI/Call/Fragments/ZRTPPopup.swift b/Linphone/UI/Call/Fragments/ZRTPPopup.swift
index 332803d22..3c27f8df4 100644
--- a/Linphone/UI/Call/Fragments/ZRTPPopup.swift
+++ b/Linphone/UI/Call/Fragments/ZRTPPopup.swift
@@ -27,123 +27,367 @@ struct ZRTPPopup: View {
@ObservedObject var callViewModel: CallViewModel
+ private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
+ @State private var orientation = UIDevice.current.orientation
+
+ var resizeView: CGFloat
+
var body: some View {
+ if callViewModel.isNotVerified {
+ alertZRTP
+ } else {
+ popupZRTP
+ }
+ }
+
+ var popupZRTP: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
- Text("Validate the device")
- .default_text_style_600(styleSize: 20)
-
- Text("Say \(callViewModel.upperCaseAuthTokenToRead) and click on the letters given by your correspondent:")
- .default_text_style(styleSize: 15)
- .padding(.bottom, 20)
-
- HStack(spacing: 25) {
- Spacer()
-
- HStack(alignment: .center) {
- Text(callViewModel.letters1)
- .default_text_style(styleSize: 30)
- .frame(width: 60, height: 60)
- }
- .padding(10)
- .background(Color.grayMain2c200)
- .cornerRadius(40)
- .onTapGesture {
- callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters1)
- callViewModel.zrtpPopupDisplayed = false
- }
-
- HStack(alignment: .center) {
- Text(callViewModel.letters2)
- .default_text_style(styleSize: 30)
- .frame(width: 60, height: 60)
- }
- .padding(10)
- .background(Color.grayMain2c200)
- .cornerRadius(40)
- .onTapGesture {
- callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters2)
- callViewModel.zrtpPopupDisplayed = false
- }
-
- Spacer()
- }
- .padding(.bottom, 20)
-
- HStack(spacing: 25) {
- Spacer()
-
- HStack(alignment: .center) {
- Text(callViewModel.letters3)
- .default_text_style(styleSize: 30)
- .frame(width: 60, height: 60)
- }
- .padding(10)
- .background(Color.grayMain2c200)
- .cornerRadius(40)
- .onTapGesture {
- callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters3)
- callViewModel.zrtpPopupDisplayed = false
- }
-
- HStack(alignment: .center) {
- Text(callViewModel.letters4)
- .default_text_style(styleSize: 30)
- .frame(width: 60, height: 60)
- }
- .padding(10)
- .background(Color.grayMain2c200)
- .cornerRadius(40)
- .onTapGesture {
- callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters4)
- callViewModel.zrtpPopupDisplayed = false
- }
-
- Spacer()
- }
- .padding(.bottom, 20)
-
- HStack {
- Text("Skip")
- .underline()
- .tint(Color.grayMain2c600)
- .default_text_style_600(styleSize: 15)
- .foregroundStyle(Color.grayMain2c500)
- }
- .frame(maxWidth: .infinity)
- .padding(.bottom, 30)
- .onTapGesture {
- callViewModel.skipZrtpAuthentication()
- callViewModel.zrtpPopupDisplayed = false
- }
-
- Button(action: {
- callViewModel.updateZrtpSas(authTokenClicked: "")
- callViewModel.zrtpPopupDisplayed = false
- }, label: {
- Text("Letters don't match!")
- .default_text_style_orange_600(styleSize: 20)
- .frame(height: 35)
+ ZStack(alignment: .top, content: {
+ HStack {
+ Spacer()
+
+ VStack {
+ Image("security")
+ .resizable()
+ .frame(width: 20, height: 20, alignment: .leading)
+
+ Text("call_dialog_zrtp_validate_trust_title")
+ .default_text_style_white_700(styleSize: 16 / resizeView)
+ }
.frame(maxWidth: .infinity)
+
+ Spacer()
+ }
+ .padding(.top, 15)
+ .padding(.bottom, 2)
+
+ HStack {
+ Spacer()
+ HStack {
+ Text("call_zrtp_sas_validation_skip")
+ .underline()
+ .tint(.white)
+ .default_text_style_white_600(styleSize: 16 / resizeView)
+ .foregroundStyle(.white)
+ }
+ .onTapGesture {
+ callViewModel.skipZrtpAuthentication()
+ callViewModel.zrtpPopupDisplayed = false
+ }
+ }
+ .padding(.top, 10 / resizeView)
+ .padding(.trailing, 15 / resizeView)
})
- .padding(.horizontal, 20)
- .padding(.vertical, 10)
- .cornerRadius(60)
- .overlay(
- RoundedRectangle(cornerRadius: 60)
- .inset(by: 0.5)
- .stroke(Color.orangeMain500, lineWidth: 1)
- )
- .padding(.bottom)
+
+ VStack(alignment: .center) {
+ VStack {
+ if idiom != .pad && (orientation == .landscapeLeft
+ || orientation == .landscapeRight
+ || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
+ HStack {
+ Text("call_dialog_zrtp_validate_trust_message")
+ .default_text_style(styleSize: 16 / resizeView)
+ .multilineTextAlignment(.center)
+ .padding(.bottom, 10 / resizeView)
+
+ VStack {
+ Text("call_dialog_zrtp_validate_trust_local_code_label")
+ .default_text_style(styleSize: 16 / resizeView)
+ .multilineTextAlignment(.center)
+
+ Text(!callViewModel.upperCaseAuthTokenToRead.isEmpty ? callViewModel.upperCaseAuthTokenToRead : "ZZ")
+ .default_text_style_700(styleSize: 22 / resizeView)
+ .padding(.bottom, 20 / resizeView)
+ }
+ }
+ } else {
+ Text(callViewModel.cacheMismatch ? "call_dialog_zrtp_validate_trust_warning_message" : "call_dialog_zrtp_validate_trust_message")
+ .default_text_style(styleSize: 16 / resizeView)
+ .multilineTextAlignment(.center)
+ .padding(.bottom, 10 / resizeView)
+
+ Text("call_dialog_zrtp_validate_trust_local_code_label")
+ .default_text_style(styleSize: 16 / resizeView)
+ .multilineTextAlignment(.center)
+
+ Text(!callViewModel.upperCaseAuthTokenToRead.isEmpty ? callViewModel.upperCaseAuthTokenToRead : "ZZ")
+ .default_text_style_800(styleSize: 22 / resizeView)
+ }
+ }
+ .padding(.bottom, 5)
+
+ VStack {
+ Text("call_dialog_zrtp_validate_trust_remote_code_label")
+ .default_text_style(styleSize: 16 / resizeView)
+ .multilineTextAlignment(.center)
+ .padding(.top, 15 / resizeView)
+ .padding(.bottom, 10 / resizeView)
+
+ if idiom != .pad && (orientation == .landscapeLeft
+ || orientation == .landscapeRight
+ || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
+ HStack(spacing: 30) {
+ HStack(alignment: .center) {
+ Text(callViewModel.letters1)
+ .default_text_style(styleSize: 24 / resizeView)
+ .frame(width: 45 / resizeView, height: 45 / resizeView)
+ }
+ .padding(10 / resizeView)
+ .background(.white)
+ .clipShape(Circle())
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ .onTapGesture {
+ callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters1)
+ callViewModel.zrtpPopupDisplayed = false
+ }
+
+ HStack(alignment: .center) {
+ Text(callViewModel.letters2)
+ .default_text_style(styleSize: 24 / resizeView)
+ .frame(width: 45 / resizeView, height: 45 / resizeView)
+ }
+ .padding(10 / resizeView)
+ .background(.white)
+ .clipShape(Circle())
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ .onTapGesture {
+ callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters2)
+ callViewModel.zrtpPopupDisplayed = false
+ }
+
+ HStack(alignment: .center) {
+ Text(callViewModel.letters3)
+ .default_text_style(styleSize: 24 / resizeView)
+ .frame(width: 45 / resizeView, height: 45 / resizeView)
+ }
+ .padding(10 / resizeView)
+ .background(.white)
+ .clipShape(Circle())
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ .onTapGesture {
+ callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters3)
+ callViewModel.zrtpPopupDisplayed = false
+ }
+
+ HStack(alignment: .center) {
+ Text(callViewModel.letters4)
+ .default_text_style(styleSize: 24 / resizeView)
+ .frame(width: 45 / resizeView, height: 45 / resizeView)
+ }
+ .padding(10 / resizeView)
+ .background(.white)
+ .clipShape(Circle())
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ .onTapGesture {
+ callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters4)
+ callViewModel.zrtpPopupDisplayed = false
+ }
+ }
+ .padding(.horizontal, 40 / resizeView)
+ .padding(.bottom, 20 / resizeView)
+ } else {
+ HStack(spacing: 30) {
+ HStack(alignment: .center) {
+ Text(callViewModel.letters1)
+ .default_text_style(styleSize: 34 / resizeView)
+ .frame(width: 60 / resizeView, height: 60 / resizeView)
+ }
+ .padding(10 / resizeView)
+ .background(.white)
+ .clipShape(Circle())
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ .onTapGesture {
+ callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters1)
+ callViewModel.zrtpPopupDisplayed = false
+ }
+
+ HStack(alignment: .center) {
+ Text(callViewModel.letters2)
+ .default_text_style(styleSize: 34 / resizeView)
+ .frame(width: 60 / resizeView, height: 60 / resizeView)
+ }
+ .padding(10 / resizeView)
+ .background(.white)
+ .clipShape(Circle())
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ .onTapGesture {
+ callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters2)
+ callViewModel.zrtpPopupDisplayed = false
+ }
+ }
+ .padding(.horizontal, 40 / resizeView)
+ .padding(.bottom, 20 / resizeView)
+
+ HStack(spacing: 30) {
+ HStack(alignment: .center) {
+ Text(callViewModel.letters3)
+ .default_text_style(styleSize: 34 / resizeView)
+ .frame(width: 60 / resizeView, height: 60 / resizeView)
+ }
+ .padding(10 / resizeView)
+ .background(.white)
+ .clipShape(Circle())
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ .onTapGesture {
+ callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters3)
+ callViewModel.zrtpPopupDisplayed = false
+ }
+
+ HStack(alignment: .center) {
+ Text(callViewModel.letters4)
+ .default_text_style(styleSize: 34 / resizeView)
+ .frame(width: 60 / resizeView, height: 60 / resizeView)
+ }
+ .padding(10 / resizeView)
+ .background(.white)
+ .clipShape(Circle())
+ .shadow(color: .gray.opacity(0.4), radius: 4)
+ .onTapGesture {
+ callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters4)
+ callViewModel.zrtpPopupDisplayed = false
+ }
+ }
+ .padding(.horizontal, 40 / resizeView)
+ .padding(.bottom, 20 / resizeView)
+ }
+ }
+ .padding(.horizontal, 10 / resizeView)
+ .padding(.bottom, 10 / resizeView)
+ .cornerRadius(20)
+ .overlay(
+ RoundedRectangle(cornerRadius: 20)
+ .inset(by: 0.5)
+ .stroke(Color.grayMain2c200, lineWidth: 1)
+ )
+ .padding(.bottom, 10 / resizeView)
+
+ Button(action: {
+ callViewModel.updateZrtpSas(authTokenClicked: "")
+ callViewModel.zrtpPopupDisplayed = false
+ }, label: {
+ Text("call_dialog_zrtp_validate_trust_letters_do_not_match")
+ .foregroundStyle(Color.redDanger500)
+ .default_text_style_orange_600(styleSize: 20 / resizeView)
+ .frame(height: 35 / resizeView)
+ .frame(maxWidth: .infinity)
+ })
+ .padding(.horizontal, 20 / resizeView)
+ .padding(.vertical, 10 / resizeView)
+ .cornerRadius(60)
+ .overlay(
+ RoundedRectangle(cornerRadius: 60)
+ .inset(by: 0.5)
+ .stroke(Color.redDanger500, lineWidth: 1)
+ )
+ .padding(.bottom)
+ }
+ .padding(.top, 20 / resizeView)
+ .padding(.horizontal, 20 / resizeView)
+ .background(.white)
+ .cornerRadius(20)
}
- .padding(.horizontal, 20)
- .padding(.vertical, 20)
- .background(.white)
+ .background(callViewModel.cacheMismatch ? Color.orangeWarning600 : Color.blueInfo500)
.cornerRadius(20)
- .padding(.horizontal)
+ .padding(.horizontal, 2)
.frame(maxHeight: .infinity)
- .shadow(color: Color.orangeMain500, radius: 0, x: 0, y: 2)
- .frame(maxWidth: sharedMainViewModel.maxWidth)
+ .shadow(color: callViewModel.cacheMismatch ? Color.orangeWarning600 : Color.blueInfo500, radius: 0, x: 0, y: 2)
+ .frame(maxWidth: sharedMainViewModel.maxWidth * 1.2)
+ .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
+ .onAppear {
+ callViewModel.remoteAuthenticationTokens()
+ }
+ }
+ }
+
+ var alertZRTP: some View {
+ GeometryReader { geometry in
+ VStack(alignment: .leading) {
+ ZStack(alignment: .top, content: {
+ HStack {
+ Spacer()
+
+ VStack {
+ Image("shield-warning")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 25, height: 25, alignment: .leading)
+
+ Text("call_dialog_zrtp_security_alert_title")
+ .default_text_style_white_700(styleSize: 16 / resizeView)
+ }
+ .frame(maxWidth: .infinity)
+
+ Spacer()
+ }
+ .padding(.top, 15)
+ .padding(.bottom, 2)
+ })
+
+ VStack(alignment: .center) {
+ VStack {
+ Text("call_dialog_zrtp_security_alert_message")
+ .default_text_style(styleSize: 16 / resizeView)
+ .multilineTextAlignment(.center)
+ .padding(.bottom, 10 / resizeView)
+ }
+ .padding(.bottom, 5)
+
+ if telecomManager.isNotVerifiedCounter <= 1 {
+ Button(action: {
+ callViewModel.isNotVerified = false
+ }, label: {
+ Text("call_dialog_zrtp_security_alert_try_again")
+ .foregroundStyle(Color.redDanger500)
+ .default_text_style_orange_600(styleSize: 20 / resizeView)
+ .frame(height: 35 / resizeView)
+ .frame(maxWidth: .infinity)
+ })
+ .padding(.horizontal, 20 / resizeView)
+ .padding(.vertical, 10 / resizeView)
+ .cornerRadius(60)
+ .overlay(
+ RoundedRectangle(cornerRadius: 60)
+ .inset(by: 0.5)
+ .stroke(Color.redDanger500, lineWidth: 1)
+ )
+ .padding(.bottom)
+ }
+
+ Button(action: {
+ callViewModel.terminateCall()
+ }, label: {
+ HStack {
+ Image("phone-disconnect")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(.white)
+ .frame(width: 20, height: 20)
+
+ Text("call_action_hang_up")
+ .default_text_style_white_600(styleSize: 20)
+ .frame(height: 35)
+ }
+ .frame(maxWidth: .infinity)
+ })
+ .padding(.horizontal, 20 / resizeView)
+ .padding(.vertical, 10 / resizeView)
+ .background(Color.redDanger500)
+ .cornerRadius(60)
+ .padding(.bottom)
+ }
+ .padding(.top, 20 / resizeView)
+ .padding(.horizontal, 20 / resizeView)
+ .background(.white)
+ .cornerRadius(20)
+ }
+ .background(Color.redDanger500)
+ .cornerRadius(20)
+ .padding(.horizontal, 2)
+ .frame(maxHeight: .infinity)
+ .shadow(color: Color.redDanger500, radius: 0, x: 0, y: 2)
+ .frame(maxWidth: sharedMainViewModel.maxWidth * 1.2)
.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
.onAppear {
callViewModel.remoteAuthenticationTokens()
@@ -153,5 +397,5 @@ struct ZRTPPopup: View {
}
#Preview {
- ZRTPPopup(callViewModel: CallViewModel())
+ ZRTPPopup(callViewModel: CallViewModel(), resizeView: 1)
}
diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift
index 741d58cf4..6fcdf3ba8 100644
--- a/Linphone/UI/Call/ViewModel/CallViewModel.swift
+++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift
@@ -42,8 +42,11 @@ class CallViewModel: ObservableObject {
@Published var upperCaseAuthTokenToRead = ""
@Published var upperCaseAuthTokenToListen = ""
@Published var isMediaEncrypted: Bool = false
- @Published var isZrtpPq: Bool = false
+ @Published var isNotEncrypted: Bool = false
+ @Published var isZrtp: Bool = false
@Published var isRemoteDeviceTrusted: Bool = false
+ @Published var cacheMismatch: Bool = false
+ @Published var isNotVerified: Bool = false
@Published var selectedCall: Call?
@Published var isTransferInsteadCall: Bool = false
@Published var isOneOneCall: Bool = false
@@ -125,14 +128,14 @@ class CallViewModel: ObservableObject {
}
var isMediaEncryptedTmp = false
- var isZrtpPqTmp = false
+ var isZrtpTmp = false
if self.currentCall != nil && self.currentCall!.currentParams != nil {
if self.currentCall!.currentParams!.mediaEncryption == .ZRTP ||
self.currentCall!.currentParams!.mediaEncryption == .SRTP ||
self.currentCall!.currentParams!.mediaEncryption == .DTLS {
isMediaEncryptedTmp = true
- isZrtpPqTmp = self.currentCall!.currentParams!.mediaEncryption == .ZRTP
+ isZrtpTmp = self.currentCall!.currentParams!.mediaEncryption == .ZRTP
}
}
@@ -200,6 +203,9 @@ class CallViewModel: ObservableObject {
self.zrtpPopupDisplayed = false
self.upperCaseAuthTokenToRead = ""
self.upperCaseAuthTokenToListen = ""
+ self.isNotVerified = false
+
+ self.updateEncryption()
self.isConference = false
self.participantList = []
self.activeSpeakerParticipant = nil
@@ -209,7 +215,9 @@ class CallViewModel: ObservableObject {
self.videoDisplayed = videoDisplayedTmp
self.isOneOneCall = isOneOneCallTmp
self.isMediaEncrypted = isMediaEncryptedTmp
- self.isZrtpPq = isZrtpPqTmp
+ self.isNotEncrypted = false
+ self.isZrtp = isZrtpTmp
+ self.cacheMismatch = cacheMismatchFlag
self.getCallsList()
@@ -228,21 +236,35 @@ class CallViewModel: ObservableObject {
})
self.callSuscriptions.insert(self.currentCall!.publisher?.onStatsUpdated?.postOnCoreQueue {(cbVal: (call: Call, stats: CallStats)) in
- if self.currentCall != nil {
- DispatchQueue.main.async {
+ DispatchQueue.main.async {
+ if self.currentCall != nil {
self.callStatsModel.update(call: self.currentCall!, stats: cbVal.stats)
}
}
})
-
self.callSuscriptions.insert(
self.currentCall!.publisher?.onAuthenticationTokenVerified?.postOnCoreQueue {(call: Call, verified: Bool) in
Log.warn("[CallViewModel][ZRTPPopup] Notified that authentication token is \(verified ? "verified" : "not verified!")")
-
- self.updateEncryption()
- if self.currentCall != nil {
- self.callMediaEncryptionModel.update(call: self.currentCall!)
+ if verified {
+ self.updateEncryption()
+ if self.currentCall != nil {
+ self.callMediaEncryptionModel.update(call: self.currentCall!)
+ }
+ } else {
+ if self.telecomManager.isNotVerifiedCounter == 0 {
+ DispatchQueue.main.async {
+ self.isNotVerified = true
+ self.telecomManager.isNotVerifiedCounter += 1
+ }
+ self.showZrtpSasDialogIfPossible()
+ } else {
+ DispatchQueue.main.async {
+ self.isNotVerified = true
+ self.telecomManager.isNotVerifiedCounter += 1
+ self.zrtpPopupDisplayed = true
+ }
+ }
}
}
)
@@ -641,6 +663,7 @@ class CallViewModel: ObservableObject {
telecomManager.callInProgress = true
telecomManager.callDisplayed = true
telecomManager.callStarted = true
+ telecomManager.isNotVerifiedCounter = 0
}
coreContext.doOnCoreQueue { core in
@@ -881,18 +904,20 @@ class CallViewModel: ObservableObject {
coreContext.doOnCoreQueue { core in
if core.currentCall != nil {
let tokens = core.currentCall!.remoteAuthenticationTokens
- DispatchQueue.main.async {
- self.letters1 = tokens[0]
- self.letters2 = tokens[1]
- self.letters3 = tokens[2]
- self.letters4 = tokens[3]
+ if !tokens.isEmpty {
+ DispatchQueue.main.async {
+ self.letters1 = tokens[0]
+ self.letters2 = tokens[1]
+ self.letters3 = tokens[2]
+ self.letters4 = tokens[3]
+ }
}
}
}
}
private func updateEncryption() {
- coreContext.doOnCoreQueue { core in
+ coreContext.doOnCoreQueue { _ in
if self.currentCall != nil && self.currentCall!.currentParams != nil {
switch self.currentCall!.currentParams!.mediaEncryption {
case MediaEncryption.ZRTP:
@@ -918,12 +943,14 @@ class CallViewModel: ObservableObject {
*/
// When Post Quantum is available, ZRTP is Post Quantum
- let isZrtpPqTmp = Core.getPostQuantumAvailable
+ let isZrtpPQTmp = Core.getPostQuantumAvailable
DispatchQueue.main.async {
self.isRemoteDeviceTrusted = isRemoteDeviceTrustedTmp
self.isMediaEncrypted = true
- self.isZrtpPq = isZrtpPqTmp
+ self.isZrtp = true
+ self.cacheMismatch = cacheMismatchFlag
+ self.isNotEncrypted = false
if isDeviceTrusted {
ToastViewModel.shared.toastMessage = "Info_call_securised"
@@ -938,12 +965,18 @@ class CallViewModel: ObservableObject {
case MediaEncryption.SRTP, MediaEncryption.DTLS:
DispatchQueue.main.async {
self.isMediaEncrypted = true
- self.isZrtpPq = false
+ self.isZrtp = false
+ self.isNotEncrypted = false
}
- default:
+ case MediaEncryption.None:
DispatchQueue.main.async {
self.isMediaEncrypted = false
- self.isZrtpPq = false
+ self.isZrtp = false
+ if self.currentCall!.state == .StreamsRunning {
+ self.isNotEncrypted = true
+ } else {
+ self.isNotEncrypted = false
+ }
}
}
}
@@ -971,26 +1004,28 @@ class CallViewModel: ObservableObject {
let mySubstringSuffix = upperCaseAuthToken.suffix(2)
- switch self.currentCall!.dir {
- case Call.Dir.Incoming:
- self.upperCaseAuthTokenToRead = String(mySubstringPrefix)
- self.upperCaseAuthTokenToListen = String(mySubstringSuffix)
- default:
- self.upperCaseAuthTokenToRead = String(mySubstringSuffix)
- self.upperCaseAuthTokenToListen = String(mySubstringPrefix)
+ DispatchQueue.main.async {
+ switch self.currentCall!.dir {
+ case Call.Dir.Incoming:
+ self.upperCaseAuthTokenToRead = String(mySubstringPrefix)
+ self.upperCaseAuthTokenToListen = String(mySubstringSuffix)
+ default:
+ self.upperCaseAuthTokenToRead = String(mySubstringSuffix)
+ self.upperCaseAuthTokenToListen = String(mySubstringPrefix)
+ }
+
+ self.zrtpPopupDisplayed = true
}
-
- self.zrtpPopupDisplayed = true
}
}
func transferClicked() {
coreContext.doOnCoreQueue { core in
- var callToTransferTo = core.calls.last { call in
+ let callToTransferTo = core.calls.last { call in
call.state == Call.State.Paused && call.callLog?.callId != self.currentCall?.callLog?.callId
}
- if (callToTransferTo == nil) {
+ if callToTransferTo == nil {
Log.error(
"[CallViewModel] Couldn't find a call in Paused state to transfer current call to"
)
diff --git a/Linphone/Utils/Extensions/AccountExtension.swift b/Linphone/Utils/Extensions/AccountExtension.swift
index 44be51e12..7ae58904b 100644
--- a/Linphone/Utils/Extensions/AccountExtension.swift
+++ b/Linphone/Utils/Extensions/AccountExtension.swift
@@ -35,6 +35,10 @@ extension Account {
}
static func == (lhs: Account, rhs: Account) -> Bool {
- return lhs.params?.identityAddress?.asString() == rhs.params?.identityAddress?.asString()
+ if lhs.params != nil && lhs.params?.identityAddress != nil && rhs.params != nil && rhs.params?.identityAddress != nil {
+ return lhs.params?.identityAddress?.asString() == rhs.params?.identityAddress?.asString()
+ } else {
+ return false
+ }
}
}