Add account settings view

This commit is contained in:
Benoit Martins 2025-01-03 17:55:25 +01:00
parent 35eb8cb8df
commit 6d116b6cec
5 changed files with 1781 additions and 501 deletions

View file

@ -151,6 +151,8 @@
D7C3650E2AF15BF200FE6142 /* PhotoPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */; };
D7C48DF42AFA66F900D938CB /* EditContactController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C48DF32AFA66F900D938CB /* EditContactController.swift */; };
D7C48DF62AFCDF4700D938CB /* ContactInnerActionsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C48DF52AFCDF4700D938CB /* ContactInnerActionsFragment.swift */; };
D7C500402D27F16C00DD53EC /* AccountSettingsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C5003F2D27F16900DD53EC /* AccountSettingsFragment.swift */; };
D7C500422D2BE98100DD53EC /* AccountSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C500412D2BE96E00DD53EC /* AccountSettingsViewModel.swift */; };
D7CEE0352B7A210300FD79B7 /* ConversationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CEE0342B7A210300FD79B7 /* ConversationsView.swift */; };
D7CEE0382B7A214F00FD79B7 /* ConversationsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CEE0372B7A214F00FD79B7 /* ConversationsListViewModel.swift */; };
D7CEE03B2B7A234200FD79B7 /* ConversationsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CEE03A2B7A234200FD79B7 /* ConversationsFragment.swift */; };
@ -347,6 +349,8 @@
D7C3650D2AF15BF200FE6142 /* PhotoPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPicker.swift; sourceTree = "<group>"; };
D7C48DF32AFA66F900D938CB /* EditContactController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditContactController.swift; sourceTree = "<group>"; };
D7C48DF52AFCDF4700D938CB /* ContactInnerActionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInnerActionsFragment.swift; sourceTree = "<group>"; };
D7C5003F2D27F16900DD53EC /* AccountSettingsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingsFragment.swift; sourceTree = "<group>"; };
D7C500412D2BE96E00DD53EC /* AccountSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingsViewModel.swift; sourceTree = "<group>"; };
D7CEE0342B7A210300FD79B7 /* ConversationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationsView.swift; sourceTree = "<group>"; };
D7CEE0372B7A214F00FD79B7 /* ConversationsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationsListViewModel.swift; sourceTree = "<group>"; };
D7CEE03A2B7A234200FD79B7 /* ConversationsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationsFragment.swift; sourceTree = "<group>"; };
@ -927,6 +931,7 @@
D7DC096B2CFA192F00A6D47C /* Fragments */ = {
isa = PBXGroup;
children = (
D7C5003F2D27F16900DD53EC /* AccountSettingsFragment.swift */,
D7DC096E2CFA1D7400A6D47C /* AccountProfileFragment.swift */,
);
path = Fragments;
@ -942,6 +947,7 @@
D7DC096D2CFA194600A6D47C /* ViewModel */ = {
isa = PBXGroup;
children = (
D7C500412D2BE96E00DD53EC /* AccountSettingsViewModel.swift */,
D7DC09702CFDBF8300A6D47C /* AccountProfileViewModel.swift */,
);
path = ViewModel;
@ -1189,6 +1195,7 @@
C6A5A9452C10B6270070FEA4 /* OIDAuthStateExtension.swift in Sources */,
D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */,
D79F1C162CD3D6AD00FF0A05 /* ConversationInfoFragment.swift in Sources */,
D7C500422D2BE98100DD53EC /* AccountSettingsViewModel.swift in Sources */,
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */,
C67586B02C09F247002E77BF /* URIHandler.swift in Sources */,
C62817282C1B389700DBA646 /* SideMenuAccountRow.swift in Sources */,
@ -1214,6 +1221,7 @@
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */,
D732A9152B04C7FE00DB42BA /* HistoryListViewModel.swift in Sources */,
6646A7A32BB2E224006B842A /* ScheduleMeetingFragment.swift in Sources */,
D7C500402D27F16C00DD53EC /* AccountSettingsFragment.swift in Sources */,
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */,
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */,
D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */,

View file

@ -150,6 +150,421 @@
},
"9" : {
},
"A subject and at least one participant is required to create a meeting" : {
},
"Accept all" : {
},
"Account successfully logged out" : {
},
"account_settings_audio_video_conference_factory_uri_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Audio/video conference factory URI"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "URI de l'usine à réunions"
}
}
}
},
"account_settings_avpf_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "AVPF"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "AVPF"
}
}
}
},
"account_settings_bundle_mode_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bundle mode"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mode \"bundle\""
}
}
}
},
"account_settings_ccmp_server_url_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "CCMP server URL"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "URL du serveur CCMP"
}
}
}
},
"account_settings_conference_factory_uri_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Conference factory URI"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "URI de l'usine à conversations"
}
}
}
},
"account_settings_cpim_in_basic_conversations_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Use CPIM in \"basic\" conversations"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Utiliser CPIM dans les conversations \"basiques\""
}
}
}
},
"account_settings_enable_ice_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Enable ICE"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Activer ICE"
}
}
}
},
"account_settings_enable_turn_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Enable TURN"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Activer TURN"
}
}
}
},
"account_settings_expire_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Expire (in seconds)"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Expiration (en secondes)"
}
}
}
},
"account_settings_im_encryption_mandatory_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "IM encryption mandatory"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Chiffrement obligatoire des conversations"
}
}
}
},
"account_settings_lime_server_url_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "E2E encryption keys server URL"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "URL du serveur d'échange de clés de chiffrement"
}
}
}
},
"account_settings_mwi_uri_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "MWI server URI (Message Waiting Indicator)"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "URI du serveur MWI (Message Waiting Indicator)"
}
}
}
},
"account_settings_nat_policy_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "NAT policy settings"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paramètres de politique NAT"
}
}
}
},
"account_settings_outbound_proxy_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Outbound proxy"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Serveur mandataire sortant"
}
}
}
},
"account_settings_push_notification_not_available_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Push notifications aren't available!"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Les notifications poussées ne sont pas disponibles !"
}
}
}
},
"account_settings_push_notification_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Allow push notifications"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Autoriser les notifications poussées"
}
}
}
},
"account_settings_sip_proxy_url_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "SIP proxy server URL"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "URL du serveur mandataire"
}
}
}
},
"account_settings_stun_server_url_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "STUN/TURN server URL"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "URL du serveur STUN/TURN"
}
}
}
},
"account_settings_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Account settings"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paramètres de compte"
}
}
}
},
"account_settings_turn_password_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "TURN password"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mot de passe TURN"
}
}
}
},
"account_settings_turn_username_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "TURN username"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Utilisateur TURN"
}
}
}
},
"account_settings_update_password_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Update password"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mettre à jour le mot de passe"
}
}
}
},
"account_settings_voicemail_uri_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Voicemail URI"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "URI du serveur de messagerie vocale"
}
}
}
},
"Active" : {
},
"Administrateur" : {
},
"All calls will be removed from the history." : {
},
"All contacts" : {
},
"All modifications will be canceled." : {
},
"assistant_account_create" : {
"localizations" : {
@ -5266,6 +5681,32 @@
}
}
},
"Send cancellation notifications" : {
},
"Send Logs" : {
},
"Send notification to participants ?" : {
},
"settings_advanced_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Advanced settings"
}
},
"fr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Paramètres avancés"
}
}
}
},
"settings_title" : {
"extractionState" : "manual",
"localizations" : {

View file

@ -45,6 +45,7 @@ struct AccountProfileFragment: View {
private let avatarSize = 100.0
var body: some View {
NavigationView {
ZStack {
VStack(spacing: 1) {
Rectangle()
@ -542,8 +543,13 @@ struct AccountProfileFragment: View {
VStack(spacing: 0) {
VStack(spacing: 18) {
Button(action: {
}, label: {
if accountProfileViewModel.accountModelIndex != nil && CoreContext.shared.accounts.count > accountProfileViewModel.accountModelIndex! {
NavigationLink(
destination:
AccountSettingsFragment(
accountModel: CoreContext.shared.accounts[accountProfileViewModel.accountModelIndex!]
),
label: {
HStack {
Image("gear")
.renderingMode(.template)
@ -556,7 +562,9 @@ struct AccountProfileFragment: View {
.default_text_style(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
}
})
}
)
}
Divider()
@ -643,6 +651,10 @@ struct AccountProfileFragment: View {
}
}
}
.navigationTitle("")
.navigationBarHidden(true)
}
.navigationViewStyle(StackNavigationViewStyle())
}
func saveImage() {

View file

@ -0,0 +1,598 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SwiftUI
// swiftlint:disable type_body_length
struct AccountSettingsFragment: View {
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
@StateObject private var accountSettingsViewModel: AccountSettingsViewModel
@Environment(\.dismiss) var dismiss
@State var natPolicySettingsIsOpen: Bool = false
@State var advancedSettingsIsOpen: Bool = false
@State var isSecured: Bool = true
@FocusState var isVoicemailUriFocused: Bool
@FocusState var isMwiUriFocused: Bool
@FocusState var isStunServerUriFocused: Bool
@FocusState var isTurnUsernameFocused: Bool
@FocusState var isTurnPasswordFocused: Bool
@FocusState var isSipProxyUrlFocused: Bool
@FocusState var isSettingsExpireFocused: Bool
@FocusState var isConferenceFactoryUriFocused: Bool
@FocusState var isAudioVideoConferenceFactoryUriFocused: Bool
@FocusState var isCcmpServerUrlFocused: Bool
@FocusState var isLimeServerUrlFocused: Bool
init(accountModel: AccountModel) {
_accountSettingsViewModel = StateObject(wrappedValue: AccountSettingsViewModel(accountModel: accountModel))
}
var body: some View {
ZStack {
VStack(spacing: 1) {
Rectangle()
.foregroundColor(Color.orangeMain500)
.edgesIgnoringSafeArea(.top)
.frame(height: 0)
HStack {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
.padding(.top, 4)
.padding(.leading, -10)
.onTapGesture {
accountSettingsViewModel.saveChanges()
dismiss()
}
Text("account_settings_title")
.default_text_style_orange_800(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 4)
.lineLimit(1)
Spacer()
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 4)
.background(.white)
ScrollView {
VStack(spacing: 0) {
VStack(spacing: 0) {
VStack(spacing: 0) {
VStack(spacing: 30) {
Toggle("account_settings_push_notification_title", isOn: Binding(
get: { accountSettingsViewModel.pushNotification },
set: { _ in
accountSettingsViewModel.pushNotification.toggle()
}
))
.default_text_style_700(styleSize: 15)
Toggle("account_settings_im_encryption_mandatory_title", isOn: Binding(
get: { accountSettingsViewModel.imEncryptionMandatory },
set: { _ in
accountSettingsViewModel.imEncryptionMandatory.toggle()
}
))
.default_text_style_700(styleSize: 15)
VStack(alignment: .leading) {
Text("account_settings_voicemail_uri_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_voicemail_uri_title", text: Binding(
get: { accountSettingsViewModel.voicemailUri },
set: { newValue in
accountSettingsViewModel.voicemailUri = newValue
}
))
.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(isVoicemailUriFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isVoicemailUriFocused)
}
VStack(alignment: .leading) {
Text("account_settings_mwi_uri_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_mwi_uri_title", text: Binding(
get: { accountSettingsViewModel.mwiUri },
set: { newValue in
accountSettingsViewModel.mwiUri = newValue
}
))
.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(isMwiUriFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isMwiUriFocused)
}
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.top, 10)
.background(Color.gray100)
HStack(alignment: .center) {
Text("account_settings_nat_policy_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image(natPolicySettingsIsOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.padding(.top, 30)
.padding(.bottom, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
.onTapGesture {
withAnimation {
natPolicySettingsIsOpen.toggle()
}
}
if natPolicySettingsIsOpen {
if accountSettingsViewModel.accountModel.avatarModel != nil {
VStack(spacing: 0) {
VStack(spacing: 30) {
VStack(alignment: .leading) {
Text("account_settings_stun_server_url_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_stun_server_url_title", text: Binding(
get: { accountSettingsViewModel.stunServerUrl },
set: { newValue in
accountSettingsViewModel.stunServerUrl = newValue
}
))
.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(isStunServerUriFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isStunServerUriFocused)
}
Toggle("account_settings_enable_ice_title", isOn: Binding(
get: { accountSettingsViewModel.enableIce },
set: { _ in
accountSettingsViewModel.enableIce.toggle()
}
))
.default_text_style_700(styleSize: 15)
Toggle("account_settings_enable_turn_title", isOn: Binding(
get: { accountSettingsViewModel.enableTurn },
set: { _ in
accountSettingsViewModel.enableTurn.toggle()
}
))
.default_text_style_700(styleSize: 15)
VStack(alignment: .leading) {
Text("account_settings_turn_username_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_turn_username_title", text: Binding(
get: { accountSettingsViewModel.turnUsername },
set: { newValue in
accountSettingsViewModel.turnUsername = newValue
}
))
.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(isTurnUsernameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isTurnUsernameFocused)
}
VStack(alignment: .leading) {
Text("account_settings_turn_password_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
ZStack(alignment: .trailing) {
Group {
if isSecured {
SecureField("account_settings_turn_password_title", text: Binding(
get: { accountSettingsViewModel.turnPassword },
set: { newValue in
accountSettingsViewModel.turnPassword = newValue
}
))
.default_text_style(styleSize: 15)
.frame(height: 25)
.focused($isTurnPasswordFocused)
} else {
TextField("account_settings_turn_password_title", text: Binding(
get: { accountSettingsViewModel.turnPassword },
set: { newValue in
accountSettingsViewModel.turnPassword = newValue
}
))
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.frame(height: 25)
.focused($isTurnPasswordFocused)
}
}
Button(action: {
isSecured.toggle()
}, label: {
Image(self.isSecured ? "eye-slash" : "eye")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20)
})
}
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isTurnPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.padding(.bottom)
}
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.zIndex(-1)
.transition(.move(edge: .top))
}
}
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, 30)
.padding(.bottom, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
.onTapGesture {
withAnimation {
advancedSettingsIsOpen.toggle()
}
}
if advancedSettingsIsOpen {
if accountSettingsViewModel.accountModel.avatarModel != nil {
VStack(spacing: 0) {
VStack(spacing: 30) {
VStack(alignment: .leading) {
Text("Transport")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
Menu {
Button("TLS") { accountSettingsViewModel.transport = "TLS" }
Button("TCP") { accountSettingsViewModel.transport = "TCP" }
Button("UDP") { accountSettingsViewModel.transport = "UDP" }
} label: {
Text(accountSettingsViewModel.transport)
.default_text_style(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .leading)
Image("caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20)
}
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(Color.gray200, lineWidth: 1)
)
}
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: Binding(
get: { accountSettingsViewModel.sipProxyUrl },
set: { newValue in
accountSettingsViewModel.sipProxyUrl = newValue
}
))
.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(isSipProxyUrlFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isSipProxyUrlFocused)
}
Toggle("account_settings_outbound_proxy_title", isOn: Binding(
get: { accountSettingsViewModel.outboundProxy },
set: { _ in
//accountProfileViewModel.toggleRegister()
}
))
.default_text_style_700(styleSize: 15)
Toggle("account_settings_avpf_title", isOn: Binding(
get: { accountSettingsViewModel.avpf },
set: { _ in
accountSettingsViewModel.avpf.toggle()
}
))
.default_text_style_700(styleSize: 15)
Toggle("account_settings_bundle_mode_title", isOn: Binding(
get: { accountSettingsViewModel.bundleMode },
set: { _ in
accountSettingsViewModel.bundleMode.toggle()
}
))
.default_text_style_700(styleSize: 15)
Toggle("account_settings_cpim_in_basic_conversations_title", isOn: Binding(
get: { accountSettingsViewModel.cpimInBasicConversations },
set: { _ in
accountSettingsViewModel.cpimInBasicConversations.toggle()
}
))
.default_text_style_700(styleSize: 15)
VStack(alignment: .leading) {
Text("account_settings_expire_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_expire_title", text: Binding(
get: { accountSettingsViewModel.expire },
set: { newValue in
accountSettingsViewModel.expire = newValue
}
))
.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(isSettingsExpireFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isSettingsExpireFocused)
}
VStack(alignment: .leading) {
Text("account_settings_conference_factory_uri_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_conference_factory_uri_title", text: Binding(
get: { accountSettingsViewModel.conferenceFactoryUri },
set: { newValue in
accountSettingsViewModel.conferenceFactoryUri = newValue
}
))
.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(isConferenceFactoryUriFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isConferenceFactoryUriFocused)
}
VStack(alignment: .leading) {
Text("account_settings_audio_video_conference_factory_uri_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_audio_video_conference_factory_uri_title", text: Binding(
get: { accountSettingsViewModel.audioVideoConferenceFactoryUri },
set: { newValue in
accountSettingsViewModel.audioVideoConferenceFactoryUri = newValue
}
))
.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(isAudioVideoConferenceFactoryUriFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isAudioVideoConferenceFactoryUriFocused)
}
VStack(alignment: .leading) {
Text("account_settings_ccmp_server_url_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_ccmp_server_url_title", text: Binding(
get: { accountSettingsViewModel.ccmpServerUrl },
set: { newValue in
accountSettingsViewModel.ccmpServerUrl = newValue
}
))
.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(isCcmpServerUrlFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isCcmpServerUrlFocused)
}
VStack(alignment: .leading) {
Text("account_settings_lime_server_url_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_lime_server_url_title", text: Binding(
get: { accountSettingsViewModel.limeServerUrl },
set: { newValue in
accountSettingsViewModel.limeServerUrl = newValue
}
))
.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(isLimeServerUrlFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isLimeServerUrlFocused)
}
/*
Button {
// TODO Update password
} label: {
Text("account_settings_update_password_title")
.default_text_style_700(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .leading)
}
*/
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.zIndex(-2)
.transition(.move(edge: .top))
}
}
}
.frame(maxWidth: sharedMainViewModel.maxWidth)
}
.frame(maxWidth: .infinity)
}
.background(Color.gray100)
}
.background(Color.gray100)
}
.navigationTitle("")
.navigationBarHidden(true)
}
}
// swiftlint:enable type_body_length

View file

@ -0,0 +1,221 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
class AccountSettingsViewModel: ObservableObject {
static let TAG = "[AccountSettingsViewModel]"
@Published var accountModel: AccountModel
@Published var pushNotification: Bool
@Published var imEncryptionMandatory: Bool
@Published var voicemailUri: String
@Published var mwiUri: String
@Published var stunServerUrl: String
@Published var enableIce: Bool
@Published var enableTurn: Bool
@Published var turnUsername: String
@Published var turnPassword: String
@Published var transport: String
@Published var sipProxyUrl: String
@Published var outboundProxy: Bool
@Published var avpf: Bool
@Published var bundleMode: Bool
@Published var cpimInBasicConversations: Bool
@Published var expire: String
@Published var conferenceFactoryUri: String
@Published var audioVideoConferenceFactoryUri: String
@Published var ccmpServerUrl: String
@Published var limeServerUrl: String
private var natPolicy: NatPolicy?
private var natPolicyAuthInfo: AuthInfo?
init(accountModel: AccountModel) {
self.accountModel = accountModel
self.pushNotification = accountModel.account.params?.pushNotificationAllowed ?? false
self.imEncryptionMandatory = accountModel.account.params?.instantMessagingEncryptionMandatory ?? false
self.voicemailUri = accountModel.account.params?.voicemailAddress?.asStringUriOnly() ?? ""
self.mwiUri = accountModel.account.params?.mwiServerAddress?.asStringUriOnly() ?? ""
self.natPolicy = accountModel.account.params?.natPolicy
self.stunServerUrl = accountModel.account.params?.natPolicy?.stunServer ?? ""
self.enableIce = accountModel.account.params?.natPolicy?.iceEnabled ?? false
self.enableTurn = accountModel.account.params?.natPolicy?.turnEnabled ?? false
let turnUsernameTmp = accountModel.account.params?.natPolicy?.stunServerUsername ?? ""
self.turnUsername = turnUsernameTmp
self.turnPassword = ""
let transportTmp = accountModel.account.params?.transport
if transportTmp == .Tls {
self.transport = "TLS"
} else if transportTmp == .Tcp {
self.transport = "TCP"
} else if transportTmp == .Udp {
self.transport = "UDP"
} else {
self.transport = "DTLS"
}
self.sipProxyUrl = accountModel.account.params?.serverAddress?.asStringUriOnly() ?? ""
self.outboundProxy = accountModel.account.params?.outboundProxyEnabled ?? false
self.avpf = accountModel.account.avpfEnabled
self.bundleMode = accountModel.account.params?.rtpBundleEnabled ?? false
self.cpimInBasicConversations = accountModel.account.params?.cpimInBasicChatRoomEnabled ?? false
self.expire = accountModel.account.params?.expires.description ?? ""
self.conferenceFactoryUri = accountModel.account.params?.conferenceFactoryAddress?.asStringUriOnly() ?? ""
self.audioVideoConferenceFactoryUri = accountModel.account.params?.audioVideoConferenceFactoryAddress?.asStringUriOnly() ?? ""
self.ccmpServerUrl = accountModel.account.params?.ccmpServerUrl ?? ""
self.limeServerUrl = accountModel.account.params?.limeServerUrl ?? ""
if !turnUsernameTmp.isEmpty {
CoreContext.shared.doOnCoreQueue { core in
let authInfo = core.findAuthInfo(realm: nil, username: turnUsernameTmp, sipDomain: nil)
if authInfo == nil {
Log.warn("\(AccountSettingsViewModel.TAG) TURN username not empty but unable to find matching auth info!")
} else {
self.natPolicyAuthInfo = authInfo!
DispatchQueue.main.async {
self.turnPassword = authInfo!.password ?? ""
}
}
if accountModel.account.params?.natPolicy == nil {
self.natPolicy = try? core.createNatPolicy()
}
}
}
}
func saveChanges() {
CoreContext.shared.doOnCoreQueue { core in
print("\(AccountSettingsViewModel.TAG) Saving changes...")
if let newParams = self.accountModel.account.params?.clone() {
newParams.pushNotificationAllowed = self.pushNotification
newParams.remotePushNotificationAllowed = self.pushNotification
newParams.instantMessagingEncryptionMandatory = self.imEncryptionMandatory
if !self.sipProxyUrl.isEmpty {
if let serverAddress = core.interpretUrl(url: self.sipProxyUrl, applyInternationalPrefix: false) {
var transportTmp: TransportType = .Tls
if self.transport == "TLS" {
transportTmp = .Tls
} else if self.transport == "TCP" {
transportTmp = .Tcp
} else if self.transport == "UDP" {
transportTmp = .Udp
} else {
transportTmp = .Dtls
}
try? serverAddress.setTransport(newValue: transportTmp)
try? newParams.setServeraddress(newValue: serverAddress)
}
}
newParams.outboundProxyEnabled = self.outboundProxy
if let natPolicy = self.natPolicy {
print("\(AccountSettingsViewModel.TAG) Also applying changes to NAT policy")
natPolicy.stunServer = self.stunServerUrl
natPolicy.stunEnabled = !self.stunServerUrl.isEmpty
natPolicy.iceEnabled = self.enableIce
natPolicy.turnEnabled = self.enableTurn
let stunTurnUsername = self.turnUsername
natPolicy.stunServerUsername = stunTurnUsername
newParams.natPolicy = natPolicy
if let natPolicyAuthInfo = self.natPolicyAuthInfo {
if stunTurnUsername.isEmpty {
print("\(AccountSettingsViewModel.TAG) NAT policy TURN username is now empty, removing existing auth info")
core.removeAuthInfo(info: natPolicyAuthInfo)
} else {
print("\(AccountSettingsViewModel.TAG) Found NAT policy auth info, updating it")
natPolicyAuthInfo.username = stunTurnUsername
natPolicyAuthInfo.password = self.turnPassword
}
} else if !stunTurnUsername.isEmpty {
print("\(AccountSettingsViewModel.TAG) No NAT policy auth info found, creating it with")
if let authInfo = try? Factory.Instance.createAuthInfo(
username: stunTurnUsername,
userid: nil,
passwd: self.turnPassword,
ha1: nil,
realm: nil,
domain: nil
) {
core.addAuthInfo(info: authInfo)
}
}
}
newParams.avpfMode = self.avpf ? .Enabled : .Disabled
newParams.rtpBundleEnabled = self.bundleMode
newParams.cpimInBasicChatRoomEnabled = self.cpimInBasicConversations
if !self.mwiUri.isEmpty {
newParams.mwiServerAddress = core.interpretUrl(url: self.mwiUri, applyInternationalPrefix: false)
} else {
newParams.mwiServerAddress = nil
}
if !self.voicemailUri.isEmpty {
newParams.voicemailAddress = core.interpretUrl(url: self.voicemailUri, applyInternationalPrefix: false)
} else {
newParams.voicemailAddress = nil
}
let expireInt: Int = {
if !self.expire.isEmpty {
return Int(self.expire) ?? 31536000
}
return 31536000
}()
newParams.expires = expireInt
if !self.conferenceFactoryUri.isEmpty {
newParams.conferenceFactoryAddress = core.interpretUrl(url: self.conferenceFactoryUri, applyInternationalPrefix: false)
} else {
newParams.conferenceFactoryAddress = nil
}
if !self.audioVideoConferenceFactoryUri.isEmpty {
newParams.audioVideoConferenceFactoryAddress = core.interpretUrl(url: self.audioVideoConferenceFactoryUri, applyInternationalPrefix: false)
} else {
newParams.audioVideoConferenceFactoryAddress = nil
}
newParams.ccmpServerUrl = self.ccmpServerUrl
newParams.limeServerUrl = self.limeServerUrl
self.accountModel.account.params = newParams
print("\(AccountSettingsViewModel.TAG) Changes have been saved")
}
}
}
}