mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Replaced AccountCreator by AccountManagerServices & using it to list account devices
This commit is contained in:
parent
54ee456f8e
commit
57f8ff3341
19 changed files with 466 additions and 378 deletions
|
|
@ -28,6 +28,6 @@
|
|||
</section>
|
||||
<section name="sip">
|
||||
<entry name="media_encryption" overwrite="true">zrtp</entry>
|
||||
<entry name="media_encryption_mandatory" overwrite="true">1</entry>
|
||||
<entry name="media_encryption_mandatory">1</entry>
|
||||
</section>
|
||||
</config>
|
||||
|
|
|
|||
|
|
@ -22,4 +22,12 @@
|
|||
<entry name="rtp_bundle" overwrite="true">1</entry>
|
||||
<entry name="lime_server_url" overwrite="true"></entry>
|
||||
</section>
|
||||
<section name="nat_policy_default_values">
|
||||
<entry name="stun_server" overwrite="true">stun.linphone.org</entry>
|
||||
<entry name="protocols" overwrite="true">stun,ice</entry>
|
||||
</section>
|
||||
<section name="sip">
|
||||
<entry name="media_encryption">srtp</entry>
|
||||
<entry name="media_encryption_mandatory" overwrite="true">0</entry>
|
||||
</section>
|
||||
</config>
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ automatically_accept_direction=2 #receive only
|
|||
|
||||
[app]
|
||||
tunnel=disabled
|
||||
auto_start=1
|
||||
record_aware=1
|
||||
auto_download_incoming_voice_recordings=1
|
||||
auto_download_incoming_icalendars=1
|
||||
|
||||
|
|
|
|||
|
|
@ -46,15 +46,10 @@ notify_each_friend_individually_when_presence_received=0
|
|||
store_friends=0
|
||||
|
||||
[app]
|
||||
activation_code_length=4
|
||||
prefer_basic_chat_room=1
|
||||
record_aware=1
|
||||
|
||||
[account_creator]
|
||||
backend=1
|
||||
# 1 means FlexiAPI, 0 is XMLRPC
|
||||
url=https://subscribe.linphone.org/api/
|
||||
# replace above URL by https://staging-subscribe.linphone.org/api/ for testing
|
||||
|
||||
[lime]
|
||||
lime_update_threshold=86400
|
||||
|
|
@ -62,9 +57,4 @@ lime_update_threshold=86400
|
|||
[alerts]
|
||||
alerts_enabled=1
|
||||
|
||||
[assistant]
|
||||
algorithm=SHA-256
|
||||
password_min_length=6
|
||||
username_regex=^[a-z0-9+_.\-]*$
|
||||
|
||||
## End of factory rc
|
||||
|
|
|
|||
|
|
@ -778,7 +778,7 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
|
|||
val deviceName = AppUtils.getDeviceName(context)
|
||||
val appName = context.getString(org.linphone.R.string.app_name)
|
||||
val androidVersion = BuildConfig.VERSION_NAME
|
||||
val userAgent = "$appName/$androidVersion ($deviceName) LinphoneSDK"
|
||||
val userAgent = "${appName}Android/$androidVersion ($deviceName) LinphoneSDK"
|
||||
val sdkVersion = context.getString(R.string.linphone_sdk_version)
|
||||
val sdkBranch = context.getString(R.string.linphone_sdk_branch)
|
||||
val sdkUserAgent = "$sdkVersion ($sdkBranch)"
|
||||
|
|
|
|||
|
|
@ -69,9 +69,8 @@ class RegisterCodeConfirmationFragment : GenericFragment() {
|
|||
viewModel.accountCreatedEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
val identity = viewModel.username.value.orEmpty()
|
||||
Log.i("$TAG Account [$identity] has been created")
|
||||
val action = RegisterCodeConfirmationFragmentDirections.actionRegisterCodeConfirmationFragmentToLandingFragment()
|
||||
findNavController().navigate(action)
|
||||
Log.i("$TAG Account [$identity] has been created, leaving assistant")
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ class RegisterFragment : GenericFragment() {
|
|||
|
||||
model.confirmPhoneNumberEvent.observe(viewLifecycleOwner) {
|
||||
it.consume {
|
||||
viewModel.requestToken()
|
||||
viewModel.startAccountCreation()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import androidx.annotation.WorkerThread
|
|||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -32,15 +33,16 @@ 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.AccountCreator.PhoneNumberStatus
|
||||
import org.linphone.core.AccountCreator.UsernameStatus
|
||||
import org.linphone.core.AccountCreatorListenerStub
|
||||
import org.linphone.core.Account
|
||||
import org.linphone.core.AccountManagerServices
|
||||
import org.linphone.core.AccountManagerServicesRequest
|
||||
import org.linphone.core.AccountManagerServicesRequestListenerStub
|
||||
import org.linphone.core.AuthInfo
|
||||
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
|
||||
|
|
@ -51,6 +53,9 @@ import org.linphone.utils.LinphoneUtils
|
|||
class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
||||
companion object {
|
||||
private const val TAG = "[Account Creation ViewModel]"
|
||||
|
||||
private const val TIME_TO_WAIT_FOR_PUSH_NOTIFICATION_WITH_ACCOUNT_CREATION_TOKEN = 5000
|
||||
private const val HASH_ALGORITHM = "SHA-256"
|
||||
}
|
||||
|
||||
val username = MutableLiveData<String>()
|
||||
|
|
@ -88,6 +93,7 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
|
||||
val operationInProgress = MutableLiveData<Boolean>()
|
||||
|
||||
private var normalizedPhoneNumber: String? = null
|
||||
val normalizedPhoneNumberEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
val goToSmsCodeConfirmationViewEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
|
@ -101,131 +107,102 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
private var waitingForFlexiApiPushToken = false
|
||||
private var waitForPushJob: Job? = null
|
||||
|
||||
private lateinit var accountCreator: AccountCreator
|
||||
private lateinit var accountManagerServices: AccountManagerServices
|
||||
private var accountCreationToken: String? = null
|
||||
|
||||
private val accountCreatorListener = object : AccountCreatorListenerStub() {
|
||||
private var accountCreatedAuthInfo: AuthInfo? = null
|
||||
private var accountCreated: Account? = null
|
||||
|
||||
private val accountManagerServicesListener = object : AccountManagerServicesRequestListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onIsAccountExist(
|
||||
creator: AccountCreator,
|
||||
status: AccountCreator.Status,
|
||||
response: String?
|
||||
override fun onRequestSuccessful(
|
||||
request: AccountManagerServicesRequest,
|
||||
data: String?
|
||||
) {
|
||||
Log.i("$TAG onIsAccountExist status [$status] ($response)")
|
||||
|
||||
when (status) {
|
||||
AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> {
|
||||
operationInProgress.postValue(false)
|
||||
createEnabled.postValue(false)
|
||||
|
||||
val error = AppUtils.getString(
|
||||
R.string.assistant_account_register_username_already_in_use_error
|
||||
)
|
||||
usernameError.postValue(error)
|
||||
}
|
||||
AccountCreator.Status.AccountNotExist -> {
|
||||
operationInProgress.postValue(false)
|
||||
checkPhoneNumber()
|
||||
}
|
||||
else -> {
|
||||
Log.e("$TAG An unexpected error occurred!")
|
||||
operationInProgress.postValue(false)
|
||||
createEnabled.postValue(false)
|
||||
|
||||
phoneNumberError.postValue(
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_invalid_phone_number_error
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onIsAliasUsed(
|
||||
creator: AccountCreator,
|
||||
status: AccountCreator.Status,
|
||||
response: String?
|
||||
) {
|
||||
Log.i("$TAG onIsAliasUsed status [$status] ($response)")
|
||||
when (status) {
|
||||
AccountCreator.Status.AliasExist, AccountCreator.Status.AliasIsAccount -> {
|
||||
operationInProgress.postValue(false)
|
||||
createEnabled.postValue(false)
|
||||
|
||||
val error = AppUtils.getString(
|
||||
R.string.assistant_account_register_phone_number_already_in_use_error
|
||||
)
|
||||
phoneNumberError.postValue(error)
|
||||
}
|
||||
AccountCreator.Status.AliasNotExist -> {
|
||||
operationInProgress.postValue(false)
|
||||
createAccount()
|
||||
}
|
||||
else -> {
|
||||
Log.e("$TAG An unexpected error occurred!")
|
||||
operationInProgress.postValue(false)
|
||||
createEnabled.postValue(false)
|
||||
|
||||
phoneNumberError.postValue(
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_invalid_phone_number_error
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onCreateAccount(
|
||||
creator: AccountCreator,
|
||||
status: AccountCreator.Status,
|
||||
response: String?
|
||||
) {
|
||||
Log.i("$TAG onCreateAccount status [$status] ($response)")
|
||||
accountCreator.token = null
|
||||
Log.i("$TAG Request [$request] was successful, data is [$data]")
|
||||
operationInProgress.postValue(false)
|
||||
|
||||
when (status) {
|
||||
AccountCreator.Status.AccountCreated -> {
|
||||
when (request.type) {
|
||||
AccountManagerServicesRequest.Type.CreateAccountUsingToken -> {
|
||||
if (!data.isNullOrEmpty()) {
|
||||
storeAccountInCore(data)
|
||||
sendCodeBySms()
|
||||
} else {
|
||||
Log.e(
|
||||
"$TAG No data found for createAccountUsingToken request, can't continue!"
|
||||
)
|
||||
}
|
||||
}
|
||||
AccountManagerServicesRequest.Type.SendPhoneNumberLinkingCodeBySms -> {
|
||||
goToSmsCodeConfirmationViewEvent.postValue(Event(true))
|
||||
}
|
||||
else -> {
|
||||
Log.e("$TAG Account couldn't be created, an unexpected error occurred!")
|
||||
errorHappenedEvent.postValue(
|
||||
Event(
|
||||
AppUtils.getFormattedString(
|
||||
R.string.assistant_account_register_server_error,
|
||||
status.toInt()
|
||||
)
|
||||
AccountManagerServicesRequest.Type.LinkPhoneNumberUsingCode -> {
|
||||
val account = accountCreated
|
||||
if (account != null) {
|
||||
Log.i(
|
||||
"$TAG Account [${account.params.identityAddress?.asStringUriOnly()}] has been created & activated, setting it as default"
|
||||
)
|
||||
)
|
||||
coreContext.core.defaultAccount = account
|
||||
}
|
||||
accountCreatedEvent.postValue(Event(true))
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onActivateAccount(
|
||||
creator: AccountCreator,
|
||||
status: AccountCreator.Status,
|
||||
response: String?
|
||||
override fun onRequestError(
|
||||
request: AccountManagerServicesRequest,
|
||||
statusCode: Int,
|
||||
errorMessage: String?,
|
||||
parameterErrors: Dictionary?
|
||||
) {
|
||||
Log.i("$TAG onActivateAccount status [$status] ($response)")
|
||||
Log.e(
|
||||
"$TAG Request [$request] returned an error with status code [$statusCode] and message [$errorMessage]"
|
||||
)
|
||||
operationInProgress.postValue(false)
|
||||
|
||||
if (status == AccountCreator.Status.AccountActivated) {
|
||||
Log.i("$TAG Account has been successfully activated, going to login page")
|
||||
accountCreatedEvent.postValue(Event(true))
|
||||
} else {
|
||||
Log.e("$TAG Account couldn't be activated, an unexpected error occurred!")
|
||||
errorHappenedEvent.postValue(
|
||||
if (!errorMessage.isNullOrEmpty()) {
|
||||
showFormattedRedToastEvent.postValue(
|
||||
Event(
|
||||
AppUtils.getFormattedString(
|
||||
R.string.assistant_account_register_server_error,
|
||||
status.toInt()
|
||||
Pair(
|
||||
errorMessage,
|
||||
R.drawable.warning_circle
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
for (parameter in parameterErrors?.keys.orEmpty()) {
|
||||
val parameterErrorMessage = parameterErrors?.getString(parameter) ?: ""
|
||||
when (parameter) {
|
||||
"username" -> usernameError.postValue(parameterErrorMessage)
|
||||
"password" -> passwordError.postValue(parameterErrorMessage)
|
||||
"phone" -> phoneNumberError.postValue(parameterErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
when (request.type) {
|
||||
AccountManagerServicesRequest.Type.SendAccountCreationTokenByPush -> {
|
||||
Log.w("$TAG Cancelling job waiting for push notification")
|
||||
waitingForFlexiApiPushToken = false
|
||||
waitForPushJob?.cancel()
|
||||
}
|
||||
AccountManagerServicesRequest.Type.SendPhoneNumberLinkingCodeBySms -> {
|
||||
val authInfo = accountCreatedAuthInfo
|
||||
if (authInfo != null) {
|
||||
coreContext.core.removeAuthInfo(authInfo)
|
||||
}
|
||||
val account = accountCreated
|
||||
if (account != null) {
|
||||
coreContext.core.removeAccount(account)
|
||||
}
|
||||
createEnabled.postValue(true)
|
||||
}
|
||||
else -> {
|
||||
createEnabled.postValue(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,9 +230,11 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
|
||||
val token = customPayload.getString("token")
|
||||
if (token.isNotEmpty()) {
|
||||
Log.i("$TAG Extracted token [$token] from push payload")
|
||||
accountCreator.token = token
|
||||
checkUsername()
|
||||
accountCreationToken = token
|
||||
Log.i(
|
||||
"$TAG Extracted token [$accountCreationToken] from push payload, creating account"
|
||||
)
|
||||
createAccount()
|
||||
} else {
|
||||
Log.e("$TAG Push payload JSON object has an empty 'token'!")
|
||||
onFlexiApiTokenRequestError()
|
||||
|
|
@ -292,8 +271,8 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
)
|
||||
}
|
||||
|
||||
accountCreator = core.createAccountCreator(core.accountCreatorUrl)
|
||||
accountCreator.addListener(accountCreatorListener)
|
||||
accountManagerServices = core.createAccountManagerServices()
|
||||
accountManagerServices.language = Locale.getDefault().language // Returns en, fr, etc...
|
||||
core.addListener(coreListener)
|
||||
}
|
||||
|
||||
|
|
@ -316,9 +295,6 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
@UiThread
|
||||
override fun onCleared() {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
if (::accountCreator.isInitialized) {
|
||||
accountCreator.removeListener(accountCreatorListener)
|
||||
}
|
||||
core.removeListener(coreListener)
|
||||
}
|
||||
waitForPushJob?.cancel()
|
||||
|
|
@ -327,79 +303,29 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
}
|
||||
|
||||
@UiThread
|
||||
fun confirmPhoneNumber() {
|
||||
fun phoneNumberConfirmedByUser() {
|
||||
coreContext.postOnCoreThread {
|
||||
if (::accountCreator.isInitialized) {
|
||||
if (::accountManagerServices.isInitialized) {
|
||||
val dialPlan = selectedDialPlan.value
|
||||
val prefix = dialPlan?.countryCallingCode.orEmpty()
|
||||
val digitsPrefix = if (prefix.startsWith("+")) {
|
||||
prefix.substring(1)
|
||||
} else {
|
||||
prefix
|
||||
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 status = accountCreator.setPhoneNumber(number, digitsPrefix)
|
||||
Log.i("$TAG setPhoneNumber returned $status")
|
||||
if (status == PhoneNumberStatus.Ok.toInt()) {
|
||||
val normalizedPhoneNumber = accountCreator.phoneNumber
|
||||
|
||||
val message = coreContext.context.getString(
|
||||
R.string.assistant_account_creation_sms_confirmation_explanation,
|
||||
normalizedPhoneNumber
|
||||
)
|
||||
confirmationMessage.postValue(message)
|
||||
|
||||
Log.i(
|
||||
"$TAG Normalized phone number from [$number] and prefix [$digitsPrefix] is [$normalizedPhoneNumber]"
|
||||
)
|
||||
if (!normalizedPhoneNumber.isNullOrEmpty()) {
|
||||
normalizedPhoneNumberEvent.postValue(Event(normalizedPhoneNumber))
|
||||
} else {
|
||||
Log.e(
|
||||
"$TAG Failed to compute phone number using international prefix [$digitsPrefix] and number [$number]"
|
||||
)
|
||||
|
||||
val error = AppUtils.getString(
|
||||
R.string.assistant_account_register_invalid_phone_number_error
|
||||
)
|
||||
phoneNumberError.postValue(error)
|
||||
}
|
||||
} else {
|
||||
Log.e(
|
||||
"$TAG Failed to set phone number [$number] and prefix [$digitsPrefix] into account creator!"
|
||||
)
|
||||
val error = when (status) {
|
||||
PhoneNumberStatus.Invalid.toInt() -> {
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_invalid_phone_number_error
|
||||
)
|
||||
}
|
||||
PhoneNumberStatus.InvalidCountryCode.toInt() -> {
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_invalid_phone_number_international_prefix_error
|
||||
)
|
||||
}
|
||||
PhoneNumberStatus.TooLong.toInt() -> {
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_invalid_phone_number_too_long_error
|
||||
)
|
||||
}
|
||||
PhoneNumberStatus.TooShort.toInt() -> {
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_invalid_phone_number_too_short_error
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_invalid_phone_number_error
|
||||
)
|
||||
}
|
||||
}
|
||||
phoneNumberError.postValue(error)
|
||||
}
|
||||
val message = coreContext.context.getString(
|
||||
R.string.assistant_account_creation_sms_confirmation_explanation,
|
||||
formattedPhoneNumber
|
||||
)
|
||||
normalizedPhoneNumber = formattedPhoneNumber
|
||||
confirmationMessage.postValue(message)
|
||||
normalizedPhoneNumberEvent.postValue(Event(formattedPhoneNumber))
|
||||
} else {
|
||||
Log.e("$TAG Account creator hasn't been initialized!")
|
||||
Log.e("$TAG Account manager services hasn't been initialized!")
|
||||
errorHappenedEvent.postValue(
|
||||
Event(AppUtils.getString(R.string.assistant_account_register_unexpected_error))
|
||||
)
|
||||
|
|
@ -408,16 +334,22 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
}
|
||||
|
||||
@UiThread
|
||||
fun requestToken() {
|
||||
fun startAccountCreation() {
|
||||
operationInProgress.value = true
|
||||
|
||||
coreContext.postOnCoreThread {
|
||||
if (accountCreator.token == null) {
|
||||
if (accountCreationToken.isNullOrEmpty()) {
|
||||
Log.i("$TAG We don't have a creation token, let's request one")
|
||||
requestFlexiApiToken()
|
||||
} else {
|
||||
Log.i("$TAG We've already have a token [${accountCreator.token}], continuing")
|
||||
checkUsername()
|
||||
val authInfo = accountCreatedAuthInfo
|
||||
if (authInfo != null) {
|
||||
Log.i("$TAG Account has already been created, requesting SMS to be sent")
|
||||
sendCodeBySms()
|
||||
} else {
|
||||
Log.i("$TAG We've already have a token [$accountCreationToken], continuing")
|
||||
createAccount()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -434,141 +366,144 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
|
||||
@UiThread
|
||||
fun validateCode() {
|
||||
usernameError.postValue("")
|
||||
passwordError.postValue("")
|
||||
phoneNumberError.postValue("")
|
||||
operationInProgress.value = true
|
||||
|
||||
val code = "${smsCodeFirstDigit.value}${smsCodeSecondDigit.value}${smsCodeThirdDigit.value}${smsCodeLastDigit.value}"
|
||||
Log.i("$TAG Activating account using code [$code]")
|
||||
accountCreator.activationCode = code
|
||||
|
||||
coreContext.postOnCoreThread {
|
||||
val status = accountCreator.activateAccount()
|
||||
Log.i("$TAG activateAccount returned $status")
|
||||
if (status != AccountCreator.Status.RequestOk) {
|
||||
Log.e("$TAG Can't activate account [$status]")
|
||||
operationInProgress.postValue(false)
|
||||
errorHappenedEvent.postValue(
|
||||
Event(
|
||||
AppUtils.getFormattedString(
|
||||
R.string.assistant_account_register_server_error,
|
||||
status.toInt()
|
||||
)
|
||||
)
|
||||
val account = accountCreated
|
||||
if (::accountManagerServices.isInitialized && account != null) {
|
||||
val code =
|
||||
"${smsCodeFirstDigit.value}${smsCodeSecondDigit.value}${smsCodeThirdDigit.value}${smsCodeLastDigit.value}"
|
||||
val identity = account.params.identityAddress
|
||||
if (identity != null) {
|
||||
Log.i(
|
||||
"$TAG Activating account using code [$code] for account [${identity.asStringUriOnly()}]"
|
||||
)
|
||||
val request = accountManagerServices.createLinkPhoneNumberToAccountUsingCodeRequest(
|
||||
identity,
|
||||
code
|
||||
)
|
||||
request.addListener(accountManagerServicesListener)
|
||||
request.submit()
|
||||
|
||||
// Reset code
|
||||
smsCodeFirstDigit.postValue("")
|
||||
smsCodeSecondDigit.postValue("")
|
||||
smsCodeThirdDigit.postValue("")
|
||||
smsCodeLastDigit.postValue("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun checkUsername() {
|
||||
operationInProgress.postValue(true)
|
||||
|
||||
private fun sendCodeBySms() {
|
||||
usernameError.postValue("")
|
||||
val usernameStatus = accountCreator.setUsername(username.value.orEmpty().trim())
|
||||
Log.i("$TAG setUsername returned $usernameStatus")
|
||||
if (usernameStatus != UsernameStatus.Ok) {
|
||||
val error = when (usernameStatus) {
|
||||
UsernameStatus.InvalidCharacters, UsernameStatus.Invalid -> {
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_username_invalid_characters_error
|
||||
)
|
||||
}
|
||||
UsernameStatus.TooShort -> {
|
||||
AppUtils.getString(R.string.assistant_account_register_username_too_short_error)
|
||||
}
|
||||
UsernameStatus.TooLong -> {
|
||||
AppUtils.getString(R.string.assistant_account_register_username_too_long_error)
|
||||
}
|
||||
else -> {
|
||||
AppUtils.getString(R.string.assistant_account_register_username_error)
|
||||
}
|
||||
passwordError.postValue("")
|
||||
phoneNumberError.postValue("")
|
||||
|
||||
val account = accountCreated
|
||||
if (::accountManagerServices.isInitialized && account != null) {
|
||||
val phoneNumberValue = normalizedPhoneNumber
|
||||
if (phoneNumberValue.isNullOrEmpty()) {
|
||||
Log.e("$TAG Phone number is null or empty, this shouldn't happen at this step!")
|
||||
return
|
||||
}
|
||||
usernameError.postValue(error)
|
||||
operationInProgress.postValue(false)
|
||||
return
|
||||
}
|
||||
|
||||
accountCreator.domain = corePreferences.defaultDomain
|
||||
operationInProgress.postValue(true)
|
||||
createEnabled.postValue(false)
|
||||
|
||||
val status = accountCreator.isAccountExist
|
||||
Log.i("$TAG isAccountExist for username [${accountCreator.username}] returned $status")
|
||||
if (status != AccountCreator.Status.RequestOk) {
|
||||
Log.e("$TAG Can't check if account already exists [$status]")
|
||||
operationInProgress.postValue(false)
|
||||
errorHappenedEvent.postValue(
|
||||
Event(
|
||||
AppUtils.getFormattedString(
|
||||
R.string.assistant_account_register_server_error,
|
||||
status.toInt()
|
||||
)
|
||||
val identity = account.params.identityAddress
|
||||
if (identity != null) {
|
||||
Log.i(
|
||||
"$TAG Account [${identity.asStringUriOnly()}] should now be created, asking account manager to send a confirmation code by SMS to [$phoneNumberValue]"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun checkPhoneNumber() {
|
||||
operationInProgress.postValue(true)
|
||||
|
||||
val status = accountCreator.isAliasUsed
|
||||
Log.i("$TAG isAliasUsed returned $status")
|
||||
if (status != AccountCreator.Status.RequestOk) {
|
||||
Log.e("$TAG Can't check if phone number is already used [$status]")
|
||||
operationInProgress.postValue(false)
|
||||
errorHappenedEvent.postValue(
|
||||
Event(
|
||||
AppUtils.getFormattedString(
|
||||
R.string.assistant_account_register_server_error,
|
||||
status.toInt()
|
||||
)
|
||||
val request = accountManagerServices.createSendPhoneNumberLinkingCodeBySmsRequest(
|
||||
identity,
|
||||
phoneNumberValue
|
||||
)
|
||||
)
|
||||
request.addListener(accountManagerServicesListener)
|
||||
request.submit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun createAccount() {
|
||||
operationInProgress.postValue(true)
|
||||
usernameError.postValue("")
|
||||
passwordError.postValue("")
|
||||
phoneNumberError.postValue("")
|
||||
|
||||
val passwordStatus = accountCreator.setPassword(password.value.orEmpty().trim())
|
||||
Log.i("$TAG setPassword returned $passwordStatus")
|
||||
if (passwordStatus != AccountCreator.PasswordStatus.Ok) {
|
||||
val error = when (passwordStatus) {
|
||||
AccountCreator.PasswordStatus.InvalidCharacters -> {
|
||||
AppUtils.getString(
|
||||
R.string.assistant_account_register_password_invalid_characters_error
|
||||
)
|
||||
}
|
||||
AccountCreator.PasswordStatus.TooShort -> {
|
||||
AppUtils.getString(R.string.assistant_account_register_password_too_short)
|
||||
}
|
||||
AccountCreator.PasswordStatus.TooLong -> {
|
||||
AppUtils.getString(R.string.assistant_account_register_password_too_long_error)
|
||||
}
|
||||
else -> {
|
||||
AppUtils.getString(R.string.assistant_account_register_invalid_password_error)
|
||||
}
|
||||
if (::accountManagerServices.isInitialized) {
|
||||
val token = accountCreationToken
|
||||
if (token.isNullOrEmpty()) {
|
||||
Log.e("$TAG No account creation token, can't create account!")
|
||||
return
|
||||
}
|
||||
passwordError.postValue(error)
|
||||
operationInProgress.postValue(false)
|
||||
|
||||
operationInProgress.postValue(true)
|
||||
createEnabled.postValue(false)
|
||||
|
||||
val usernameValue = username.value
|
||||
val passwordValue = password.value
|
||||
if (usernameValue.isNullOrEmpty() || passwordValue.isNullOrEmpty()) {
|
||||
Log.e("$TAG Either username [$usernameValue] or password is null or empty!")
|
||||
return
|
||||
}
|
||||
|
||||
Log.i(
|
||||
"$TAG Account creation token is [$token], creating account with username [$usernameValue] and algorithm SHA-256"
|
||||
)
|
||||
val request = accountManagerServices.createNewAccountUsingTokenRequest(
|
||||
usernameValue,
|
||||
passwordValue,
|
||||
HASH_ALGORITHM,
|
||||
token
|
||||
)
|
||||
request.addListener(accountManagerServicesListener)
|
||||
request.submit()
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun storeAccountInCore(identity: String) {
|
||||
val passwordValue = password.value
|
||||
|
||||
val core = coreContext.core
|
||||
val sipIdentity = Factory.instance().createAddress(identity)
|
||||
if (sipIdentity == null) {
|
||||
Log.e("$TAG Failed to create address from SIP Identity [$identity]!")
|
||||
return
|
||||
}
|
||||
|
||||
val status = accountCreator.createAccount() // TODO FIXME: use createPushAccount instead ?
|
||||
Log.i("$TAG createAccount returned $status")
|
||||
if (status != AccountCreator.Status.RequestOk) {
|
||||
Log.e("$TAG Can't create account [$status]")
|
||||
operationInProgress.postValue(false)
|
||||
errorHappenedEvent.postValue(
|
||||
Event(
|
||||
AppUtils.getFormattedString(
|
||||
R.string.assistant_account_register_server_error,
|
||||
status.toInt()
|
||||
)
|
||||
)
|
||||
// We need to have an AuthInfo for newly created account to authorize phone number linking request
|
||||
val authInfo = Factory.instance().createAuthInfo(
|
||||
sipIdentity.username.orEmpty(),
|
||||
null,
|
||||
passwordValue,
|
||||
null,
|
||||
null,
|
||||
sipIdentity.domain
|
||||
)
|
||||
core.addAuthInfo(authInfo)
|
||||
Log.i("$TAG Auth info for SIP identity [${sipIdentity.asStringUriOnly()}] created & added")
|
||||
|
||||
val dialPlan = selectedDialPlan.value
|
||||
val accountParams = core.createAccountParams()
|
||||
accountParams.identityAddress = sipIdentity
|
||||
if (dialPlan != null) {
|
||||
Log.i(
|
||||
"$TAG Setting international prefix [${dialPlan.internationalCallPrefix}] and country [${dialPlan.isoCountryCode}] to account params"
|
||||
)
|
||||
} else {
|
||||
Log.i("$TAG createAccount consumed our token, setting it to null")
|
||||
accountCreator.token = null
|
||||
accountParams.internationalPrefix = dialPlan.internationalCallPrefix
|
||||
accountParams.internationalPrefixIsoCountryCode = dialPlan.isoCountryCode
|
||||
}
|
||||
val account = core.createAccount(accountParams)
|
||||
core.addAccount(account)
|
||||
Log.i("$TAG Account for SIP identity [${sipIdentity.asStringUriOnly()}] created & added")
|
||||
|
||||
accountCreatedAuthInfo = authInfo
|
||||
accountCreated = account
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
@ -582,39 +517,46 @@ class AccountCreationViewModel @UiThread constructor() : GenericViewModel() {
|
|||
}
|
||||
|
||||
operationInProgress.postValue(true)
|
||||
createEnabled.postValue(false)
|
||||
|
||||
val pushConfig = coreContext.core.pushNotificationConfig
|
||||
if (pushConfig != null) {
|
||||
Log.i(
|
||||
"$TAG Found push notification info: provider [${pushConfig.provider}], param [${pushConfig.param}] and prid [${pushConfig.prid}]"
|
||||
)
|
||||
accountCreator.pnProvider = pushConfig.provider
|
||||
accountCreator.pnParam = pushConfig.param
|
||||
accountCreator.pnPrid = pushConfig.prid
|
||||
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 result = accountCreator.requestAuthToken()
|
||||
if (result == AccountCreator.Status.RequestOk) {
|
||||
val waitFor = 5000
|
||||
waitingForFlexiApiPushToken = true
|
||||
waitForPushJob?.cancel()
|
||||
val request = accountManagerServices.createSendAccountCreationTokenByPushRequest(
|
||||
provider,
|
||||
param,
|
||||
prid
|
||||
)
|
||||
request.addListener(accountManagerServicesListener)
|
||||
request.submit()
|
||||
|
||||
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()
|
||||
}
|
||||
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 Failed to require a push with an auth token: [$result]")
|
||||
onFlexiApiTokenRequestError()
|
||||
}
|
||||
} else {
|
||||
Log.e("$TAG No push configuration object in Core, shouldn't happen!")
|
||||
|
|
|
|||
|
|
@ -20,15 +20,49 @@
|
|||
package org.linphone.ui.main.settings.model
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import java.time.ZonedDateTime
|
||||
import org.linphone.core.AccountDevice
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.TimestampUtils
|
||||
|
||||
class AccountDeviceModel @WorkerThread constructor(
|
||||
val name: String,
|
||||
val lastConnectionDate: String,
|
||||
val lastConnectionTime: String,
|
||||
private val onRemove: () -> (Unit)
|
||||
private val accountDevice: AccountDevice,
|
||||
private val onRemove: (accountDevice: AccountDevice) -> (Unit)
|
||||
) {
|
||||
companion object {
|
||||
const val TAG = "[Account Device Model]"
|
||||
}
|
||||
|
||||
val name = accountDevice.name
|
||||
val timestamp = if (accountDevice.lastUpdateTimestamp == 0L) {
|
||||
Log.w("$TAG SDK failed to parse [${accountDevice.lastUpdateTimestamp}] as time_t!")
|
||||
try {
|
||||
ZonedDateTime.parse(accountDevice.lastUpdateTime).toEpochSecond()
|
||||
} catch (e: Exception) {
|
||||
Log.e("$TAG Failed to parse [${accountDevice.lastUpdateTime}] as ZonedDateTime!")
|
||||
0L
|
||||
}
|
||||
} else {
|
||||
accountDevice.lastUpdateTimestamp
|
||||
}
|
||||
val lastConnectionDate = TimestampUtils.toString(
|
||||
timestamp,
|
||||
onlyDate = true,
|
||||
shortDate = true,
|
||||
hideYear = false
|
||||
)
|
||||
val lastConnectionTime = TimestampUtils.timeToString(timestamp)
|
||||
val isMobileDevice = accountDevice.userAgent.contains("LinphoneAndroid") || accountDevice.userAgent.contains(
|
||||
"LinphoneiOS"
|
||||
)
|
||||
|
||||
init {
|
||||
Log.d(
|
||||
"$TAG Device's [$name] last update timestamp is [$timestamp] ($lastConnectionDate - $lastConnectionTime)"
|
||||
)
|
||||
}
|
||||
|
||||
fun removeDevice() {
|
||||
onRemove()
|
||||
onRemove(accountDevice)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,19 @@
|
|||
package org.linphone.ui.main.settings.viewmodel
|
||||
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import java.util.Locale
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.core.Account
|
||||
import org.linphone.core.AccountDevice
|
||||
import org.linphone.core.AccountManagerServices
|
||||
import org.linphone.core.AccountManagerServicesRequest
|
||||
import org.linphone.core.AccountManagerServicesRequestListenerStub
|
||||
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
|
||||
|
|
@ -62,6 +70,8 @@ class AccountProfileViewModel @UiThread constructor() : GenericViewModel() {
|
|||
|
||||
val expandDevices = MutableLiveData<Boolean>()
|
||||
|
||||
val devicesAvailable = MutableLiveData<Boolean>()
|
||||
|
||||
val hideAccountSettings = MutableLiveData<Boolean>()
|
||||
|
||||
val accountRemovedEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
|
|
@ -70,9 +80,72 @@ class AccountProfileViewModel @UiThread constructor() : GenericViewModel() {
|
|||
|
||||
private lateinit var account: Account
|
||||
|
||||
private lateinit var accountManagerServices: AccountManagerServices
|
||||
|
||||
private val accountManagerServicesListener = object : AccountManagerServicesRequestListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onDevicesListFetched(
|
||||
request: AccountManagerServicesRequest,
|
||||
accountDevices: Array<out AccountDevice>
|
||||
) {
|
||||
Log.i("$TAG Fetched [${accountDevices.size}] devices for our account")
|
||||
val devicesList = arrayListOf<AccountDeviceModel>()
|
||||
for (accountDevice in accountDevices) {
|
||||
devicesList.add(
|
||||
AccountDeviceModel(accountDevice) { device ->
|
||||
if (::accountManagerServices.isInitialized) {
|
||||
val identityAddress = account.params.identityAddress
|
||||
if (identityAddress != null) {
|
||||
Log.i(
|
||||
"$TAG Removing device with name [${device.name}] and uuid [${device.uuid}]"
|
||||
)
|
||||
val deleteRequest = accountManagerServices.createDeleteDeviceRequest(
|
||||
identityAddress,
|
||||
device
|
||||
)
|
||||
deleteRequest.addListener(this)
|
||||
deleteRequest.submit()
|
||||
} else {
|
||||
Log.e("$TAG Account identity address is null, can't delete device!")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
devices.postValue(devicesList)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onRequestError(
|
||||
request: AccountManagerServicesRequest,
|
||||
statusCode: Int,
|
||||
errorMessage: String?,
|
||||
parameterErrors: Dictionary?
|
||||
) {
|
||||
Log.e(
|
||||
"$TAG Request [${request.type}] returned an error with status code [$statusCode] and message [$errorMessage]"
|
||||
)
|
||||
if (!errorMessage.isNullOrEmpty()) {
|
||||
when (request.type) {
|
||||
AccountManagerServicesRequest.Type.GetDevicesList, AccountManagerServicesRequest.Type.DeleteDevice -> {
|
||||
showFormattedRedToastEvent.postValue(
|
||||
Event(
|
||||
Pair(
|
||||
errorMessage,
|
||||
R.drawable.warning_circle
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
expandDetails.value = true
|
||||
expandDevices.value = false // TODO: set to true when feature will be available
|
||||
expandDevices.value = false
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
hideAccountSettings.postValue(corePreferences.hideAccountSettings)
|
||||
|
|
@ -92,7 +165,7 @@ class AccountProfileViewModel @UiThread constructor() : GenericViewModel() {
|
|||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
coreContext.postOnCoreThread {
|
||||
accountModel.value?.destroy()
|
||||
}
|
||||
}
|
||||
|
|
@ -113,9 +186,30 @@ class AccountProfileViewModel @UiThread constructor() : GenericViewModel() {
|
|||
sipAddress.postValue(account.params.identityAddress?.asStringUriOnly())
|
||||
displayName.postValue(account.params.identityAddress?.displayName)
|
||||
|
||||
val devicesList = arrayListOf<AccountDeviceModel>()
|
||||
// TODO FIXME: use real devices list from API, not implemented yet
|
||||
devices.postValue(devicesList)
|
||||
val identityAddress = account.params.identityAddress
|
||||
if (identityAddress != null) {
|
||||
val domain = identityAddress.domain
|
||||
val defaultDomain = corePreferences.defaultDomain
|
||||
devicesAvailable.postValue(domain == defaultDomain)
|
||||
if (domain == defaultDomain) {
|
||||
Log.i(
|
||||
"$TAG Request list of known devices for account [${identityAddress.asStringUriOnly()}]"
|
||||
)
|
||||
accountManagerServices = core.createAccountManagerServices()
|
||||
accountManagerServices.language = Locale.getDefault().language // Returns en, fr, etc...
|
||||
val request = accountManagerServices.createGetDevicesListRequest(
|
||||
identityAddress
|
||||
)
|
||||
request.addListener(accountManagerServicesListener)
|
||||
request.submit()
|
||||
} else {
|
||||
Log.i(
|
||||
"$TAG Account with domain [$domain] can't get devices list, only works with [$defaultDomain] domain"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Log.e("$TAG No identity address found!")
|
||||
}
|
||||
|
||||
val prefix = account.params.internationalPrefix
|
||||
val isoCountryCode = account.params.internationalPrefixIsoCountryCode
|
||||
|
|
|
|||
|
|
@ -253,14 +253,12 @@ class MainViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
@WorkerThread
|
||||
override fun onAccountRemoved(core: Core, account: Account) {
|
||||
accountsFound -= 1
|
||||
|
||||
if (core.defaultAccount == null) {
|
||||
Log.i(
|
||||
"$TAG Default account was removed, setting first available account (if any) as default"
|
||||
)
|
||||
core.defaultAccount = core.accountList.firstOrNull()
|
||||
}
|
||||
Log.w(
|
||||
"$TAG Account [${account.params.identityAddress?.asStringUriOnly()}] has been removed!"
|
||||
)
|
||||
removeAlert(NON_DEFAULT_ACCOUNT_NOT_CONNECTED)
|
||||
core.refreshRegisters()
|
||||
computeNonDefaultAccountNotificationsCount()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
9
app/src/main/res/drawable/desktop.xml
Normal file
9
app/src/main/res/drawable/desktop.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="M208,40H48A24,24 0,0 0,24 64V176a24,24 0,0 0,24 24h72v16H96a8,8 0,0 0,0 16h64a8,8 0,0 0,0 -16H136V200h72a24,24 0,0 0,24 -24V64A24,24 0,0 0,208 40ZM48,56H208a8,8 0,0 1,8 8v80H40V64A8,8 0,0 1,48 56ZM208,184H48a8,8 0,0 1,-8 -8V160H216v16A8,8 0,0 1,208 184Z"
|
||||
android:fillColor="#4e6074"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/device_mobile_camera.xml
Normal file
9
app/src/main/res/drawable/device_mobile_camera.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="M176,16L80,16A24,24 0,0 0,56 40L56,216a24,24 0,0 0,24 24h96a24,24 0,0 0,24 -24L200,40A24,24 0,0 0,176 16ZM184,216a8,8 0,0 1,-8 8L80,224a8,8 0,0 1,-8 -8L72,40a8,8 0,0 1,8 -8h96a8,8 0,0 1,8 8ZM140,60a12,12 0,1 1,-12 -12A12,12 0,0 1,140 60Z"
|
||||
android:fillColor="#4e6074"/>
|
||||
</vector>
|
||||
|
|
@ -22,12 +22,15 @@
|
|||
android:id="@+id/name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="23dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginTop="34dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:text="@{model.name, default=`Pixel 6 Pro`}"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:drawableStart="@{model.isMobileDevice ? @drawable/device_mobile_camera : @drawable/desktop, default=@drawable/device_mobile_camera}"
|
||||
android:drawablePadding="6dp"
|
||||
app:drawableTint="?attr/color_main2_700"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/remove"/>
|
||||
|
|
@ -60,29 +63,30 @@
|
|||
android:id="@+id/last_connection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="23dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/manage_account_device_last_connection"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintTop_toBottomOf="@id/name"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/last_connection_date"/>
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/last_connection_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginStart="25dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@{model.lastConnectionDate, default=`03/10/2023`}"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?attr/color_main2_600"
|
||||
android:drawableStart="@drawable/calendar_blank"
|
||||
android:drawablePadding="6dp"
|
||||
app:layout_constraintTop_toTopOf="@id/last_connection"
|
||||
app:layout_constraintBottom_toBottomOf="@id/last_connection"
|
||||
app:layout_constraintStart_toEndOf="@id/last_connection"
|
||||
app:drawableTint="?attr/color_main2_700"
|
||||
app:layout_constraintTop_toBottomOf="@id/last_connection"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/last_connection_time"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
|
|
@ -90,16 +94,17 @@
|
|||
android:id="@+id/last_connection_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@{model.lastConnectionTime, default=`9h25`}"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?attr/color_main2_600"
|
||||
android:drawableStart="@drawable/clock"
|
||||
android:drawablePadding="6dp"
|
||||
app:layout_constraintTop_toTopOf="@id/last_connection"
|
||||
app:layout_constraintBottom_toBottomOf="@id/last_connection"
|
||||
app:layout_constraintStart_toEndOf="@id/last_connection_date"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
app:drawableTint="?attr/color_main2_700"
|
||||
app:layout_constraintTop_toBottomOf="@id/last_connection"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/last_connection_date"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -379,9 +379,9 @@
|
|||
android:layout_marginEnd="26dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/manage_account_devices_title"
|
||||
android:visibility="@{viewModel.devicesAvailable ? View.VISIBLE : View.GONE, default=gone}"
|
||||
android:drawableEnd="@{viewModel.expandDevices ? @drawable/caret_up : @drawable/caret_down, default=@drawable/caret_up}"
|
||||
android:drawableTint="@color/gray_main2_600"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_background"/>
|
||||
|
|
@ -400,9 +400,10 @@
|
|||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:background="@drawable/shape_squircle_white_background"
|
||||
android:visibility="@{viewModel.expandDevices ? View.VISIBLE : View.GONE, default=gone}"
|
||||
android:visibility="@{viewModel.devicesAvailable && viewModel.expandDevices ? View.VISIBLE : View.GONE, default=gone}"
|
||||
app:entries="@{viewModel.devices}"
|
||||
app:layout="@{@layout/account_profile_device_list_cell}"
|
||||
app:layout_constraintHeight_min="80dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/devices" />
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@
|
|||
<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"
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="@id/password" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.confirmPhoneNumber()}"
|
||||
android:onClick="@{() -> viewModel.phoneNumberConfirmedByUser()}"
|
||||
android:enabled="@{viewModel.createEnabled && viewModel.pushNotificationsAvailable && !viewModel.operationInProgress, default=false}"
|
||||
style="@style/primary_button_label_style"
|
||||
android:id="@+id/create"
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
|
||||
<!-- Generic toasts -->
|
||||
<string name="sip_address_copied_to_clipboard_toast">L\'adresse SIP a été copiée</string>
|
||||
<string name="new_account_configured_toast">Connexion réussie</string>
|
||||
<string name="new_account_configured_toast">Nouveau compte ajouté</string>
|
||||
<string name="default_account_connection_state_error_toast">Erreur de connexion !</string>
|
||||
<string name="file_successfully_exported_to_media_store_toast">Le media a été exporté</string>
|
||||
<string name="export_file_to_media_store_error_toast">Le media n\'a pas pû être exporté !</string>
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@
|
|||
|
||||
<!-- Generic toasts -->
|
||||
<string name="sip_address_copied_to_clipboard_toast">SIP address copied into clipboard</string>
|
||||
<string name="new_account_configured_toast">Connection successful</string>
|
||||
<string name="new_account_configured_toast">New account configured</string>
|
||||
<string name="default_account_connection_state_error_toast">Connection error!</string>
|
||||
<string name="file_successfully_exported_to_media_store_toast">File has been exported to native gallery</string>
|
||||
<string name="export_file_to_media_store_error_toast">Error trying to export file to native gallery</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue