mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Added recover phone account form to be able to login with only a phone number
This commit is contained in:
parent
1843bd4c1e
commit
cf513627ab
15 changed files with 1524 additions and 20 deletions
|
|
@ -49,6 +49,7 @@ record_aware=1
|
|||
|
||||
[account_creator]
|
||||
url=https://subscribe.linphone.org/api/
|
||||
backend=1
|
||||
|
||||
[lime]
|
||||
lime_update_threshold=86400
|
||||
|
|
|
|||
|
|
@ -103,22 +103,10 @@ class LandingFragment : GenericFragment() {
|
|||
}
|
||||
|
||||
binding.setForgottenPasswordClickListener {
|
||||
val url = getString(R.string.web_platform_forgotten_password_url)
|
||||
try {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
startActivity(browserIntent)
|
||||
} catch (ise: IllegalStateException) {
|
||||
Log.e(
|
||||
"$TAG Can't start ACTION_VIEW intent for URL [$url], IllegalStateException: $ise"
|
||||
)
|
||||
} catch (anfe: ActivityNotFoundException) {
|
||||
Log.e(
|
||||
"$TAG Can't start ACTION_VIEW intent for URL [$url], ActivityNotFoundException: $anfe"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"$TAG Can't start ACTION_VIEW intent for URL [$url]: $e"
|
||||
)
|
||||
if (findNavController().currentDestination?.id == R.id.landingFragment) {
|
||||
val action =
|
||||
LandingFragmentDirections.actionLandingFragmentToRecoverAccountFragment()
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.assistant.fragment
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.core.net.toUri
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.GenericFragment
|
||||
import org.linphone.databinding.AssistantRecoverAccountFragmentBinding
|
||||
import org.linphone.ui.assistant.viewmodel.RecoverPhoneAccountViewModel
|
||||
import kotlin.getValue
|
||||
|
||||
@UiThread
|
||||
class RecoverAccountFragment : GenericFragment() {
|
||||
companion object {
|
||||
private const val TAG = "[Recover Account Fragment]"
|
||||
}
|
||||
|
||||
private lateinit var binding: AssistantRecoverAccountFragmentBinding
|
||||
|
||||
private val viewModel: RecoverPhoneAccountViewModel by navGraphViewModels(
|
||||
R.id.assistant_nav_graph
|
||||
)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = AssistantRecoverAccountFragmentBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.viewModel = viewModel
|
||||
observeToastEvents(viewModel)
|
||||
|
||||
binding.setBackClickListener {
|
||||
goBack()
|
||||
}
|
||||
|
||||
binding.setRecoverEmailAccountClickListener {
|
||||
recoverEmailAccount()
|
||||
}
|
||||
|
||||
binding.setRecoverPhoneNumberAccountClickListener {
|
||||
if (findNavController().currentDestination?.id == R.id.recoverAccountFragment) {
|
||||
val action = RecoverAccountFragmentDirections.actionRecoverAccountFragmentToRecoverPhoneAccountFragment()
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goBack() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
|
||||
private fun recoverEmailAccount() {
|
||||
val url = getString(R.string.web_platform_forgotten_password_url)
|
||||
try {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
startActivity(browserIntent)
|
||||
} catch (ise: IllegalStateException) {
|
||||
Log.e(
|
||||
"$TAG Can't start ACTION_VIEW intent for URL [$url], IllegalStateException: $ise"
|
||||
)
|
||||
} catch (anfe: ActivityNotFoundException) {
|
||||
Log.e(
|
||||
"$TAG Can't start ACTION_VIEW intent for URL [$url], ActivityNotFoundException: $anfe"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"$TAG Can't start ACTION_VIEW intent for URL [$url]: $e"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.assistant.fragment
|
||||
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.databinding.AssistantRecoverPhoneAccountConfirmSmsCodeFragmentBinding
|
||||
import org.linphone.ui.GenericFragment
|
||||
import org.linphone.ui.assistant.viewmodel.RecoverPhoneAccountViewModel
|
||||
|
||||
@UiThread
|
||||
class RecoverPhoneAccountCodeConfirmationFragment : GenericFragment() {
|
||||
companion object {
|
||||
private const val TAG = "[Recover Phone Account Code Confirmation Fragment]"
|
||||
}
|
||||
|
||||
private lateinit var binding: AssistantRecoverPhoneAccountConfirmSmsCodeFragmentBinding
|
||||
|
||||
private val viewModel: RecoverPhoneAccountViewModel by navGraphViewModels(
|
||||
R.id.assistant_nav_graph
|
||||
)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = AssistantRecoverPhoneAccountConfirmSmsCodeFragmentBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.viewModel = viewModel
|
||||
observeToastEvents(viewModel)
|
||||
|
||||
binding.setBackClickListener {
|
||||
goBack()
|
||||
}
|
||||
|
||||
viewModel.accountCreatedEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { identity ->
|
||||
Log.i("$TAG Account [$identity] has been created, leaving assistant")
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
|
||||
// This won't work starting Android 10 as clipboard access is denied unless app has focus,
|
||||
// which won't be the case when the SMS arrives unless it is added into clipboard from a notification
|
||||
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.addPrimaryClipChangedListener {
|
||||
val data = clipboard.primaryClip
|
||||
if (data != null && data.itemCount > 0) {
|
||||
val clip = data.getItemAt(0).text.toString()
|
||||
if (clip.length == 4) {
|
||||
Log.i(
|
||||
"$TAG Found 4 digits [$clip] as primary clip in clipboard, using it and clear it"
|
||||
)
|
||||
viewModel.smsCodeFirstDigit.value = clip[0].toString()
|
||||
viewModel.smsCodeSecondDigit.value = clip[1].toString()
|
||||
viewModel.smsCodeThirdDigit.value = clip[2].toString()
|
||||
viewModel.smsCodeLastDigit.value = clip[3].toString()
|
||||
clipboard.clearPrimaryClip()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goBack() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.assistant.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.telephony.TelephonyManager
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
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.appcompat.widget.AppCompatTextView
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.GenericFragment
|
||||
import org.linphone.databinding.AssistantRecoverPhoneAccountFragmentBinding
|
||||
import org.linphone.ui.assistant.viewmodel.RecoverPhoneAccountViewModel
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.ConfirmationDialogModel
|
||||
import org.linphone.utils.DialogUtils
|
||||
import org.linphone.utils.PhoneNumberUtils
|
||||
import kotlin.getValue
|
||||
|
||||
@UiThread
|
||||
class RecoverPhoneAccountFragment : GenericFragment() {
|
||||
companion object {
|
||||
private const val TAG = "[Recover Phone Account Fragment]"
|
||||
}
|
||||
|
||||
private lateinit var binding: AssistantRecoverPhoneAccountFragmentBinding
|
||||
|
||||
private val viewModel: RecoverPhoneAccountViewModel by navGraphViewModels(
|
||||
R.id.assistant_nav_graph
|
||||
)
|
||||
|
||||
private val dropdownListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
val dialPlan = viewModel.dialPlansList[position]
|
||||
Log.i(
|
||||
"$TAG Selected dialplan updated [+${dialPlan.countryCallingCode}] / [${dialPlan.country}]"
|
||||
)
|
||||
viewModel.selectedDialPlan.value = dialPlan
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = AssistantRecoverPhoneAccountFragmentBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.viewModel = viewModel
|
||||
observeToastEvents(viewModel)
|
||||
|
||||
binding.setBackClickListener {
|
||||
goBack()
|
||||
}
|
||||
|
||||
binding.phoneNumber.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
viewModel.phoneNumberError.value = ""
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
viewModel.normalizedPhoneNumberEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { number ->
|
||||
Log.i("$TAG Showing confirmation dialog for phone number [$number]")
|
||||
showPhoneNumberConfirmationDialog(number)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.goToSmsValidationEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
if (findNavController().currentDestination?.id == R.id.recoverPhoneAccountFragment) {
|
||||
Log.i("$TAG Going to SMS code validation fragment")
|
||||
val action = RecoverPhoneAccountFragmentDirections.actionRecoverPhoneAccountFragmentToRecoverPhoneAccountCodeConfirmationFragment()
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val telephonyManager = requireContext().getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||
val countryIso = telephonyManager.networkCountryIso
|
||||
coreContext.postOnCoreThread {
|
||||
val fragmentContext = context ?: return@postOnCoreThread
|
||||
|
||||
val adapter = object : ArrayAdapter<String>(
|
||||
fragmentContext,
|
||||
R.layout.drop_down_item,
|
||||
viewModel.dialPlansLabelList
|
||||
) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = convertView ?: super.getView(position, null, parent)
|
||||
val label = viewModel.dialPlansShortLabelList[position]
|
||||
(view as? AppCompatTextView)?.text = label
|
||||
return view
|
||||
}
|
||||
}
|
||||
adapter.setDropDownViewResource(R.layout.assistant_country_picker_dropdown_cell)
|
||||
|
||||
val dialPlan = PhoneNumberUtils.getDeviceDialPlan(countryIso)
|
||||
var default = 0
|
||||
if (dialPlan != null) {
|
||||
viewModel.selectedDialPlan.postValue(dialPlan)
|
||||
default = viewModel.dialPlansList.indexOf(dialPlan)
|
||||
}
|
||||
|
||||
coreContext.postOnMainThread {
|
||||
binding.prefix.adapter = adapter
|
||||
binding.prefix.setSelection(default)
|
||||
binding.prefix.onItemSelectedListener = dropdownListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goBack() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
|
||||
private fun showPhoneNumberConfirmationDialog(number: String) {
|
||||
val label = AppUtils.getFormattedString(R.string.assistant_dialog_confirm_phone_number_message, number)
|
||||
val model = ConfirmationDialogModel(label)
|
||||
val dialog = DialogUtils.getAccountCreationPhoneNumberConfirmationDialog(
|
||||
requireActivity(),
|
||||
model
|
||||
)
|
||||
|
||||
model.dismissEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
Log.w("$TAG User dismissed the dialog, aborting recovery process")
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
model.confirmEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
Log.i("$TAG User confirmed the phone number, requesting account creation token & SMS code")
|
||||
viewModel.startRecoveryProcess()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
|
@ -365,7 +365,7 @@ class AccountCreationViewModel
|
|||
val account = accountCreated
|
||||
if (::accountManagerServices.isInitialized && account != null) {
|
||||
val code =
|
||||
"${smsCodeFirstDigit.value}${smsCodeSecondDigit.value}${smsCodeThirdDigit.value}${smsCodeLastDigit.value}"
|
||||
"${smsCodeFirstDigit.value.orEmpty().trim()}${smsCodeSecondDigit.value.orEmpty().trim()}${smsCodeThirdDigit.value.orEmpty().trim()}${smsCodeLastDigit.value.orEmpty().trim()}"
|
||||
val identity = account.params.identityAddress
|
||||
if (identity != null) {
|
||||
Log.i(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,449 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.assistant.viewmodel
|
||||
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.core.AccountCreator
|
||||
import org.linphone.core.AccountCreatorListenerStub
|
||||
import org.linphone.core.AccountManagerServices
|
||||
import org.linphone.core.AccountManagerServicesRequest
|
||||
import org.linphone.core.AccountManagerServicesRequestListenerStub
|
||||
import org.linphone.core.Core
|
||||
import org.linphone.core.CoreListenerStub
|
||||
import org.linphone.core.DialPlan
|
||||
import org.linphone.core.Dictionary
|
||||
import org.linphone.core.Factory
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.GenericViewModel
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
import java.util.Locale
|
||||
|
||||
class RecoverPhoneAccountViewModel : GenericViewModel() {
|
||||
companion object {
|
||||
private const val TAG = "[Recover Phone Account ViewModel]"
|
||||
|
||||
private const val TIME_TO_WAIT_FOR_PUSH_NOTIFICATION_WITH_ACCOUNT_CREATION_TOKEN = 5000
|
||||
}
|
||||
|
||||
val pushNotificationsAvailable = MutableLiveData<Boolean>()
|
||||
|
||||
val dialPlansLabelList = arrayListOf<String>()
|
||||
|
||||
val dialPlansShortLabelList = arrayListOf<String>()
|
||||
|
||||
val dialPlansList = arrayListOf<DialPlan>()
|
||||
|
||||
val selectedDialPlan = MutableLiveData<DialPlan>()
|
||||
|
||||
val phoneNumber = MutableLiveData<String>()
|
||||
|
||||
val phoneNumberError = MutableLiveData<String>()
|
||||
|
||||
val confirmationMessage = MutableLiveData<String>()
|
||||
|
||||
val smsCodeFirstDigit = MutableLiveData<String>()
|
||||
val smsCodeSecondDigit = MutableLiveData<String>()
|
||||
val smsCodeThirdDigit = MutableLiveData<String>()
|
||||
val smsCodeLastDigit = MutableLiveData<String>()
|
||||
|
||||
val operationInProgress = MutableLiveData<Boolean>()
|
||||
|
||||
val recoverEnabled = MediatorLiveData<Boolean>()
|
||||
|
||||
private var normalizedPhoneNumber: String? = null
|
||||
val normalizedPhoneNumberEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
val goToSmsValidationEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
val accountCreatedEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
private lateinit var accountManagerServices: AccountManagerServices
|
||||
private val accountManagerServicesListener = object : AccountManagerServicesRequestListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onRequestSuccessful(
|
||||
request: AccountManagerServicesRequest,
|
||||
data: String?
|
||||
) {
|
||||
Log.i("$TAG Request [$request] was successful, data is [$data]")
|
||||
operationInProgress.postValue(false)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onRequestError(
|
||||
request: AccountManagerServicesRequest,
|
||||
statusCode: Int,
|
||||
errorMessage: String?,
|
||||
parameterErrors: Dictionary?
|
||||
) {
|
||||
Log.e(
|
||||
"$TAG Request [$request] returned an error with status code [$statusCode] and message [$errorMessage]"
|
||||
)
|
||||
operationInProgress.postValue(false)
|
||||
|
||||
if (!errorMessage.isNullOrEmpty()) {
|
||||
showFormattedRedToast(errorMessage, R.drawable.warning_circle)
|
||||
}
|
||||
|
||||
when (request.type) {
|
||||
AccountManagerServicesRequest.Type.SendAccountCreationTokenByPush -> {
|
||||
Log.w("$TAG Cancelling job waiting for push notification")
|
||||
waitingForFlexiApiPushToken = false
|
||||
waitForPushJob?.cancel()
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
recoverEnabled.postValue(true)
|
||||
}
|
||||
}
|
||||
private var accountCreationToken: String? = null
|
||||
|
||||
private var waitingForFlexiApiPushToken = false
|
||||
private var waitForPushJob: Job? = null
|
||||
|
||||
private lateinit var accountCreator: AccountCreator
|
||||
private val accountCreatorListener = object : AccountCreatorListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onRecoverAccount(
|
||||
creator: AccountCreator,
|
||||
status: AccountCreator.Status,
|
||||
response: String?
|
||||
) {
|
||||
Log.i("$TAG Recover account status is $status")
|
||||
operationInProgress.postValue(false)
|
||||
|
||||
if (status == AccountCreator.Status.RequestOk) {
|
||||
goToSmsValidationEvent.postValue(Event(true))
|
||||
} else {
|
||||
Log.e("$TAG Error in onRecoverAccount [${status.name}]")
|
||||
showFormattedRedToast(status.name, R.drawable.warning_circle)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onLoginLinphoneAccount(
|
||||
creator: AccountCreator,
|
||||
status: AccountCreator.Status,
|
||||
response: String?
|
||||
) {
|
||||
Log.i("$TAG onLoginLinphoneAccount status is $status")
|
||||
operationInProgress.postValue(false)
|
||||
|
||||
if (status == AccountCreator.Status.RequestOk) {
|
||||
if (!createAccountAndAuthInfo()) {
|
||||
Log.e("$TAG Failed to create account object")
|
||||
}
|
||||
} else {
|
||||
Log.e("$TAG Error in onRecoverAccount [${status.name}]")
|
||||
showFormattedRedToast(status.name, R.drawable.warning_circle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val coreListener = object : CoreListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onPushNotificationReceived(core: Core, payload: String?) {
|
||||
Log.i("$TAG Push received: [$payload]")
|
||||
|
||||
val data = payload.orEmpty()
|
||||
if (data.isNotEmpty()) {
|
||||
try {
|
||||
// This is because JSONObject.toString() done by the SDK will result in payload looking like {"custom-payload":"{\"token\":\"value\"}"}
|
||||
val cleanPayload = data
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\"{", "{")
|
||||
.replace("}\"", "}")
|
||||
Log.i("$TAG Cleaned payload is: [$cleanPayload]")
|
||||
|
||||
val json = JSONObject(cleanPayload)
|
||||
val customPayload = json.getJSONObject("custom-payload")
|
||||
if (customPayload.has("token")) {
|
||||
waitForPushJob?.cancel()
|
||||
waitingForFlexiApiPushToken = false
|
||||
operationInProgress.postValue(false)
|
||||
|
||||
val token = customPayload.getString("token")
|
||||
if (token.isNotEmpty()) {
|
||||
accountCreationToken = token
|
||||
Log.i(
|
||||
"$TAG Extracted token [$accountCreationToken] from push payload, recovering account"
|
||||
)
|
||||
requestSmsCode()
|
||||
} else {
|
||||
Log.e("$TAG Push payload JSON object has an empty 'token'!")
|
||||
onFlexiApiTokenRequestError()
|
||||
}
|
||||
} else {
|
||||
Log.e("$TAG Push payload JSON object has no 'token' key!")
|
||||
onFlexiApiTokenRequestError()
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
Log.e("$TAG Exception trying to parse push payload as JSON: [$e]")
|
||||
onFlexiApiTokenRequestError()
|
||||
}
|
||||
} else {
|
||||
Log.e("$TAG Push payload is null or empty, can't extract auth token!")
|
||||
onFlexiApiTokenRequestError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
core.addListener(coreListener)
|
||||
|
||||
pushNotificationsAvailable.postValue(LinphoneUtils.arePushNotificationsAvailable(core))
|
||||
|
||||
val dialPlans = Factory.instance().dialPlans.toList()
|
||||
for (dialPlan in dialPlans) {
|
||||
dialPlansList.add(dialPlan)
|
||||
dialPlansLabelList.add(
|
||||
"${dialPlan.flag} ${dialPlan.country} | +${dialPlan.countryCallingCode}"
|
||||
)
|
||||
dialPlansShortLabelList.add(
|
||||
"${dialPlan.flag} +${dialPlan.countryCallingCode}"
|
||||
)
|
||||
}
|
||||
|
||||
accountManagerServices = core.createAccountManagerServices()
|
||||
accountManagerServices.language = Locale.getDefault().language // Returns en, fr, etc...
|
||||
|
||||
accountCreator = core.createAccountCreator("https://subscribe.linphone.org/api/")
|
||||
accountCreator.addListener(accountCreatorListener)
|
||||
}
|
||||
|
||||
recoverEnabled.addSource(selectedDialPlan) {
|
||||
recoverEnabled.value = phoneNumber.value.orEmpty().isNotEmpty() && selectedDialPlan.value?.countryCallingCode.orEmpty().isNotEmpty()
|
||||
}
|
||||
recoverEnabled.addSource(phoneNumber) {
|
||||
recoverEnabled.value = phoneNumber.value.orEmpty().isNotEmpty() && selectedDialPlan.value?.countryCallingCode.orEmpty().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
core.removeListener(coreListener)
|
||||
accountCreator.removeListener(accountCreatorListener)
|
||||
}
|
||||
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun sendCode() {
|
||||
coreContext.postOnCoreThread {
|
||||
val dialPlan = selectedDialPlan.value
|
||||
if (dialPlan == null) {
|
||||
Log.e("$TAG No dial plan (country) selected!")
|
||||
return@postOnCoreThread
|
||||
}
|
||||
val number = phoneNumber.value.orEmpty().trim()
|
||||
val formattedPhoneNumber = dialPlan.formatPhoneNumber(number, false)
|
||||
Log.i(
|
||||
"$TAG Formatted phone number [$number] using dial plan [${dialPlan.country}] is [$formattedPhoneNumber]"
|
||||
)
|
||||
|
||||
val message = coreContext.context.getString(
|
||||
R.string.assistant_account_creation_sms_confirmation_explanation,
|
||||
formattedPhoneNumber
|
||||
)
|
||||
normalizedPhoneNumber = formattedPhoneNumber
|
||||
confirmationMessage.postValue(message)
|
||||
normalizedPhoneNumberEvent.postValue(Event(formattedPhoneNumber))
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun requestSmsCode() {
|
||||
operationInProgress.postValue(true)
|
||||
|
||||
coreContext.core.loadConfigFromXml(corePreferences.linphoneDefaultValuesPath)
|
||||
accountCreator.domain = corePreferences.defaultDomain
|
||||
|
||||
val dialPlan = selectedDialPlan.value
|
||||
if (dialPlan == null) {
|
||||
Log.e("$TAG No dial plan (country) selected!")
|
||||
return
|
||||
}
|
||||
val number = phoneNumber.value.orEmpty().trim()
|
||||
val countryCallingCode = dialPlan.countryCallingCode
|
||||
var result = AccountCreator.PhoneNumberStatus.fromInt(
|
||||
accountCreator.setPhoneNumber(number, countryCallingCode)
|
||||
)
|
||||
if (result != AccountCreator.PhoneNumberStatus.Ok) {
|
||||
Log.e(
|
||||
"$TAG Error [$result] setting the phone number: $number with prefix: $countryCallingCode"
|
||||
)
|
||||
phoneNumberError.postValue(result.name)
|
||||
operationInProgress.postValue(false)
|
||||
return
|
||||
}
|
||||
Log.i("$TAG Phone number is ${accountCreator.phoneNumber}")
|
||||
|
||||
val result2 = accountCreator.setUsername(accountCreator.phoneNumber)
|
||||
if (result2 != AccountCreator.UsernameStatus.Ok) {
|
||||
Log.e(
|
||||
"$TAG Error [${result2.name}] setting the username: ${accountCreator.phoneNumber}"
|
||||
)
|
||||
phoneNumberError.postValue(result2.name)
|
||||
operationInProgress.postValue(false)
|
||||
return
|
||||
}
|
||||
Log.i("$TAG Username is ${accountCreator.username}")
|
||||
|
||||
accountCreator.token = accountCreationToken
|
||||
Log.i("$TAG Token is ${accountCreator.token}")
|
||||
|
||||
val status = accountCreator.recoverAccount()
|
||||
Log.i("$TAG Recover account returned $status")
|
||||
if (status != AccountCreator.Status.RequestOk) {
|
||||
operationInProgress.postValue(false)
|
||||
Log.e("$TAG Error doing recoverAccount [${status.name}]")
|
||||
showFormattedRedToast(status.name, R.drawable.warning_circle)
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun validateCode() {
|
||||
operationInProgress.value = true
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val code =
|
||||
"${smsCodeFirstDigit.value.orEmpty().trim()}${smsCodeSecondDigit.value.orEmpty().trim()}${smsCodeThirdDigit.value.orEmpty().trim()}${smsCodeLastDigit.value.orEmpty().trim()}"
|
||||
accountCreator.activationCode = code
|
||||
val status = accountCreator.loginLinphoneAccount()
|
||||
Log.i("$TAG Code [$code] validation result is $status")
|
||||
if (status != AccountCreator.Status.RequestOk) {
|
||||
operationInProgress.postValue(false)
|
||||
Log.e("$TAG Error doing loginLinphoneAccount [${status.name}]")
|
||||
showFormattedRedToast(status.name, R.drawable.warning_circle)
|
||||
}
|
||||
|
||||
// Reset code
|
||||
smsCodeFirstDigit.postValue("")
|
||||
smsCodeSecondDigit.postValue("")
|
||||
smsCodeThirdDigit.postValue("")
|
||||
smsCodeLastDigit.postValue("")
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun createAccountAndAuthInfo(): Boolean {
|
||||
val account = accountCreator.createAccountInCore()
|
||||
|
||||
if (account == null) {
|
||||
Log.e("$TAG Account creator couldn't create account")
|
||||
return false
|
||||
}
|
||||
coreContext.core.defaultAccount = account
|
||||
|
||||
val username = account.params.identityAddress?.username.orEmpty()
|
||||
Log.i("$TAG Account created with username [$username]")
|
||||
accountCreatedEvent.postValue(Event(username))
|
||||
return true
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun startRecoveryProcess() {
|
||||
coreContext.postOnCoreThread {
|
||||
requestFlexiApiToken()
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun requestFlexiApiToken() {
|
||||
if (!coreContext.core.isPushNotificationAvailable) {
|
||||
Log.e(
|
||||
"$TAG Core says push notification aren't available, can't request a token from FlexiAPI"
|
||||
)
|
||||
onFlexiApiTokenRequestError()
|
||||
return
|
||||
}
|
||||
|
||||
operationInProgress.postValue(true)
|
||||
recoverEnabled.postValue(false)
|
||||
|
||||
val pushConfig = coreContext.core.pushNotificationConfig
|
||||
if (pushConfig != null) {
|
||||
val provider = pushConfig.provider
|
||||
val param = pushConfig.param
|
||||
val prid = pushConfig.prid
|
||||
if (provider.isNullOrEmpty() || param.isNullOrEmpty() || prid.isNullOrEmpty()) {
|
||||
Log.e(
|
||||
"$TAG At least one mandatory push information (provider [$provider], param [$param], prid [$prid]) is missing!"
|
||||
)
|
||||
onFlexiApiTokenRequestError()
|
||||
return
|
||||
}
|
||||
|
||||
// Request an auth token, will be sent by push
|
||||
val request = accountManagerServices.createSendAccountCreationTokenByPushRequest(
|
||||
provider,
|
||||
param,
|
||||
prid
|
||||
)
|
||||
request.addListener(accountManagerServicesListener)
|
||||
request.submit()
|
||||
|
||||
val waitFor = TIME_TO_WAIT_FOR_PUSH_NOTIFICATION_WITH_ACCOUNT_CREATION_TOKEN
|
||||
waitingForFlexiApiPushToken = true
|
||||
waitForPushJob?.cancel()
|
||||
|
||||
Log.i("$TAG Waiting push with auth token for $waitFor ms")
|
||||
waitForPushJob = viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
delay(waitFor.toLong())
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
if (waitingForFlexiApiPushToken) {
|
||||
waitingForFlexiApiPushToken = false
|
||||
Log.e("$TAG Auth token wasn't received by push in [$waitFor] ms")
|
||||
onFlexiApiTokenRequestError()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e("$TAG No push configuration object in Core, shouldn't happen!")
|
||||
onFlexiApiTokenRequestError()
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun onFlexiApiTokenRequestError() {
|
||||
Log.e("$TAG Flexi API token request by push error!")
|
||||
operationInProgress.postValue(false)
|
||||
showRedToast(R.string.assistant_account_register_push_notification_not_received_error, R.drawable.warning_circle)
|
||||
}
|
||||
}
|
||||
9
app/src/main/res/drawable/password.xml
Normal file
9
app/src/main/res/drawable/password.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:pathData="M48,56L48,200a8,8 0,0 1,-16 0L32,56a8,8 0,0 1,16 0ZM140,110.5L120,117L120,96a8,8 0,0 0,-16 0v21L84,110.5a8,8 0,0 0,-5 15.22l20,6.49 -12.34,17a8,8 0,1 0,12.94 9.4l12.34,-17 12.34,17a8,8 0,1 0,12.94 -9.4l-12.34,-17 20,-6.49A8,8 0,0 0,140 110.5ZM246,115.64A8,8 0,0 0,236 110.5L216,117L216,96a8,8 0,0 0,-16 0v21l-20,-6.49a8,8 0,0 0,-4.95 15.22l20,6.49 -12.34,17a8,8 0,1 0,12.94 9.4l12.34,-17 12.34,17a8,8 0,1 0,12.94 -9.4l-12.34,-17 20,-6.49A8,8 0,0 0,246 115.64Z"
|
||||
android:fillColor="#4e6074"/>
|
||||
</vector>
|
||||
|
|
@ -214,7 +214,7 @@
|
|||
android:onClick="@{qrCodeClickListener}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="22dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingStart="20dp"
|
||||
|
|
@ -235,7 +235,7 @@
|
|||
android:onClick="@{thirdPartySipAccountLoginClickListener}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingStart="20dp"
|
||||
|
|
|
|||
181
app/src/main/res/layout/assistant_recover_account_fragment.xml
Normal file
181
app/src/main/res/layout/assistant_recover_account_fragment.xml
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<?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"
|
||||
xmlns:bind="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<import type="android.text.InputType" />
|
||||
<variable
|
||||
name="backClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="recoverEmailAccountClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="recoverPhoneNumberAccountClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.assistant.viewmodel.RecoverPhoneAccountViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/color_main2_000">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:onClick="@{backClickListener}"
|
||||
android:id="@+id/back"
|
||||
android:layout_width="@dimen/top_bar_height"
|
||||
android:layout_height="@dimen/top_bar_height"
|
||||
android:padding="15dp"
|
||||
android:src="@drawable/caret_left"
|
||||
android:contentDescription="@string/content_description_go_back_icon"
|
||||
app:tint="?attr/color_main2_500"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/assistant_page_title_style"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/assistant_forgotten_password_title"
|
||||
android:textColor="?attr/color_text"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/password_icon"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="68dp"
|
||||
android:background="@drawable/circle_light_blue_button_background"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/password"
|
||||
android:contentDescription="@null"
|
||||
app:tint="?attr/color_main2_500"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/header_style"
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/assistant_forgotten_password_subtitle"
|
||||
app:layout_constraintTop_toBottomOf="@id/password_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:text="@string/assistant_forgotten_password_message"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?attr/color_main2_600"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_constraintTop_toBottomOf="@id/subtitle"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/primary_button_label_style"
|
||||
android:id="@+id/recover_email"
|
||||
android:onClick="@{recoverEmailAccountClickListener}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@string/assistant_recover_email_account_label"
|
||||
app:layout_constraintWidth_max="@dimen/button_max_width"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/message" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/primary_button_label_style"
|
||||
android:id="@+id/recover_phone_number"
|
||||
android:onClick="@{recoverPhoneNumberAccountClickListener}"
|
||||
android:enabled="@{viewModel.pushNotificationsAvailable}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@string/assistant_recover_phone_number_account_label"
|
||||
app:layout_constraintWidth_max="@dimen/button_max_width"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/recover_email" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/header_style"
|
||||
android:id="@+id/no_push_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:textAlignment="center"
|
||||
android:visibility="@{viewModel.pushNotificationsAvailable ? View.GONE : View.VISIBLE, default=gone}"
|
||||
android:text="@string/assistant_recover_phone_number_account_unavailable_no_push_warning"
|
||||
app:layout_constraintTop_toBottomOf="@id/recover_phone_number"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/mountains"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:src="@drawable/mountains"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintVertical_bias="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/no_push_label"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:tint="?attr/color_main1_500" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<include
|
||||
layout="@layout/operation_in_progress"
|
||||
bind:visibility="@{viewModel.operationInProgress}" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
<?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"
|
||||
xmlns:bind="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<import type="android.text.InputType" />
|
||||
<variable
|
||||
name="backClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.assistant.viewmodel.RecoverPhoneAccountViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:onClick="@{backClickListener}"
|
||||
android:id="@+id/back"
|
||||
android:layout_width="@dimen/top_bar_height"
|
||||
android:layout_height="@dimen/top_bar_height"
|
||||
android:padding="15dp"
|
||||
android:src="@drawable/caret_left"
|
||||
android:contentDescription="@string/content_description_go_back_icon"
|
||||
app:tint="?attr/color_main2_500"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/assistant_page_title_style"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/assistant_account_creation_sms_confirmation_title"
|
||||
android:textColor="?attr/color_text"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="100dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@{viewModel.confirmationMessage, default=@string/assistant_account_creation_sms_confirmation_explanation}"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?attr/color_main2_600"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/illu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="100dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/confirm_sms_code_illu"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintTop_toBottomOf="@id/wrong_number"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
focusNextOnInput="@{true}"
|
||||
style="@style/default_text_style_300"
|
||||
android:id="@+id/code_first_digit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:background="@drawable/shape_assistant_sms_code_confirmation"
|
||||
android:text="@={viewModel.smsCodeFirstDigit, default=`1`}"
|
||||
android:maxLength="1"
|
||||
android:textColor="@color/assistant_sms_confirmation_code_color"
|
||||
android:textSize="50sp"
|
||||
android:textCursorDrawable="@color/transparent_color"
|
||||
android:gravity="center"
|
||||
android:inputType="number"
|
||||
android:imeOptions="actionNext"
|
||||
android:nextFocusDown="@id/code_second_digit"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/message"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/code_second_digit" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
focusNextOnInput="@{true}"
|
||||
style="@style/default_text_style_300"
|
||||
android:id="@+id/code_second_digit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:background="@drawable/shape_assistant_sms_code_confirmation"
|
||||
android:text="@={viewModel.smsCodeSecondDigit, default=`2`}"
|
||||
android:maxLength="1"
|
||||
android:textColor="@color/assistant_sms_confirmation_code_color"
|
||||
android:textSize="50sp"
|
||||
android:textCursorDrawable="@color/transparent_color"
|
||||
android:gravity="center"
|
||||
android:inputType="number"
|
||||
android:imeOptions="actionNext"
|
||||
android:nextFocusDown="@id/code_third_digit"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/message"
|
||||
app:layout_constraintStart_toEndOf="@id/code_first_digit"
|
||||
app:layout_constraintEnd_toStartOf="@id/code_third_digit" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
focusNextOnInput="@{true}"
|
||||
style="@style/default_text_style_300"
|
||||
android:id="@+id/code_third_digit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:background="@drawable/shape_assistant_sms_code_confirmation"
|
||||
android:text="@={viewModel.smsCodeThirdDigit, default=`3`}"
|
||||
android:maxLength="1"
|
||||
android:textColor="@color/assistant_sms_confirmation_code_color"
|
||||
android:textSize="50sp"
|
||||
android:textCursorDrawable="@color/transparent_color"
|
||||
android:gravity="center"
|
||||
android:inputType="number"
|
||||
android:imeOptions="actionNext"
|
||||
android:nextFocusDown="@id/code_last_digit"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/message"
|
||||
app:layout_constraintStart_toEndOf="@id/code_second_digit"
|
||||
app:layout_constraintEnd_toStartOf="@id/code_last_digit" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
validateOnInput="@{() -> viewModel.validateCode()}"
|
||||
style="@style/default_text_style_300"
|
||||
android:id="@+id/code_last_digit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:background="@drawable/shape_assistant_sms_code_confirmation"
|
||||
android:text="@={viewModel.smsCodeLastDigit, default=`4`}"
|
||||
android:maxLength="1"
|
||||
android:textColor="@color/assistant_sms_confirmation_code_color"
|
||||
android:textSize="50sp"
|
||||
android:textCursorDrawable="@color/transparent_color"
|
||||
android:gravity="center"
|
||||
android:inputType="number"
|
||||
android:imeOptions="actionDone"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/message"
|
||||
app:layout_constraintStart_toEndOf="@id/code_third_digit"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style_600"
|
||||
android:id="@+id/wrong_number"
|
||||
android:onClick="@{backClickListener}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="51dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text="@string/assistant_account_creation_wrong_phone_number"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/secondary_button_label_color"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/secondary_button_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/code_first_digit" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/mountains"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:src="@drawable/mountains"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintVertical_bias="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/wrong_number"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:tint="?attr/color_main1_500" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<include
|
||||
layout="@layout/operation_in_progress"
|
||||
bind:visibility="@{viewModel.operationInProgress}" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
<?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"
|
||||
xmlns:bind="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<import type="android.text.InputType" />
|
||||
<variable
|
||||
name="backClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.assistant.viewmodel.RecoverPhoneAccountViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/color_main2_000">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:onClick="@{backClickListener}"
|
||||
android:id="@+id/back"
|
||||
android:layout_width="@dimen/top_bar_height"
|
||||
android:layout_height="@dimen/top_bar_height"
|
||||
android:padding="15dp"
|
||||
android:src="@drawable/caret_left"
|
||||
android:contentDescription="@string/content_description_go_back_icon"
|
||||
app:tint="?attr/color_main2_500"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/assistant_page_title_style"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/assistant_forgotten_password_title"
|
||||
android:textColor="?attr/color_text"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/password_icon"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="68dp"
|
||||
android:background="@drawable/circle_light_blue_button_background"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/password"
|
||||
android:contentDescription="@null"
|
||||
app:tint="?attr/color_main2_500"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/header_style"
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/assistant_recover_phone_number_account_subtitle"
|
||||
app:layout_constraintTop_toBottomOf="@id/password_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/header_style"
|
||||
android:id="@+id/phone_number_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@{@string/phone_number + `*`, default=`Phone number*`}"
|
||||
app:layout_constraintTop_toBottomOf="@id/subtitle"
|
||||
app:layout_constraintStart_toStartOf="@id/prefix"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/phone_number_background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:src="@{viewModel.phoneNumberError.length() > 0 ? @drawable/shape_edit_text_error_background : @drawable/edit_text_background, default=@drawable/edit_text_background}"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintStart_toStartOf="@id/prefix"
|
||||
app:layout_constraintEnd_toEndOf="@id/phone_number"
|
||||
app:layout_constraintTop_toTopOf="@id/prefix"
|
||||
app:layout_constraintBottom_toBottomOf="@id/prefix" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/prefix"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="25dp"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?attr/color_main2_600"
|
||||
android:gravity="center_vertical"
|
||||
android:overlapAnchor="false"
|
||||
android:dropDownVerticalOffset="25dp"
|
||||
android:spinnerMode="dropdown"
|
||||
android:popupBackground="@drawable/shape_squircle_white_background"
|
||||
android:background="@color/transparent_color"
|
||||
app:layout_constraintTop_toTopOf="@id/phone_number"
|
||||
app:layout_constraintBottom_toBottomOf="@id/phone_number"
|
||||
app:layout_constraintStart_toStartOf="@id/send_code" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/prefix_caret"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/caret_down"
|
||||
android:contentDescription="@null"
|
||||
app:tint="?attr/color_main2_600"
|
||||
app:layout_constraintTop_toTopOf="@id/prefix"
|
||||
app:layout_constraintBottom_toBottomOf="@id/prefix"
|
||||
app:layout_constraintEnd_toEndOf="@id/prefix"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/phone_number"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@={viewModel.phoneNumber, default=`6 01 02 03 04 05`}"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?attr/color_main2_600"
|
||||
android:inputType="phone"
|
||||
android:drawableStart="@drawable/separator"
|
||||
android:drawablePadding="10dp"
|
||||
android:hint="@string/phone_number"
|
||||
android:background="@color/transparent_color"
|
||||
app:layout_constraintWidth_max="@dimen/text_input_max_width"
|
||||
app:layout_constraintTop_toBottomOf="@id/phone_number_label"
|
||||
app:layout_constraintStart_toEndOf="@id/prefix_caret"
|
||||
app:layout_constraintEnd_toEndOf="@id/send_code"
|
||||
bind:ignore="RtlSymmetry" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style_600"
|
||||
android:visibility="@{viewModel.phoneNumberError.length() == 0 ? View.GONE : View.VISIBLE, default=gone}"
|
||||
android:id="@+id/phone_number_error"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{viewModel.phoneNumberError, default=`Error`}"
|
||||
android:textSize="13sp"
|
||||
android:textColor="?attr/color_danger_500"
|
||||
app:layout_constraintTop_toBottomOf="@id/prefix"
|
||||
app:layout_constraintStart_toStartOf="@id/prefix"
|
||||
app:layout_constraintEnd_toEndOf="@id/phone_number"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/primary_button_label_style"
|
||||
android:id="@+id/send_code"
|
||||
android:onClick="@{() -> viewModel.sendCode()}"
|
||||
android:enabled="@{viewModel.recoverEnabled && !viewModel.operationInProgress, default=false}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@string/assistant_recover_phone_number_account_send_code_label"
|
||||
app:layout_constraintWidth_max="@dimen/button_max_width"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/phone_number_error" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/mountains"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:src="@drawable/mountains"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintVertical_bias="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/send_code"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:tint="?attr/color_main1_500" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<include
|
||||
layout="@layout/operation_in_progress"
|
||||
bind:visibility="@{viewModel.operationInProgress}" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -144,6 +144,14 @@
|
|||
app:launchSingleTop="true"
|
||||
app:popUpTo="@id/landingFragment"
|
||||
app:popUpToInclusive="true"/>
|
||||
<action
|
||||
android:id="@+id/action_landingFragment_to_recoverAccountFragment"
|
||||
app:destination="@id/recoverAccountFragment"
|
||||
app:enterAnim="@anim/slide_in_right"
|
||||
app:exitAnim="@anim/slide_out_left"
|
||||
app:popEnterAnim="@anim/slide_in_left"
|
||||
app:popExitAnim="@anim/slide_out_right"
|
||||
app:launchSingleTop="true"/>
|
||||
|
||||
</fragment>
|
||||
|
||||
|
|
@ -169,4 +177,40 @@
|
|||
app:enterAnim="@anim/slide_in"
|
||||
app:popExitAnim="@anim/slide_out" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/recoverAccountFragment"
|
||||
android:name="org.linphone.ui.assistant.fragment.RecoverAccountFragment"
|
||||
android:label="RecoverAccountFragment"
|
||||
tools:layout="@layout/assistant_recover_account_fragment">
|
||||
<action
|
||||
android:id="@+id/action_recoverAccountFragment_to_recoverPhoneAccountFragment"
|
||||
app:destination="@id/recoverPhoneAccountFragment"
|
||||
app:enterAnim="@anim/slide_in_right"
|
||||
app:exitAnim="@anim/slide_out_left"
|
||||
app:popEnterAnim="@anim/slide_in_left"
|
||||
app:popExitAnim="@anim/slide_out_right"
|
||||
app:launchSingleTop="true"/>
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/recoverPhoneAccountFragment"
|
||||
android:name="org.linphone.ui.assistant.fragment.RecoverPhoneAccountFragment"
|
||||
android:label="RecoverPhoneAccountFragment"
|
||||
tools:layout="@layout/assistant_recover_phone_account_fragment">
|
||||
<action
|
||||
android:id="@+id/action_recoverPhoneAccountFragment_to_recoverPhoneAccountCodeConfirmationFragment"
|
||||
app:destination="@id/recoverPhoneAccountCodeConfirmationFragment"
|
||||
app:enterAnim="@anim/slide_in_right"
|
||||
app:exitAnim="@anim/slide_out_left"
|
||||
app:popEnterAnim="@anim/slide_in_left"
|
||||
app:popExitAnim="@anim/slide_out_right"
|
||||
app:launchSingleTop="true"/>
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/recoverPhoneAccountCodeConfirmationFragment"
|
||||
android:name="org.linphone.ui.assistant.fragment.RecoverPhoneAccountCodeConfirmationFragment"
|
||||
android:label="RecoverPhoneAccountCodeConfirmationFragment"
|
||||
tools:layout="@layout/assistant_recover_phone_account_confirm_sms_code_fragment"/>
|
||||
|
||||
</navigation>
|
||||
|
|
@ -133,6 +133,14 @@
|
|||
<string name="assistant_permissions_post_notifications_title"><b>Notifications :</b> Pour vous informer quand vous recevez un message ou un appel.</string>
|
||||
<string name="assistant_permissions_record_audio_title"><b>Microphone :</b> Pour permettre à vos correspondants de vous entendre. </string>
|
||||
<string name="assistant_permissions_access_camera_title"><b>Caméra :</b> Pour capturer votre vidéo lors des appels et des conférences.</string>
|
||||
<string name="assistant_forgotten_password_title">Mot de passe oublié</string>
|
||||
<string name="assistant_forgotten_password_subtitle">Choisissez comment récupérer votre compte.</string>
|
||||
<string name="assistant_forgotten_password_message">Vous avez créé votre compte avec :</string>
|
||||
<string name="assistant_recover_email_account_label">Un email</string>
|
||||
<string name="assistant_recover_phone_number_account_label">Un numéro de téléphone</string>
|
||||
<string name="assistant_recover_phone_number_account_subtitle">Veuillez saisir le numéro de téléphone lié à votre compte, nous allons vous envoyer un code.</string>
|
||||
<string name="assistant_recover_phone_number_account_send_code_label">Envoyer le code</string>
|
||||
<string name="assistant_recover_phone_number_account_unavailable_no_push_warning">Les notifications push ne semblent pas être disponibles sur votre appareil. Celles-ci sont nécessaires à la récupération d’un compte sur l’application mobile avec un numéro de téléphone.</string>
|
||||
|
||||
<!-- Main navigation items -->
|
||||
<string name="bottom_navigation_contacts_label">Contacts</string>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<string name="emoji_tear" translatable="false">😢</string>
|
||||
|
||||
<string name="help_about_open_source_licenses_title" translatable="false">GNU General Public License v3.0</string>
|
||||
<string name="help_about_open_source_licenses_subtitle" translatable="false">© Belledonne Communications 2010-2024</string>
|
||||
<string name="help_about_open_source_licenses_subtitle" translatable="false">© Belledonne Communications 2010-2025</string>
|
||||
<string name="help_advanced_send_debug_logs_email_address" translatable="false">linphone-android@belledonne-communications.com</string>
|
||||
|
||||
<string name="website_contact_url" translatable="false">https://linphone.org/contact</string>
|
||||
|
|
@ -173,6 +173,14 @@
|
|||
<string name="assistant_permissions_post_notifications_title"><b>Post notifications:</b> To be informed when you receive a message or a call.</string>
|
||||
<string name="assistant_permissions_record_audio_title"><b>Record audio:</b> So your correspondent can hear you and to record voice messages.</string>
|
||||
<string name="assistant_permissions_access_camera_title"><b>Access camera:</b> To capture video during video calls and conferences.</string>
|
||||
<string name="assistant_forgotten_password_title">Password forgotten</string>
|
||||
<string name="assistant_forgotten_password_subtitle">Choose how to recover your account.</string>
|
||||
<string name="assistant_forgotten_password_message">You created your account using:</string>
|
||||
<string name="assistant_recover_email_account_label">An email</string>
|
||||
<string name="assistant_recover_phone_number_account_label">A phone number</string>
|
||||
<string name="assistant_recover_phone_number_account_subtitle">Enter the phone number linked to your account, we\'ll send you a code.</string>
|
||||
<string name="assistant_recover_phone_number_account_send_code_label">Send the code</string>
|
||||
<string name="assistant_recover_phone_number_account_unavailable_no_push_warning">Push notifications do not seem to be available on your device, but they are mandatory for recovering a phone number account in the mobile app.</string>
|
||||
|
||||
<!-- Main navigation items -->
|
||||
<string name="bottom_navigation_contacts_label">Contacts</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue