From de37ae245d36c49c442ba95c622b7948ff0b4527 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 3 Oct 2023 14:19:06 +0200 Subject: [PATCH] Added account settings --- .../ThirdPartySipAccountLoginViewModel.kt | 15 +- .../fragment/AccountSettingsFragment.kt | 76 ++++++ .../viewmodel/AccountSettingsViewModel.kt | 142 +++++++++++ .../res/layout/account_settings_fragment.xml | 234 ++++++++++++++++++ app/src/main/res/values/strings.xml | 8 + 5 files changed, 466 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/main/settings/viewmodel/AccountSettingsViewModel.kt diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt index dc139f534..683436f00 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt @@ -24,6 +24,7 @@ import androidx.annotation.WorkerThread import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import java.util.Locale import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -42,10 +43,6 @@ import org.linphone.utils.Event class ThirdPartySipAccountLoginViewModel @UiThread constructor() : ViewModel() { companion object { private const val TAG = "[Third Party SIP Account Login ViewModel]" - - private const val UDP = "UDP" - private const val TCP = "TCP" - private const val TLS = "TLS" } val username = MutableLiveData() @@ -134,9 +131,9 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : ViewModel() { // TODO: handle formatting errors ? - availableTransports.add(UDP) - availableTransports.add(TCP) - availableTransports.add(TLS) + availableTransports.add(TransportType.Udp.name.uppercase(Locale.getDefault())) + availableTransports.add(TransportType.Tcp.name.uppercase(Locale.getDefault())) + availableTransports.add(TransportType.Tls.name.uppercase(Locale.getDefault())) } @UiThread @@ -167,8 +164,8 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : ViewModel() { val serverAddress = Factory.instance().createAddress("sip:$domainValue") serverAddress?.transport = when (transport.value.orEmpty().trim()) { - TCP -> TransportType.Tcp - TLS -> TransportType.Tls + TransportType.Tcp.name.uppercase(Locale.getDefault()) -> TransportType.Tcp + TransportType.Tls.name.uppercase(Locale.getDefault()) -> TransportType.Tls else -> TransportType.Udp } accountParams.serverAddress = serverAddress diff --git a/app/src/main/java/org/linphone/ui/main/settings/fragment/AccountSettingsFragment.kt b/app/src/main/java/org/linphone/ui/main/settings/fragment/AccountSettingsFragment.kt index 43f4ef5e2..59ab0f426 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/fragment/AccountSettingsFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/fragment/AccountSettingsFragment.kt @@ -4,11 +4,20 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter import androidx.annotation.UiThread +import androidx.core.view.doOnPreDraw +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import java.util.Locale +import org.linphone.R +import org.linphone.core.TransportType import org.linphone.core.tools.Log import org.linphone.databinding.AccountSettingsFragmentBinding import org.linphone.ui.main.fragment.GenericFragment +import org.linphone.ui.main.settings.viewmodel.AccountSettingsViewModel @UiThread class AccountSettingsFragment : GenericFragment() { @@ -20,6 +29,29 @@ class AccountSettingsFragment : GenericFragment() { private val args: AccountSettingsFragmentArgs by navArgs() + private lateinit var viewModel: AccountSettingsViewModel + + private val dropdownListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val transport = viewModel.availableTransports[position] + val transportType = when { + transport == TransportType.Tcp.name.uppercase(Locale.getDefault()) -> TransportType.Tcp + transport == TransportType.Tls.name.uppercase(Locale.getDefault()) -> TransportType.Tls + else -> TransportType.Udp + } + Log.i("$TAG Selected transport updated [$transport] -> [${transportType.name}]") + viewModel.selectedTransport.value = transportType + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + } + } + + override fun goBack(): Boolean { + findNavController().popBackStack() + return true + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -31,14 +63,58 @@ class AccountSettingsFragment : GenericFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + postponeEnterTransition() binding.lifecycleOwner = viewLifecycleOwner + viewModel = requireActivity().run { + ViewModelProvider(this)[AccountSettingsViewModel::class.java] + } + binding.viewModel = viewModel + val identity = args.accountIdentity Log.i("$TAG Looking up for account with identity address [$identity]") + viewModel.findAccountMatchingIdentity(identity) binding.setBackClickListener { goBack() } + + viewModel.accountFoundEvent.observe(viewLifecycleOwner) { + it.consume { found -> + if (found) { + (view.parent as? ViewGroup)?.doOnPreDraw { + startPostponedEnterTransition() + + val adapter = ArrayAdapter( + requireContext(), + R.layout.drop_down_item, + viewModel.availableTransports + ) + adapter.setDropDownViewResource(R.layout.generic_dropdown_cell) + val currentTransport = viewModel.selectedTransport.value?.name?.uppercase( + Locale.getDefault() + ) + binding.transportSpinner.adapter = adapter + binding.transportSpinner.setSelection( + viewModel.availableTransports.indexOf(currentTransport) + ) + binding.transportSpinner.onItemSelectedListener = dropdownListener + } + } else { + Log.e( + "$TAG Failed to find an account matching this identity address [$identity]" + ) + // TODO: show error + goBack() + } + } + } + } + + override fun onPause() { + super.onPause() + + viewModel.saveChanges() } } diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/AccountSettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/AccountSettingsViewModel.kt new file mode 100644 index 000000000..4e795b769 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/AccountSettingsViewModel.kt @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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 . + */ +package org.linphone.ui.main.settings.viewmodel + +import androidx.annotation.UiThread +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import java.util.Locale +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.AVPFMode +import org.linphone.core.Account +import org.linphone.core.Factory +import org.linphone.core.NatPolicy +import org.linphone.core.TransportType +import org.linphone.core.tools.Log +import org.linphone.utils.Event + +class AccountSettingsViewModel @UiThread constructor() : ViewModel() { + companion object { + private const val TAG = "[Account Settings ViewModel]" + } + + val availableTransports = arrayListOf() + + val selectedTransport = MutableLiveData() + + val pushNotificationsEnabled = MutableLiveData() + + val sipProxyServer = MutableLiveData() + + val outboundProxyEnabled = MutableLiveData() + + val stunServer = MutableLiveData() + + val iceEnabled = MutableLiveData() + + val avpfEnabled = MutableLiveData() + + val expire = MutableLiveData() + + val accountFoundEvent = MutableLiveData>() + + private lateinit var account: Account + private lateinit var natPolicy: NatPolicy + + init { + availableTransports.add(TransportType.Udp.name.uppercase(Locale.getDefault())) + availableTransports.add(TransportType.Tcp.name.uppercase(Locale.getDefault())) + availableTransports.add(TransportType.Tls.name.uppercase(Locale.getDefault())) + } + + @UiThread + fun findAccountMatchingIdentity(identity: String) { + coreContext.postOnCoreThread { core -> + val found = core.accountList.find { + it.params.identityAddress?.asStringUriOnly() == identity + } + if (found != null) { + Log.i("$TAG Found matching account [$found]") + account = found + + val params = account.params + + pushNotificationsEnabled.postValue(params.pushNotificationAllowed) + + val transportType = params.serverAddress?.transport ?: TransportType.Tls + selectedTransport.postValue(transportType) + + sipProxyServer.postValue(params.serverAddress?.asStringUriOnly()) + outboundProxyEnabled.postValue(params.isOutboundProxyEnabled) + + natPolicy = params.natPolicy ?: core.createNatPolicy() + stunServer.postValue(natPolicy.stunServer) + iceEnabled.postValue(natPolicy.isIceEnabled) + + avpfEnabled.postValue(account.isAvpfEnabled) + + expire.postValue(params.expires.toString()) + + accountFoundEvent.postValue(Event(true)) + } else { + Log.e("$TAG Failed to find account matching identity [$identity]") + accountFoundEvent.postValue(Event(false)) + } + } + } + + @UiThread + fun saveChanges() { + coreContext.postOnCoreThread { core -> + Log.i("$TAG Saving changes...") + + if (::account.isInitialized) { + val newParams = account.params.clone() + + newParams.pushNotificationAllowed = pushNotificationsEnabled.value == true + + val server = sipProxyServer.value.orEmpty() + if (server.isNotEmpty()) { + val serverAddress = Factory.instance().createAddress(server) + if (serverAddress != null) { + serverAddress.transport = selectedTransport.value + newParams.serverAddress = serverAddress + } + } + newParams.isOutboundProxyEnabled = outboundProxyEnabled.value == true + + if (::natPolicy.isInitialized) { + Log.i("$TAG Also applying changes to NAT policy") + natPolicy.stunServer = stunServer.value + natPolicy.isStunEnabled = stunServer.value.orEmpty().isNotEmpty() + natPolicy.isIceEnabled = iceEnabled.value == true + newParams.natPolicy = natPolicy + } + + newParams.avpfMode = if (avpfEnabled.value == true) AVPFMode.Enabled else AVPFMode.Disabled + + newParams.expires = expire.value?.toInt() ?: 31536000 + + account.params = newParams + Log.i("$TAG Changes have been saved") + } + } + } +} diff --git a/app/src/main/res/layout/account_settings_fragment.xml b/app/src/main/res/layout/account_settings_fragment.xml index c007d3ad7..3413ec278 100644 --- a/app/src/main/res/layout/account_settings_fragment.xml +++ b/app/src/main/res/layout/account_settings_fragment.xml @@ -8,6 +8,9 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a65505c44..0c777543c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -245,6 +245,14 @@ Remove Last connection: + Allow push notifications + SIP proxy server URL + Outbound proxy + STUN server server URL + Enable ICE + AVPF + Expire + Online Online on %s Online today at %s