Added Tunnel settings

This commit is contained in:
Sylvain Berfini 2024-05-20 16:01:43 +02:00
parent 26e1332421
commit 76716503e9
6 changed files with 409 additions and 15 deletions

View file

@ -91,6 +91,15 @@ class SettingsFragment : GenericMainFragment() {
}
}
private val tunnelModeListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
viewModel.tunnelModeIndex.value = position
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -212,6 +221,20 @@ class SettingsFragment : GenericMainFragment() {
binding.userInterfaceSettings.colorSpinner.onItemSelectedListener = colorListener
}
// Tunnel mode
val tunnelModeAdapter = ArrayAdapter(
requireContext(),
R.layout.drop_down_item,
viewModel.tunnelModeLabels
)
tunnelModeAdapter.setDropDownViewResource(R.layout.generic_dropdown_cell)
binding.tunnelSettings.tunnelModeSpinner.adapter = tunnelModeAdapter
binding.tunnelSettings.tunnelModeSpinner.onItemSelectedListener = tunnelModeListener
viewModel.tunnelModeIndex.observe(viewLifecycleOwner) { index ->
binding.tunnelSettings.tunnelModeSpinner.setSelection(index)
}
startPostponedEnterTransition()
}
@ -221,4 +244,12 @@ class SettingsFragment : GenericMainFragment() {
viewModel.reloadLdapServers()
viewModel.reloadConfiguredCardDavServers()
}
override fun onPause() {
if (viewModel.isTunnelAvailable.value == true) {
viewModel.saveTunnelConfig()
}
super.onPause()
}
}

View file

@ -30,7 +30,9 @@ import org.linphone.core.AudioDevice
import org.linphone.core.Conference
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.Factory
import org.linphone.core.FriendList
import org.linphone.core.Tunnel
import org.linphone.core.VFS
import org.linphone.core.tools.Log
import org.linphone.ui.GenericViewModel
@ -51,6 +53,8 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() {
val expandMeetings = MutableLiveData<Boolean>()
val expandNetwork = MutableLiveData<Boolean>()
val expandUserInterface = MutableLiveData<Boolean>()
val expandTunnel = MutableLiveData<Boolean>()
val isTunnelAvailable = MutableLiveData<Boolean>()
val recreateActivityEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
@ -148,6 +152,19 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() {
"purple"
)
// Tunnel settings
val tunnelMainHost = MutableLiveData<String>()
val tunnelMainPort = MutableLiveData<String>()
val tunnelDualMode = MutableLiveData<Boolean>()
val tunnelDualHost = MutableLiveData<String>()
val tunnelDualPort = MutableLiveData<String>()
val tunnelModeIndex = MutableLiveData<Int>()
val tunnelModeLabels = arrayListOf(
AppUtils.getString(R.string.settings_tunnel_mode_disabled_label),
AppUtils.getString(R.string.settings_tunnel_mode_always_label),
AppUtils.getString(R.string.settings_tunnel_mode_auto_label)
)
// Advanced settings
val keepAliveThirdPartyAccountsService = MutableLiveData<Boolean>()
@ -180,6 +197,7 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() {
coreContext.postOnCoreThread { core ->
core.addListener(coreListener)
isTunnelAvailable.postValue(core.tunnelAvailable())
showConversationsSettings.postValue(!corePreferences.disableChat)
showMeetingsSettings.postValue(!corePreferences.disableMeetings)
ldapAvailable.postValue(core.ldapAvailable())
@ -195,6 +213,7 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() {
expandMeetings.value = false
expandNetwork.value = false
expandUserInterface.value = false
expandTunnel.value = false
expandAudioDevices.value = false
expandAudioCodecs.value = false
expandVideoCodecs.value = false
@ -227,6 +246,10 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() {
theme.postValue(corePreferences.darkMode)
color.postValue(corePreferences.themeMainColor)
if (core.tunnelAvailable()) {
setupTunnel()
}
keepAliveThirdPartyAccountsService.postValue(corePreferences.keepServiceAlive)
remoteProvisioningUrl.postValue(core.provisioningUri)
@ -465,6 +488,63 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() {
color.value = colorName
}
@UiThread
fun toggleTunnelExpand() {
expandTunnel.value = expandTunnel.value == false
}
@WorkerThread
private fun setupTunnel() {
// Tunnel mode values are 0, 1 and 2, we can use selected item position directly
val tunnelConfig = coreContext.core.tunnel
if (tunnelConfig != null) {
val mainTunnel = tunnelConfig.servers.firstOrNull()
if (mainTunnel != null) {
tunnelMainHost.postValue(mainTunnel.host)
tunnelMainPort.postValue(mainTunnel.port.toString())
if (tunnelConfig.isDualModeEnabled) {
tunnelDualHost.postValue(mainTunnel.host2)
tunnelDualPort.postValue(mainTunnel.port2.toString())
}
}
tunnelDualMode.postValue(tunnelConfig.isDualModeEnabled)
tunnelModeIndex.postValue(tunnelConfig.mode.ordinal)
} else {
Log.w("$TAG No tunnel config found!")
tunnelModeIndex.postValue(0)
}
}
@UiThread
fun toggleTunnelDualMode() {
tunnelDualMode.value = tunnelDualMode.value == false
}
@UiThread
fun saveTunnelConfig() {
coreContext.postOnCoreThread { core ->
if (core.tunnelAvailable()) {
val tunnel = core.tunnel
tunnel?.cleanServers()
val config = Factory.instance().createTunnelConfig()
config.host = tunnelMainHost.value.orEmpty()
config.port = tunnelMainPort.value?.toInt() ?: 0
tunnel?.isDualModeEnabled = tunnelDualMode.value == true
if (tunnelDualMode.value == true) {
config.host2 = tunnelDualHost.value.orEmpty()
config.port2 = tunnelDualPort.value?.toInt() ?: 0
}
tunnel?.mode = Tunnel.Mode.fromInt(tunnelModeIndex.value ?: 0)
tunnel?.addServer(config)
Log.i("$TAG Tunnel configuration added into Core")
}
}
}
@UiThread
fun toggleKeepAliveThirdPartyAccountService() {
val newValue = keepAliveThirdPartyAccountsService.value == false

View file

@ -63,8 +63,8 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:onClick="@{() -> viewModel.toggleSecurityExpand()}"
android:id="@+id/security"
android:onClick="@{() -> viewModel.toggleSecurityExpand()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
@ -86,14 +86,14 @@
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="@{viewModel.expandSecurity ? View.VISIBLE : View.GONE}"
android:visibility="@{viewModel.expandSecurity ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/security"
bind:viewModel="@{viewModel}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:onClick="@{() -> viewModel.toggleCallsExpand()}"
android:id="@+id/calls"
android:onClick="@{() -> viewModel.toggleCallsExpand()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
@ -115,14 +115,14 @@
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="@{viewModel.expandCalls ? View.VISIBLE : View.GONE}"
android:visibility="@{viewModel.expandCalls ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/calls"
bind:viewModel="@{viewModel}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:onClick="@{() -> viewModel.toggleConversationsExpand()}"
android:id="@+id/chat"
android:onClick="@{() -> viewModel.toggleConversationsExpand()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
@ -145,14 +145,14 @@
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="@{viewModel.expandConversations &amp;&amp; viewModel.showConversationsSettings ? View.VISIBLE : View.GONE}"
android:visibility="@{viewModel.expandConversations &amp;&amp; viewModel.showConversationsSettings ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/chat"
bind:viewModel="@{viewModel}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:onClick="@{() -> viewModel.toggleContactsExpand()}"
android:id="@+id/contacts"
android:onClick="@{() -> viewModel.toggleContactsExpand()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
@ -175,14 +175,14 @@
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="@{viewModel.expandContacts &amp;&amp; viewModel.showContactsSettings ? View.VISIBLE : View.GONE}"
android:visibility="@{viewModel.expandContacts &amp;&amp; viewModel.showContactsSettings ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/contacts"
bind:viewModel="@{viewModel}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:onClick="@{() -> viewModel.toggleMeetingsExpand()}"
android:id="@+id/meetings"
android:onClick="@{() -> viewModel.toggleMeetingsExpand()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
@ -205,14 +205,14 @@
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="@{viewModel.expandMeetings &amp;&amp; viewModel.showMeetingsSettings ? View.VISIBLE : View.GONE}"
android:visibility="@{viewModel.expandMeetings &amp;&amp; viewModel.showMeetingsSettings ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/meetings"
bind:viewModel="@{viewModel}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:onClick="@{() -> viewModel.toggleNetworkExpand()}"
android:id="@+id/network"
android:onClick="@{() -> viewModel.toggleNetworkExpand()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
@ -234,14 +234,14 @@
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="@{viewModel.expandNetwork ? View.VISIBLE : View.GONE}"
android:visibility="@{viewModel.expandNetwork ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/network"
bind:viewModel="@{viewModel}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:onClick="@{() -> viewModel.toggleUserInterfaceExpand()}"
android:id="@+id/user_interface"
android:onClick="@{() -> viewModel.toggleUserInterfaceExpand()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
@ -263,10 +263,40 @@
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="@{viewModel.expandUserInterface ? View.VISIBLE : View.GONE}"
android:visibility="@{viewModel.expandUserInterface ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/user_interface"
bind:viewModel="@{viewModel}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:id="@+id/tunnel"
android:onClick="@{() -> viewModel.toggleTunnelExpand()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:layout_marginStart="26dp"
android:layout_marginEnd="26dp"
android:layout_marginTop="16dp"
android:text="@string/settings_tunnel_title"
android:visibility="@{viewModel.isTunnelAvailable ? View.VISIBLE : View.GONE}"
android:drawableEnd="@{viewModel.expandTunnel ? @drawable/caret_up : @drawable/caret_down, default=@drawable/caret_up}"
android:drawableTint="?attr/color_main2_600"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_interface_settings"/>
<include
android:id="@+id/tunnel_settings"
layout="@layout/settings_tunnel"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="@{viewModel.isTunnelAvailable &amp;&amp; viewModel.expandTunnel ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/tunnel"
bind:viewModel="@{viewModel}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
android:id="@+id/advanced_settings"
@ -284,7 +314,7 @@
app:layout_constraintVertical_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_interface_settings"
app:layout_constraintTop_toBottomOf="@id/tunnel_settings"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,233 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.ui.main.settings.viewmodel.SettingsViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="20dp"
android:background="@drawable/shape_squircle_white_background">
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
android:id="@+id/tunnel_host_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/settings_tunnel_main_host_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.appcompat.widget.AppCompatEditText
style="@style/default_text_style"
android:id="@+id/tunnel_host"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:text="@={viewModel.tunnelMainHost}"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:maxLines="1"
android:background="@drawable/edit_text_background"
android:inputType="text|textUri"
android:hint="@string/settings_tunnel_main_host_label"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/tunnel_host_label"
app:layout_constraintStart_toStartOf="@id/tunnel_host_label"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
android:id="@+id/tunnel_port_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/settings_tunnel_main_port_label"
app:layout_constraintTop_toBottomOf="@id/tunnel_host"
app:layout_constraintStart_toStartOf="parent"/>
<androidx.appcompat.widget.AppCompatEditText
style="@style/default_text_style"
android:id="@+id/tunnel_port"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:text="@={viewModel.tunnelMainPort}"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:maxLines="1"
android:background="@drawable/edit_text_background"
android:inputType="number|numberSigned"
android:hint="@string/settings_tunnel_main_port_label"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/tunnel_port_label"
app:layout_constraintStart_toStartOf="@id/tunnel_port_label"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/settings_title_style"
android:onClick="@{() -> viewModel.toggleTunnelDualMode()}"
android:id="@+id/tunnel_dual_mode_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="10dp"
android:text="@string/settings_tunnel_dual_mode_label"
android:maxLines="2"
android:ellipsize="end"
app:layout_constraintTop_toTopOf="@id/tunnel_dual_mode_switch"
app:layout_constraintBottom_toBottomOf="@id/tunnel_dual_mode_switch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/tunnel_dual_mode_switch"/>
<com.google.android.material.materialswitch.MaterialSwitch
style="@style/material_switch_style"
android:id="@+id/tunnel_dual_mode_switch"
android:onClick="@{() -> viewModel.toggleTunnelDualMode()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:checked="@{viewModel.tunnelDualMode}"
app:layout_constraintTop_toBottomOf="@id/tunnel_port"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
android:id="@+id/tunnel_dual_host_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/settings_tunnel_second_host_label"
android:visibility="@{viewModel.tunnelDualMode ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/tunnel_dual_mode_switch"
app:layout_constraintStart_toStartOf="parent"/>
<androidx.appcompat.widget.AppCompatEditText
style="@style/default_text_style"
android:id="@+id/tunnel_dual_host"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:text="@={viewModel.tunnelDualHost}"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:maxLines="1"
android:background="@drawable/edit_text_background"
android:inputType="text|textUri"
android:hint="@string/settings_tunnel_second_host_label"
android:visibility="@{viewModel.tunnelDualMode ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/tunnel_dual_host_label"
app:layout_constraintStart_toStartOf="@id/tunnel_dual_host_label"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
android:id="@+id/tunnel_dual_port_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/settings_tunnel_second_port_label"
android:visibility="@{viewModel.tunnelDualMode ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/tunnel_dual_host"
app:layout_constraintStart_toStartOf="parent"/>
<androidx.appcompat.widget.AppCompatEditText
style="@style/default_text_style"
android:id="@+id/tunnel_dual_port"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:text="@={viewModel.tunnelDualPort}"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:maxLines="1"
android:background="@drawable/edit_text_background"
android:inputType="number|numberSigned"
android:hint="@string/settings_tunnel_second_port_label"
android:visibility="@{viewModel.tunnelDualMode ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/tunnel_dual_port_label"
app:layout_constraintStart_toStartOf="@id/tunnel_dual_port_label"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
android:id="@+id/tunnel_mode_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/settings_tunnel_mode_label"
app:layout_constraintTop_toBottomOf="@id/tunnel_dual_port"
app:layout_constraintStart_toStartOf="parent"/>
<androidx.appcompat.widget.AppCompatSpinner
style="@style/default_text_style"
android:id="@+id/tunnel_mode_spinner"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:gravity="center_vertical"
android:overlapAnchor="false"
android:dropDownVerticalOffset="2dp"
android:spinnerMode="dropdown"
android:popupBackground="@drawable/shape_squircle_white_background"
android:background="@drawable/edit_text_background"
app:layout_constraintTop_toBottomOf="@id/tunnel_mode_label"
app:layout_constraintStart_toStartOf="@id/tunnel_mode_label"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/tunnel_mode_caret"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/caret_down"
android:contentDescription="@null"
app:layout_constraintTop_toTopOf="@id/tunnel_mode_spinner"
app:layout_constraintBottom_toBottomOf="@id/tunnel_mode_spinner"
app:layout_constraintEnd_toEndOf="@id/tunnel_mode_spinner"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -273,6 +273,16 @@
<string name="settings_user_interface_light_theme_label">Clair</string>
<string name="settings_user_interface_auto_theme_label">Auto</string>
<string name="settings_user_interface_color_title">Couleur principale</string>
<string name="settings_tunnel_title">Tunnel</string>
<string name="settings_tunnel_main_host_label">Hôte</string>
<string name="settings_tunnel_main_port_label">Port</string>
<string name="settings_tunnel_dual_mode_label">Utiliser deux serveurs</string>
<string name="settings_tunnel_second_host_label">Hôte second serveur</string>
<string name="settings_tunnel_second_port_label">Port second serveur</string>
<string name="settings_tunnel_mode_label">Mode</string>
<string name="settings_tunnel_mode_disabled_label">Désactivé</string>
<string name="settings_tunnel_mode_always_label">Toujours</string>
<string name="settings_tunnel_mode_auto_label">Auto</string>
<string name="settings_advanced_title">Paramètres avancés</string>
<string name="settings_advanced_keep_alive_service_title">Garder l\'app en vie via un Service</string>

View file

@ -308,6 +308,16 @@
<string name="settings_user_interface_light_theme_label">Light theme</string>
<string name="settings_user_interface_auto_theme_label">Auto</string>
<string name="settings_user_interface_color_title">Main color</string>
<string name="settings_tunnel_title">Tunnel</string>
<string name="settings_tunnel_main_host_label">Host</string>
<string name="settings_tunnel_main_port_label">Port</string>
<string name="settings_tunnel_dual_mode_label">Use two servers</string>
<string name="settings_tunnel_second_host_label">Second host</string>
<string name="settings_tunnel_second_port_label">Second port</string>
<string name="settings_tunnel_mode_label">Mode</string>
<string name="settings_tunnel_mode_disabled_label">Disabled</string>
<string name="settings_tunnel_mode_always_label">Always</string>
<string name="settings_tunnel_mode_auto_label">Auto</string>
<string name="settings_advanced_title">Advanced settings</string>
<string name="settings_advanced_keep_alive_service_title">Keep app alive using Service</string>