Update Settings UI

This commit is contained in:
Benoit Martins 2026-03-12 16:18:42 +01:00
parent 0fe2b4370f
commit 2f56839937
13 changed files with 757 additions and 202 deletions

View file

@ -330,6 +330,15 @@ class CorePreferences {
}
}
var showDeveloperSettings: Bool {
get {
config.getBool(section: "ui", key: "show_developer_settings", defaultValue: false)
}
set {
config.setBool(section: "ui", key: "show_developer_settings", value: newValue)
}
}
var showDialogWhenCallingDeviceUuidDirectly: Bool {
get {
config.getBool(section: "app", key: "show_confirmation_dialog_zrtp_trust_call", defaultValue: true)

View file

@ -1,7 +1,7 @@
import Foundation
public enum AppGitInfo {
public static let branch = "master"
public static let commit = "fa9be23c2"
public static let branch = "feature/update_settings"
public static let commit = "5906c3449"
public static let tag = "6.1.0-alpha"
}

View file

@ -475,10 +475,8 @@
"qr_code_validated" = "QR code validated";
"recordings_title" = "Recordings";
"recordings_list_empty" = "No recording for the moment…";
"recordings_list_empty" = "No recording for the moment…";
"recordings_list_empty" = "No recording for the moment…";
"recordings_list_empty" = "No recording for the moment…";
"selected_participants_count" = "%@ selected participants";
"settings_advanced_early_media_title" = "Early-media";
"settings_advanced_accept_early_media_title" = "Accept early media";
"settings_advanced_allow_outgoing_early_media_title" = "Allow outgoing early media";
"settings_advanced_audio_codecs_title" = "Audio codecs";
@ -488,10 +486,13 @@
"settings_advanced_download_apply_remote_provisioning" = "Download & apply";
"settings_advanced_input_audio_device_title" = "Default input audio device";
"settings_advanced_media_encryption_mandatory_title" = "Media encryption mandatory";
"settings_advanced_create_e2e_encrypted_conferences_title" = "Create end-to-end encrypted meetings & group calls";
"settings_advanced_output_audio_device_title" = "Default output audio device";
"settings_advanced_remote_provisioning_url" = "Remote provisioning URL";
"settings_advanced_title" = "Advanced settings";
"settings_advanced_upload_server_url" = "File sharing server URL";
"settings_advanced_logs_upload_server_url" = "Logs sharing server URL";
"settings_advanced_calls" = "Advanced calls settings";
"settings_advanced_video_codecs_title" = "Video codecs";
"settings_calls_adaptive_rate_control_title" = "Adaptive rate control";
"settings_calls_auto_record_title" = "Automatically start recording calls";
@ -542,6 +543,21 @@
"settings_conversations_hide_message_content_in_notif_title" = "Do not show message content in iOS notification";
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Mark conversation as read when dismissing message notification";
"settings_conversations_title" = "Conversations";
"settings_developer_title" = "Developer settings";
"settings_developer_show_title" = "Show developer settings";
"settings_developer_two_more_clicks_required_toast" = "Click 2 more times to enable developer settings";
"settings_developer_one_more_click_required_toast" = "Click 1 more time to enable developer settings";
"settings_developer_enabled_toast" = "Developer settings enabled";
"settings_developer_already_enabled_toast" = "Developer settings already enabled";
"settings_developer_enable_vu_meters_title" = "Enable record/playback volume vu meters while in call";
"settings_developer_enable_advanced_call_stats_title" = "Show advanced call statistics";
"settings_developer_push_compatible_domains_list_title" = "List of push notifications compatible domains (comma separated)";
"settings_developer_clear_native_friends_in_database_title" = "Clear imported contacts from native address book";
"settings_developer_clear_native_friends_in_database_subtitle" = "They will be imported again the next time the app starts unless you remove the contacts permission";
"settings_developer_cleared_native_friends_in_database_toast" = "Imported contacts have been deleted";
"settings_developer_clear_orphan_auth_info_title" = "Clear authentication info no longer associated to any account";
"settings_developer_no_auth_info_removed_toast" = "No orphan authentication info found";
"settings_developer_cleared_auth_info_toast" = "Orphaned authentication info removed";
"settings_meetings_default_layout_title" = "Default layout";
"settings_meetings_layout_active_speaker_label" = "Active speaker";
"settings_meetings_layout_mosaic_label" = "Mosaic";

View file

@ -475,6 +475,7 @@
"recordings_title" = "Enregistrements";
"recordings_list_empty" = "Aucun appel enregistré…";
"selected_participants_count" = "%@ participants selectionnés";
"settings_advanced_early_media_title" = "Early media";
"settings_advanced_accept_early_media_title" = "Accepter l'early media";
"settings_advanced_allow_outgoing_early_media_title" = "Autoriser l'early media pour les appels sortants";
"settings_advanced_audio_codecs_title" = "Codecs audio";
@ -484,10 +485,13 @@
"settings_advanced_download_apply_remote_provisioning" = "Télécharger & appliquer";
"settings_advanced_input_audio_device_title" = "Périphérique de capture par défaut";
"settings_advanced_media_encryption_mandatory_title" = "Rendre le chiffrement du média obligatoire";
"settings_advanced_create_e2e_encrypted_conferences_title" = "Créer en mode chiffré de bout en bout les réunions et les appels de groupe";
"settings_advanced_output_audio_device_title" = "Périphérique d'écoute par défaut";
"settings_advanced_remote_provisioning_url" = "URL de configuration distante";
"settings_advanced_title" = "Paramètres avancés";
"settings_advanced_upload_server_url" = "URL du serveur de partage de fichier";
"settings_advanced_logs_upload_server_url" = "URL du serveur de partage des logs";
"settings_advanced_calls" = "Paramètres d'appels avancés";
"settings_advanced_video_codecs_title" = "Codecs vidéo";
"settings_calls_adaptive_rate_control_title" = "Contrôle automatique de la qualité";
"settings_calls_auto_record_title" = "Enregistrement automatique des appels";
@ -538,6 +542,22 @@
"settings_conversations_hide_message_content_in_notif_title" = "Masquer le contenu du message dans la notification iOS";
"settings_conversations_mark_as_read_when_dismissing_notif_title" = "Marquer la conversation comme lue lorsqu'une notification de message est supprimée";
"settings_conversations_title" = "Conversations";
"settings_developer_title" = "Paramètres développeurs";
"settings_developer_show_title" = "Afficher les paramètres développeurs";
"settings_developer_two_more_clicks_required_toast" = "Encore 2 clicks pour activer les paramètres développeurs";
"settings_developer_one_more_click_required_toast" = "Encore 1 click pour activer les paramètres développeurs";
"settings_developer_enabled_toast" = "Paramètres développeurs activés";
"settings_developer_already_enabled_toast" = "Paramètres développeurs déjà activés";
"settings_developer_enable_vu_meters_title" = "Activer l'indicateur des volumes d'enregistrement et de lecture";
"settings_developer_enable_advanced_call_stats_title" = "Afficher plus de statistiques d'appel";
"settings_developer_push_compatible_domains_list_title" = "Liste des domaines qui supportent les notifications poussées (séparés par des virgules)";
"settings_developer_clear_native_friends_in_database_title" = "Supprimer les contacts natifs importés";
"settings_developer_clear_native_friends_in_database_subtitle" = "Ils seront synchronisés à nouveau au prochain démarrage de l'application sauf si vous retirez la permission de lire les contacts";
"settings_developer_cleared_native_friends_in_database_toast" = "Contacts importés supprimés";
"settings_developer_clear_orphan_auth_info_title" = "Supprimer les informations d'authentification orphelines";
"settings_developer_no_auth_info_removed_toast" = "Aucune information d'authentification orpheline trouvée";
"settings_developer_cleared_auth_info_toast_single" = "%@ information d'authentification supprimée";
"settings_developer_cleared_auth_info_toast_multiple" = "%@ informations d'authentification supprimées";
"settings_meetings_default_layout_title" = "Disposition par défaut";
"settings_meetings_layout_active_speaker_label" = "Intervenant actif";
"settings_meetings_layout_mosaic_label" = "Mosaïque";

View file

@ -198,6 +198,55 @@ struct ToastView: View {
.default_text_style(styleSize: 15)
.padding(8)
case "Success_cleared_native_friends_toast":
Text("settings_developer_cleared_native_friends_in_database_toast")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Success_no_auth_info_removed_toast":
Text("settings_developer_no_auth_info_removed_toast")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Success_cleared_auth_info_toast":
Text("settings_developer_cleared_auth_info_toast")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Success_two_more_clicks_toast":
Text("settings_developer_two_more_clicks_required_toast")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Success_one_more_click_toast":
Text("settings_developer_one_more_click_required_toast")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Success_developer_enabled_toast":
Text("settings_developer_enabled_toast")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Success_developer_already_enabled_toast":
Text("settings_developer_already_enabled_toast")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Failed_toast_call_transfer_failed":
Text("call_transfer_failed_toast")
.multilineTextAlignment(.center)

View file

@ -25,6 +25,8 @@ struct HelpFragment: View {
@Binding var isShowHelpFragment: Bool
@State var clickCounter: Int = 0
var showAssistant: Bool {
(CoreContext.shared.coreIsStarted && CoreContext.shared.accounts.isEmpty)
|| SharedMainViewModel.shared.displayProfileMode
@ -198,6 +200,29 @@ struct HelpFragment: View {
.background(Color.orangeMain100)
.cornerRadius(60)
}
.background(Color.gray100)
.onTapGesture {
if !AppServices.corePreferences.showDeveloperSettings {
clickCounter += 1
switch clickCounter {
case 1:
ToastViewModel.shared.show("Success_two_more_clicks_toast")
case 2:
ToastViewModel.shared.show("Success_one_more_click_toast")
case 3:
AppServices.corePreferences.showDeveloperSettings = true
ToastViewModel.shared.show("Success_developer_enabled_toast")
default:
ToastViewModel.shared.show("Success_developer_already_enabled_toast")
}
} else {
ToastViewModel.shared.show("Success_developer_already_enabled_toast")
}
}
Button {
if let url = URL(string: NSLocalizedString("website_open_source_licences_usage_url", comment: "")) {

View file

@ -172,19 +172,5 @@ class HelpViewModel: ObservableObject {
}
}
}
func clearNativeFriendsDatabase() {
CoreContext.shared.doOnCoreQueue { core in
if let list = core.getFriendListByName(NATIVE_ADDRESS_BOOK_FRIEND_LIST) {
let friends = list.friends
Log.i("\(self.TAG) Friend list to remove found with [\(friends.count)] friends")
for friend in friends {
list.removeFriend(friend)
}
core.removeFriendList(list)
Log.i("\(self.TAG) Friend list [\(NATIVE_ADDRESS_BOOK_FRIEND_LIST)] removed")
}
}
}
*/
}

View file

@ -0,0 +1,274 @@
/*
* 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
import UniformTypeIdentifiers
struct SettingsAdvancedCallFragment: View {
@ObservedObject var settingsViewModel: SettingsViewModel
@Environment(\.dismiss) var dismiss
@State var earlyMediaIsOpen: Bool = false
@State var audioCodecsIsOpen: Bool = false
@State var videoCodecsIsOpen: Bool = false
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 {
dismiss()
}
Text("settings_advanced_calls")
.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: 30) {
Toggle("settings_calls_enable_fec_title", isOn: $settingsViewModel.enableFec)
.default_text_style_700(styleSize: 15)
VStack(alignment: .leading) {
Text("call_stats_media_encryption_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
Menu {
Button("None") {
settingsViewModel.mediaEncryption = "None"
settingsViewModel.mediaEncryptionMandatory = false
}
Button("SRTP") {
settingsViewModel.mediaEncryption = "SRTP"
settingsViewModel.mediaEncryptionMandatory = true
}
Button("ZRTP") {
settingsViewModel.mediaEncryption = "ZRTP"
settingsViewModel.mediaEncryptionMandatory = true
}
Button("DTLS") {
settingsViewModel.mediaEncryption = "DTLS"
settingsViewModel.mediaEncryptionMandatory = true
}
} label: {
Text(settingsViewModel.mediaEncryption)
.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)
)
}
Toggle("settings_advanced_media_encryption_mandatory_title", isOn: $settingsViewModel.mediaEncryptionMandatory)
.default_text_style_700(styleSize: 15)
}
.padding(.vertical, 20)
.padding(.horizontal, 20)
.background(.white)
.cornerRadius(15)
.background(Color.gray100)
}
.padding(.vertical, 20)
.padding(.horizontal, 20)
.background(Color.gray100)
HStack(alignment: .center) {
Text("settings_advanced_early_media_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image(earlyMediaIsOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.padding(.vertical, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
.onTapGesture {
withAnimation {
earlyMediaIsOpen.toggle()
}
}
if earlyMediaIsOpen {
VStack(spacing: 0) {
VStack(spacing: 30) {
Toggle("settings_advanced_accept_early_media_title", isOn: $settingsViewModel.acceptEarlyMedia)
.default_text_style_700(styleSize: 15)
Toggle("settings_advanced_allow_outgoing_early_media_title", isOn: $settingsViewModel.allowOutgoingEarlyMedia)
.default_text_style_700(styleSize: 15)
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal, 20)
.zIndex(-1)
.transition(.move(edge: .top))
.background(Color.gray100)
}
HStack(alignment: .center) {
Text("settings_advanced_audio_codecs_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image(audioCodecsIsOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.padding(.vertical, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
.onTapGesture {
withAnimation {
audioCodecsIsOpen.toggle()
}
}
if audioCodecsIsOpen {
VStack(spacing: 0) {
VStack(spacing: 30) {
ForEach(settingsViewModel.audioCodecs) { audioCodec in
SettingsToggleWidget(title: audioCodec.mimeType, subtitle: audioCodec.subtitle, isOn: Binding(
get: { audioCodec.isEnabled },
set: { newValue in
audioCodec.toggleEnabled()
}
))
}
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal, 20)
.zIndex(-2)
.transition(.move(edge: .top))
.background(Color.gray100)
}
HStack(alignment: .center) {
Text("settings_advanced_video_codecs_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image(videoCodecsIsOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.padding(.vertical, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
.onTapGesture {
withAnimation {
videoCodecsIsOpen.toggle()
}
}
if videoCodecsIsOpen {
VStack(spacing: 0) {
VStack(spacing: 30) {
ForEach(settingsViewModel.videoCodecs) { videoCodec in
SettingsToggleWidget(title: videoCodec.mimeType, subtitle: videoCodec.subtitle, isOn: Binding(
get: { videoCodec.isEnabled },
set: { newValue in
videoCodec.toggleEnabled()
}
))
}
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal, 20)
.zIndex(-3)
.transition(.move(edge: .top))
.background(Color.gray100)
}
}
.background(Color.gray100)
}
.background(Color.gray100)
}
.background(Color.gray100)
}
.navigationTitle("")
.navigationBarHidden(true)
}
}

View file

@ -26,11 +26,8 @@ struct SettingsAdvancedFragment: View {
@Environment(\.dismiss) var dismiss
@State var audioDevicesIsOpen: Bool = false
@State var audioCodecsIsOpen: Bool = false
@State var videoCodecsIsOpen: Bool = false
@FocusState var isDeviceIdFocused: Bool
@FocusState var isUploadServerUrlFocused: Bool
@FocusState var isRemoteProvisioningUrlFocused: Bool
var body: some View {
@ -54,7 +51,7 @@ struct SettingsAdvancedFragment: View {
dismiss()
}
Text("settings_title")
Text("settings_advanced_title")
.default_text_style_orange_800(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 4)
@ -71,61 +68,6 @@ struct SettingsAdvancedFragment: View {
ScrollView {
VStack(spacing: 0) {
VStack(spacing: 30) {
Toggle("settings_calls_enable_fec_title", isOn: $settingsViewModel.enableFec)
.default_text_style_700(styleSize: 15)
VStack(alignment: .leading) {
Text("call_stats_media_encryption_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
Menu {
Button("None") {
settingsViewModel.mediaEncryption = "None"
settingsViewModel.mediaEncryptionMandatory = false
}
Button("SRTP") {
settingsViewModel.mediaEncryption = "SRTP"
settingsViewModel.mediaEncryptionMandatory = true
}
Button("ZRTP") {
settingsViewModel.mediaEncryption = "ZRTP"
settingsViewModel.mediaEncryptionMandatory = true
}
Button("DTLS") {
settingsViewModel.mediaEncryption = "DTLS"
settingsViewModel.mediaEncryptionMandatory = true
}
} label: {
Text(settingsViewModel.mediaEncryption)
.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)
)
}
Toggle("settings_advanced_media_encryption_mandatory_title", isOn: $settingsViewModel.mediaEncryptionMandatory)
.default_text_style_700(styleSize: 15)
Toggle("settings_advanced_accept_early_media_title", isOn: $settingsViewModel.acceptEarlyMedia)
.default_text_style_700(styleSize: 15)
Toggle("settings_advanced_allow_outgoing_early_media_title", isOn: $settingsViewModel.allowOutgoingEarlyMedia)
.default_text_style_700(styleSize: 15)
VStack(alignment: .leading) {
Text("settings_advanced_device_id")
.default_text_style_700(styleSize: 15)
@ -145,25 +87,6 @@ struct SettingsAdvancedFragment: View {
.focused($isDeviceIdFocused)
}
VStack(alignment: .leading) {
Text("settings_advanced_upload_server_url")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("settings_advanced_upload_server_url", text: $settingsViewModel.uploadServerUrl)
.default_text_style(styleSize: 15)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isUploadServerUrlFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isUploadServerUrlFocused)
}
VStack(alignment: .leading) {
Text("settings_advanced_remote_provisioning_url")
.default_text_style_700(styleSize: 15)
@ -201,8 +124,10 @@ struct SettingsAdvancedFragment: View {
.disabled(settingsViewModel.remoteProvisioningUrl.isEmpty)
}
}
.padding(.vertical, 30)
.padding(.vertical, 20)
.padding(.horizontal, 20)
.background(.white)
.cornerRadius(15)
.background(Color.gray100)
/*
@ -307,102 +232,15 @@ struct SettingsAdvancedFragment: View {
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.horizontal, 20)
.zIndex(-1)
.transition(.move(edge: .top))
.background(Color.gray100)
}
*/
HStack(alignment: .center) {
Text("settings_advanced_audio_codecs_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image(audioCodecsIsOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.padding(.vertical, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
.onTapGesture {
withAnimation {
audioCodecsIsOpen.toggle()
}
}
if audioCodecsIsOpen {
VStack(spacing: 0) {
VStack(spacing: 30) {
ForEach(settingsViewModel.audioCodecs) { audioCodec in
SettingsToggleWidget(title: audioCodec.mimeType, subtitle: audioCodec.subtitle, isOn: Binding(
get: { audioCodec.isEnabled },
set: { newValue in
audioCodec.toggleEnabled()
}
))
}
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.zIndex(-2)
.transition(.move(edge: .top))
}
HStack(alignment: .center) {
Text("settings_advanced_video_codecs_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image(videoCodecsIsOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.padding(.vertical, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
.onTapGesture {
withAnimation {
videoCodecsIsOpen.toggle()
}
}
if videoCodecsIsOpen {
VStack(spacing: 0) {
VStack(spacing: 30) {
ForEach(settingsViewModel.videoCodecs) { videoCodec in
SettingsToggleWidget(title: videoCodec.mimeType, subtitle: videoCodec.subtitle, isOn: Binding(
get: { videoCodec.isEnabled },
set: { newValue in
videoCodec.toggleEnabled()
}
))
}
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.zIndex(-3)
.transition(.move(edge: .top))
}
}
.padding(.vertical, 20)
.padding(.horizontal, 20)
}
.background(Color.gray100)
}

View file

@ -0,0 +1,196 @@
/*
* 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
import UniformTypeIdentifiers
struct SettingsDeveloperFragment: View {
@ObservedObject var settingsViewModel: SettingsViewModel
@Environment(\.dismiss) var dismiss
@FocusState var isUploadServerUrlFocused: Bool
@FocusState var isLogsUploadServerUrlFocused: Bool
@FocusState var isPushCompatibleDomainsListFocused: Bool
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 {
dismiss()
}
Text("settings_developer_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: 30) {
Toggle("settings_developer_show_title", isOn: $settingsViewModel.showDeveloperSettings)
.default_text_style_700(styleSize: 15)
Toggle("help_troubleshooting_print_logs_in_logcat", isOn: $settingsViewModel.printLogsInLogcat)
.default_text_style_700(styleSize: 15)
VStack(alignment: .leading) {
Text("settings_advanced_upload_server_url")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("settings_advanced_upload_server_url", text: $settingsViewModel.uploadServerUrl)
.default_text_style(styleSize: 15)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isUploadServerUrlFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isUploadServerUrlFocused)
}
VStack(alignment: .leading) {
Text("settings_advanced_logs_upload_server_url")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("settings_advanced_logs_upload_server_url", text: $settingsViewModel.logsUploadServerUrl)
.default_text_style(styleSize: 15)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isLogsUploadServerUrlFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isLogsUploadServerUrlFocused)
}
// TODO: Add these settings
/*
Toggle("settings_advanced_create_e2e_encrypted_conferences_title", isOn: $settingsViewModel.???)
.default_text_style_700(styleSize: 15)
Toggle("settings_developer_enable_vu_meters_title", isOn: $settingsViewModel.???)
.default_text_style_700(styleSize: 15)
Toggle("settings_developer_enable_advanced_call_stats_title", isOn: $settingsViewModel.???)
.default_text_style_700(styleSize: 15)
VStack(alignment: .leading) {
Text("settings_developer_push_compatible_domains_list_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("settings_developer_push_compatible_domains_list_title", text: $settingsViewModel.???)
.default_text_style(styleSize: 15)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isPushCompatibleDomainsListFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isPushCompatibleDomainsListFocused)
}
*/
VStack(alignment: .leading) {
Button(
action: {
settingsViewModel.clearNativeFriendsDatabase()
}, label: {
Text("settings_developer_clear_native_friends_in_database_title")
.default_text_style_white_600(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .center)
}
)
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background(Color.redDanger500)
.cornerRadius(60)
Text("settings_developer_clear_native_friends_in_database_subtitle")
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
}
Button(
action: {
settingsViewModel.clearOrphanAuthInfo()
}, label: {
Text("settings_developer_clear_orphan_auth_info_title")
.default_text_style_white_600(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .center)
}
)
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background(Color.redDanger500)
.cornerRadius(60)
}
.padding(.vertical, 20)
.padding(.horizontal, 20)
.background(.white)
.cornerRadius(15)
.background(Color.gray100)
}
.padding(.vertical, 20)
.padding(.horizontal, 20)
}
.background(Color.gray100)
}
.background(Color.gray100)
}
.navigationTitle("")
.navigationBarHidden(true)
}
}

View file

@ -132,7 +132,7 @@ struct SettingsFragment: View {
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.horizontal, 20)
.zIndex(-1)
.transition(.move(edge: .top))
}
@ -195,15 +195,36 @@ struct SettingsFragment: View {
}
}
*/
NavigationLink(destination: {
SettingsAdvancedCallFragment(settingsViewModel: settingsViewModel)
}, label: {
HStack(alignment: .center) {
Text("settings_advanced_calls")
.default_text_style_700(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image("caret-right")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
}
.frame(maxWidth: .infinity)
})
}
.padding(.vertical, 30)
.padding(.horizontal, 20)
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.horizontal, 20)
.zIndex(-2)
.transition(.move(edge: .top))
.background(Color.gray100)
}
HStack(alignment: .center) {
@ -243,9 +264,10 @@ struct SettingsFragment: View {
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.horizontal, 20)
.zIndex(-3)
.transition(.move(edge: .top))
.background(Color.gray100)
}
HStack(alignment: .center) {
@ -373,9 +395,10 @@ struct SettingsFragment: View {
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.horizontal, 20)
.zIndex(-4)
.transition(.move(edge: .top))
.background(Color.gray100)
}
HStack(alignment: .center) {
@ -438,9 +461,10 @@ struct SettingsFragment: View {
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.horizontal, 20)
.zIndex(-5)
.transition(.move(edge: .top))
.background(Color.gray100)
}
HStack(alignment: .center) {
@ -480,9 +504,10 @@ struct SettingsFragment: View {
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.horizontal, 20)
.zIndex(-6)
.transition(.move(edge: .top))
.background(Color.gray100)
}
/*
@ -525,11 +550,13 @@ struct SettingsFragment: View {
}
.background(.white)
.cornerRadius(15)
.padding(.horizontal)
.padding(.horizontal, 20)
.zIndex(-7)
.transition(.move(edge: .top))
.background(Color.gray100)
}
*/
NavigationLink(destination: {
SettingsAdvancedFragment(settingsViewModel: settingsViewModel)
}, label: {
@ -553,6 +580,32 @@ struct SettingsFragment: View {
.padding(.vertical, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
if AppServices.corePreferences.showDeveloperSettings {
NavigationLink(destination: {
SettingsDeveloperFragment(settingsViewModel: settingsViewModel)
}, label: {
HStack(alignment: .center) {
Text("settings_developer_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image("caret-right")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.frame(maxWidth: .infinity)
})
.padding(.vertical, 10)
.padding(.horizontal, 20)
.background(Color.gray100)
}
}
}
.background(Color.gray100)

View file

@ -57,7 +57,6 @@ class SettingsViewModel: ObservableObject {
@Published var acceptEarlyMedia: Bool = false
@Published var allowOutgoingEarlyMedia: Bool = false
@Published var deviceId: String = ""
@Published var uploadServerUrl: String = ""
@Published var remoteProvisioningUrl: String = ""
@Published var inputAudioDeviceLabels: [String] = []
@ -71,6 +70,13 @@ class SettingsViewModel: ObservableObject {
@Published var audioCodecs: [CodecModel] = []
@Published var videoCodecs: [CodecModel] = []
// Developer settings
@Published var showDeveloperSettings: Bool = false
@Published var printLogsInLogcat: Bool = false
@Published var uploadServerUrl: String = ""
@Published var logsUploadServerUrl: String = ""
init() {
CoreContext.shared.doOnCoreQueue { core in
@ -105,9 +111,14 @@ class SettingsViewModel: ObservableObject {
let acceptEarlyMediaTmp = AppServices.corePreferences.acceptEarlyMedia
let allowOutgoingEarlyMediaTmp = AppServices.corePreferences.allowOutgoingEarlyMedia
let deviceIdTmp = AppServices.corePreferences.deviceName
let fileSharingServerUrlTmp = core.fileTransferServer
let remoteProvisioningUrlTmp = core.provisioningUri
// Developer settings
let showDeveloperSettingsTmp = AppServices.corePreferences.showDeveloperSettings
let printLogsInLogcatTmp = AppServices.corePreferences.printLogsInLogcat
let fileSharingServerUrlTmp = core.fileTransferServer
let logsTransferServerTmp = core.logCollectionUploadServerUrl
DispatchQueue.main.async {
self.enableVfs = enableVfsTmp
@ -131,9 +142,14 @@ class SettingsViewModel: ObservableObject {
self.allowOutgoingEarlyMedia = allowOutgoingEarlyMediaTmp
self.deviceId = deviceIdTmp
self.uploadServerUrl = fileSharingServerUrlTmp ?? ""
self.remoteProvisioningUrl = remoteProvisioningUrlTmp ?? ""
// Developer settings
self.showDeveloperSettings = showDeveloperSettingsTmp
self.printLogsInLogcat = printLogsInLogcatTmp
self.uploadServerUrl = fileSharingServerUrlTmp ?? ""
self.logsUploadServerUrl = logsTransferServerTmp ?? ""
/*
self.setupAudioDevices()
@ -421,9 +437,74 @@ class SettingsViewModel: ObservableObject {
AppServices.corePreferences.deviceName = self.deviceId
}
// Developer settings
if AppServices.corePreferences.showDeveloperSettings != self.showDeveloperSettings {
AppServices.corePreferences.showDeveloperSettings = self.showDeveloperSettings
}
if AppServices.corePreferences.printLogsInLogcat != self.printLogsInLogcat {
AppServices.corePreferences.printLogsInLogcat = self.printLogsInLogcat
}
if core.fileTransferServer != self.uploadServerUrl && !(core.fileTransferServer == nil && self.uploadServerUrl.isEmpty) {
core.fileTransferServer = self.uploadServerUrl
}
if core.logCollectionUploadServerUrl != self.logsUploadServerUrl && !(core.logCollectionUploadServerUrl == nil && self.logsUploadServerUrl.isEmpty) {
core.logCollectionUploadServerUrl = self.logsUploadServerUrl
}
}
}
func clearNativeFriendsDatabase() {
CoreContext.shared.doOnCoreQueue { core in
let nativeAddressBookFriendList = "Native address-book"
if let list = core.getFriendListByName(name: nativeAddressBookFriendList) {
let friends = list.friends
Log.info("\(SettingsViewModel.TAG) Friend list to remove found with \(friends.count) friends")
for friend in friends {
_ = list.removeFriend(linphoneFriend: friend)
}
core.removeFriendList(list: list)
Log.info("\(SettingsViewModel.TAG) Friend list \(nativeAddressBookFriendList) removed")
}
DispatchQueue.main.async {
ToastViewModel.shared.show("Success_cleared_native_friends_toast")
}
}
}
func clearOrphanAuthInfo() {
CoreContext.shared.doOnCoreQueue { core in
var count = 0
for authInfo in core.authInfoList {
if let username = authInfo.username {
let account = core.accountList.first {
$0.params?.identityAddress?.username == username
}
if account == nil {
Log.info("\(SettingsViewModel.TAG) Removing auth info \(authInfo) with username \(username) for which no account was found")
core.removeAuthInfo(info: authInfo)
count += 1
}
} else {
Log.info("\(SettingsViewModel.TAG) Removing auth info \(authInfo) without username")
core.removeAuthInfo(info: authInfo)
count += 1
}
}
if count == 0 {
DispatchQueue.main.async {
ToastViewModel.shared.show("Success_no_auth_info_removed_toast")
}
} else {
DispatchQueue.main.async {
ToastViewModel.shared.show("Success_cleared_auth_info_toast")
}
}
}
}
}

View file

@ -205,6 +205,8 @@
D7D5AD872DD34F3C00016721 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = D7D5AD862DD34F3C00016721 /* FirebaseCrashlytics */; };
D7D67B6A2F601F6100DD9976 /* AppIntentVocabulary.plist in Resources */ = {isa = PBXBuildFile; fileRef = D7D67B692F601F6100DD9976 /* AppIntentVocabulary.plist */; };
D7D67B6B2F601F6100DD9976 /* AppIntentVocabulary.plist in Resources */ = {isa = PBXBuildFile; fileRef = D7D67B692F601F6100DD9976 /* AppIntentVocabulary.plist */; };
D7D67B8B2F62B0DA00DD9976 /* SettingsAdvancedCallFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D67B8A2F62B07F00DD9976 /* SettingsAdvancedCallFragment.swift */; };
D7D67B8D2F62B0F100DD9976 /* SettingsDeveloperFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D67B8C2F62B0EF00DD9976 /* SettingsDeveloperFragment.swift */; };
D7DA67622ACCB2FA00E95002 /* LoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DA67612ACCB2FA00E95002 /* LoginFragment.swift */; };
D7DA67642ACCB31700E95002 /* ProfileModeFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DA67632ACCB31700E95002 /* ProfileModeFragment.swift */; };
D7DC096F2CFA1D7600A6D47C /* AccountProfileFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DC096E2CFA1D7400A6D47C /* AccountProfileFragment.swift */; };
@ -487,6 +489,8 @@
D7D67B7A2F601F7400DD9976 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = sk; path = sk.lproj/AppIntentVocabulary.plist; sourceTree = "<group>"; };
D7D67B7B2F601F7400DD9976 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = es; path = es.lproj/AppIntentVocabulary.plist; sourceTree = "<group>"; };
D7D67B7C2F601F7500DD9976 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = uk; path = uk.lproj/AppIntentVocabulary.plist; sourceTree = "<group>"; };
D7D67B8A2F62B07F00DD9976 /* SettingsAdvancedCallFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAdvancedCallFragment.swift; sourceTree = "<group>"; };
D7D67B8C2F62B0EF00DD9976 /* SettingsDeveloperFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDeveloperFragment.swift; sourceTree = "<group>"; };
D7DA67612ACCB2FA00E95002 /* LoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginFragment.swift; sourceTree = "<group>"; };
D7DA67632ACCB31700E95002 /* ProfileModeFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModeFragment.swift; sourceTree = "<group>"; };
D7DC096E2CFA1D7400A6D47C /* AccountProfileFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountProfileFragment.swift; sourceTree = "<group>"; };
@ -1191,12 +1195,14 @@
D7DC096B2CFA192F00A6D47C /* Fragments */ = {
isa = PBXGroup;
children = (
D7DC096E2CFA1D7400A6D47C /* AccountProfileFragment.swift */,
D7C5003F2D27F16900DD53EC /* AccountSettingsFragment.swift */,
D762102B2E97FDF8002E7999 /* CardDavAddressBookConfigurationFragment.swift */,
D711B1332E93F18300DF8C71 /* LdapServerConfigurationFragment.swift */,
D78607702D36CB87009E6A7E /* SettingsAdvancedFragment.swift */,
D732C38B2D311D2100F78100 /* SettingsFragment.swift */,
D7C5003F2D27F16900DD53EC /* AccountSettingsFragment.swift */,
D7DC096E2CFA1D7400A6D47C /* AccountProfileFragment.swift */,
D78607702D36CB87009E6A7E /* SettingsAdvancedFragment.swift */,
D7D67B8C2F62B0EF00DD9976 /* SettingsDeveloperFragment.swift */,
D7D67B8A2F62B07F00DD9976 /* SettingsAdvancedCallFragment.swift */,
);
path = Fragments;
sourceTree = "<group>";
@ -1508,6 +1514,7 @@
D71A0E192B485ADF0002C6CD /* ViewExtension.swift in Sources */,
D759CB642C3FBD4200AC35E8 /* StartConversationFragment.swift in Sources */,
D7DF8BE92E2104EC003A3BC7 /* EmojiPickerView.swift in Sources */,
D7D67B8B2F62B0DA00DD9976 /* SettingsAdvancedCallFragment.swift in Sources */,
66FDB7812C7C689A00561566 /* EventEditViewController.swift in Sources */,
D750D3392AD3E6EE00EC99C5 /* PopupLoadingView.swift in Sources */,
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */,
@ -1593,6 +1600,7 @@
D79F2D0A2C47F4BF0038FA07 /* TouchFeedback.swift in Sources */,
D78E06282BE3811D00CE3783 /* CallStatsModel.swift in Sources */,
D7E6D0512AEBDBD500A57AAF /* ContactsListBottomSheet.swift in Sources */,
D7D67B8D2F62B0F100DD9976 /* SettingsDeveloperFragment.swift in Sources */,
D75759322B56D40900E7AC10 /* ZRTPPopup.swift in Sources */,
D78E062E2BEA69F400CE3783 /* AudioRouteBottomSheet.swift in Sources */,
D7A0ACBB2C415D630043AE79 /* StartGroupConversationFragment.swift in Sources */,