This commit is contained in:
Benoit Marty 2021-04-15 10:37:40 +02:00 committed by Benoit Marty
parent f0433fd27d
commit 7117427686
17 changed files with 214 additions and 38 deletions

View file

@ -94,6 +94,12 @@ fun <T : Fragment> AppCompatActivity.addFragmentToBackstack(
} }
} }
fun AppCompatActivity.resetBackstack() {
repeat(supportFragmentManager.backStackEntryCount) {
supportFragmentManager.popBackStack()
}
}
fun AppCompatActivity.hideKeyboard() { fun AppCompatActivity.hideKeyboard() {
currentFocus?.hideKeyboard() currentFocus?.hideKeyboard()
} }

View file

@ -69,7 +69,8 @@ sealed class LoginAction2 : VectorViewModelAction {
object ResetHomeServerUrl : ResetAction() object ResetHomeServerUrl : ResetAction()
object ResetSignMode : ResetAction() object ResetSignMode : ResetAction()
object ResetLogin : ResetAction() object ResetSignin : ResetAction()
object ResetSignup : ResetAction()
object ResetResetPassword : ResetAction() object ResetResetPassword : ResetAction()
// Homeserver history // Homeserver history

View file

@ -36,6 +36,7 @@ import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.resetBackstack
import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.databinding.ActivityLoginBinding
@ -227,6 +228,7 @@ open class LoginActivity2 : VectorBaseActivity<ActivityLoginBinding>(), ToolbarC
is LoginViewEvents2.OpenSignUpChooseUsernameScreen -> { is LoginViewEvents2.OpenSignUpChooseUsernameScreen -> {
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginFragment2SignupUsername::class.java, LoginFragment2SignupUsername::class.java,
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption) option = commonOption)
} }
is LoginViewEvents2.OpenSignInWithAnythingScreen -> { is LoginViewEvents2.OpenSignInWithAnythingScreen -> {
@ -247,9 +249,15 @@ open class LoginActivity2 : VectorBaseActivity<ActivityLoginBinding>(), ToolbarC
onLoginModeNotSupported(event.supportedTypes) onLoginModeNotSupported(event.supportedTypes)
is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event) is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event)
is LoginViewEvents2.Finish -> terminate(true) is LoginViewEvents2.Finish -> terminate(true)
is LoginViewEvents2.CancelRegistration -> handleCancelRegistration()
}.exhaustive }.exhaustive
} }
private fun handleCancelRegistration() {
// Cleanup the back stack
resetBackstack()
}
private fun handleOnSessionCreated(event: LoginViewEvents2.OnSessionCreated) { private fun handleOnSessionCreated(event: LoginViewEvents2.OnSessionCreated) {
if (event.newAccount) { if (event.newAccount) {
// Propose to set avatar and display name // Propose to set avatar and display name

View file

@ -182,7 +182,7 @@ class LoginCaptchaFragment2 @Inject constructor(
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) loginViewModel.handle(LoginAction2.ResetSignup)
} }
override fun updateWithState(state: LoginViewState2) { override fun updateWithState(state: LoginViewState2) {

View file

@ -154,7 +154,7 @@ class LoginFragment2SigninPassword @Inject constructor(
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) loginViewModel.handle(LoginAction2.ResetSignin)
} }
override fun onError(throwable: Throwable) { override fun onError(throwable: Throwable) {

View file

@ -91,7 +91,7 @@ class LoginFragment2SigninUsername @Inject constructor() : AbstractLoginFragment
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) loginViewModel.handle(LoginAction2.ResetSignin)
} }
override fun onError(throwable: Throwable) { override fun onError(throwable: Throwable) {

View file

@ -129,7 +129,7 @@ class LoginFragment2SignupPassword @Inject constructor() : AbstractSSOLoginFragm
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) // loginViewModel.handle(LoginAction2.ResetSignup)
} }
override fun onError(throwable: Throwable) { override fun onError(throwable: Throwable) {

View file

@ -125,7 +125,7 @@ class LoginFragment2SignupUsername @Inject constructor() : AbstractSSOLoginFragm
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) // loginViewModel.handle(LoginAction2.ResetSignup)
} }
override fun onError(throwable: Throwable) { override fun onError(throwable: Throwable) {

View file

@ -182,7 +182,7 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) // loginViewModel.handle(LoginAction2.ResetSignin)
} }
override fun onError(throwable: Throwable) { override fun onError(throwable: Throwable) {

View file

@ -33,7 +33,8 @@ import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.isEmail
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginGenericTextInputForm2Binding
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
@ -56,12 +57,12 @@ data class LoginGenericTextInputFormFragmentArgument(
/** /**
* In this screen, the user is asked for a text input * In this screen, the user is asked for a text input
*/ */
class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFragment2<FragmentLoginGenericTextInputFormBinding>() { class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFragment2<FragmentLoginGenericTextInputForm2Binding>() {
private val params: LoginGenericTextInputFormFragmentArgument by args() private val params: LoginGenericTextInputFormFragmentArgument by args()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginGenericTextInputFormBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginGenericTextInputForm2Binding {
return FragmentLoginGenericTextInputFormBinding.inflate(inflater, container, false) return FragmentLoginGenericTextInputForm2Binding.inflate(inflater, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -77,6 +78,7 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
private fun setupViews() { private fun setupViews() {
views.loginGenericTextInputFormOtherButton.setOnClickListener { onOtherButtonClicked() } views.loginGenericTextInputFormOtherButton.setOnClickListener { onOtherButtonClicked() }
views.loginGenericTextInputFormSubmit.setOnClickListener { submit() } views.loginGenericTextInputFormSubmit.setOnClickListener { submit() }
views.loginGenericTextInputFormLater.setOnClickListener { submit() }
} }
private fun setupAutoFill() { private fun setupAutoFill() {
@ -102,9 +104,11 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
private fun setupUi() { private fun setupUi() {
when (params.mode) { when (params.mode) {
TextInputFormFragmentMode.SetEmail -> { TextInputFormFragmentMode.SetEmail -> {
views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_email_title) views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_email_title_2)
views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_email_notice) views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_email_notice_2)
views.loginGenericTextInputFormNotice2.setTextOrHide(null) // Text will be updated with the state
views.loginGenericTextInputFormMandatoryNotice.isVisible = params.mandatory
views.loginGenericTextInputFormNotice2.isVisible = false
views.loginGenericTextInputFormTil.hint = views.loginGenericTextInputFormTil.hint =
getString(if (params.mandatory) R.string.login_set_email_mandatory_hint else R.string.login_set_email_optional_hint) getString(if (params.mandatory) R.string.login_set_email_mandatory_hint else R.string.login_set_email_optional_hint)
views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
@ -112,8 +116,10 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
views.loginGenericTextInputFormSubmit.text = getString(R.string.login_set_email_submit) views.loginGenericTextInputFormSubmit.text = getString(R.string.login_set_email_submit)
} }
TextInputFormFragmentMode.SetMsisdn -> { TextInputFormFragmentMode.SetMsisdn -> {
views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_msisdn_title) views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_msisdn_title_2)
views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_msisdn_notice) views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_msisdn_notice_2)
// Text will be updated with the state
views.loginGenericTextInputFormMandatoryNotice.isVisible = params.mandatory
views.loginGenericTextInputFormNotice2.setTextOrHide(getString(R.string.login_set_msisdn_notice2)) views.loginGenericTextInputFormNotice2.setTextOrHide(getString(R.string.login_set_msisdn_notice2))
views.loginGenericTextInputFormTil.hint = views.loginGenericTextInputFormTil.hint =
getString(if (params.mandatory) R.string.login_set_msisdn_mandatory_hint else R.string.login_set_msisdn_optional_hint) getString(if (params.mandatory) R.string.login_set_msisdn_mandatory_hint else R.string.login_set_msisdn_optional_hint)
@ -124,7 +130,8 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
TextInputFormFragmentMode.ConfirmMsisdn -> { TextInputFormFragmentMode.ConfirmMsisdn -> {
views.loginGenericTextInputFormTitle.text = getString(R.string.login_msisdn_confirm_title) views.loginGenericTextInputFormTitle.text = getString(R.string.login_msisdn_confirm_title)
views.loginGenericTextInputFormNotice.text = getString(R.string.login_msisdn_confirm_notice, params.extra) views.loginGenericTextInputFormNotice.text = getString(R.string.login_msisdn_confirm_notice, params.extra)
views.loginGenericTextInputFormNotice2.setTextOrHide(null) views.loginGenericTextInputFormMandatoryNotice.isVisible = false
views.loginGenericTextInputFormNotice2.isVisible = false
views.loginGenericTextInputFormTil.hint = views.loginGenericTextInputFormTil.hint =
getString(R.string.login_msisdn_confirm_hint) getString(R.string.login_msisdn_confirm_hint)
views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_NUMBER views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_NUMBER
@ -195,26 +202,31 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.loginGenericTextInputFormSubmit.isEnabled = false views.loginGenericTextInputFormSubmit.isEnabled = false
views.loginGenericTextInputFormTextInput.textChanges() views.loginGenericTextInputFormTextInput.textChanges()
.subscribe { .subscribe { text ->
views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(it) views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(text)
text?.let { updateSubmitButtons(it) }
} }
.disposeOnDestroyView() .disposeOnDestroyView()
} }
private fun updateSubmitButtons(text: CharSequence) {
if (params.mandatory) {
views.loginGenericTextInputFormSubmit.isVisible = true
views.loginGenericTextInputFormLater.isVisible = false
} else {
views.loginGenericTextInputFormSubmit.isVisible = text.isNotEmpty()
views.loginGenericTextInputFormLater.isVisible = text.isEmpty()
}
}
private fun isInputValid(input: CharSequence): Boolean { private fun isInputValid(input: CharSequence): Boolean {
return if (input.isEmpty() && !params.mandatory) { return if (input.isEmpty() && !params.mandatory) {
true true
} else { } else {
when (params.mode) { when (params.mode) {
TextInputFormFragmentMode.SetEmail -> { TextInputFormFragmentMode.SetEmail -> input.isEmail()
input.isEmail() TextInputFormFragmentMode.SetMsisdn -> input.isNotBlank()
} TextInputFormFragmentMode.ConfirmMsisdn -> input.isNotBlank()
TextInputFormFragmentMode.SetMsisdn -> {
input.isNotBlank()
}
TextInputFormFragmentMode.ConfirmMsisdn -> {
input.isNotBlank()
}
} }
} }
} }
@ -253,6 +265,14 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) loginViewModel.handle(LoginAction2.ResetSignup)
}
override fun updateWithState(state: LoginViewState2) {
views.loginGenericTextInputFormMandatoryNotice.text = when (params.mode) {
TextInputFormFragmentMode.SetEmail -> getString(R.string.login_set_email_mandatory_notice_2, state.homeServerUrlFromUser.toReducedUrl())
TextInputFormFragmentMode.SetMsisdn -> getString(R.string.login_set_msisdn_mandatory_notice_2, state.homeServerUrlFromUser.toReducedUrl())
TextInputFormFragmentMode.ConfirmMsisdn -> null
}
} }
} }

View file

@ -48,6 +48,8 @@ sealed class LoginViewEvents2 : VectorViewEvents {
object OnResetPasswordMailConfirmationSuccess : LoginViewEvents2() object OnResetPasswordMailConfirmationSuccess : LoginViewEvents2()
object OnResetPasswordMailConfirmationSuccessDone : LoginViewEvents2() object OnResetPasswordMailConfirmationSuccessDone : LoginViewEvents2()
object CancelRegistration: LoginViewEvents2()
data class OnLoginModeNotSupported(val supportedTypes: List<String>) : LoginViewEvents2() data class OnLoginModeNotSupported(val supportedTypes: List<String>) : LoginViewEvents2()
data class OnSendEmailSuccess(val email: String) : LoginViewEvents2() data class OnSendEmailSuccess(val email: String) : LoginViewEvents2()

View file

@ -44,7 +44,6 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.FlowResult
@ -379,12 +378,27 @@ class LoginViewModel2 @AssistedInject constructor(
) )
} }
} }
LoginAction2.ResetLogin -> { LoginAction2.ResetSignin -> {
viewModelScope.launch { viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration() authenticationService.cancelPendingLoginOrRegistration()
setState { copy(isLoading = false) } setState {
copy(isLoading = false)
} }
} }
_viewEvents.post(LoginViewEvents2.CancelRegistration)
}
LoginAction2.ResetSignup -> {
viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()
setState {
// Always create a new state, to ensure the state is correctly reset
LoginViewState2(
knownCustomHomeServersUrls = knownCustomHomeServersUrls
)
}
}
_viewEvents.post(LoginViewEvents2.CancelRegistration)
}
LoginAction2.ResetResetPassword -> { LoginAction2.ResetResetPassword -> {
setState { setState {
copy( copy(
@ -397,8 +411,7 @@ class LoginViewModel2 @AssistedInject constructor(
private fun handleUpdateSignMode(action: LoginAction2.UpdateSignMode) { private fun handleUpdateSignMode(action: LoginAction2.UpdateSignMode) {
setState { setState {
// Always create a new state, to ensure the state is correctly reset copy(
LoginViewState2(
signMode = action.signMode signMode = action.signMode
) )
} }

View file

@ -70,6 +70,6 @@ class LoginWaitForEmailFragment2 @Inject constructor() : AbstractLoginFragment2<
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) loginViewModel.handle(LoginAction2.ResetSignup)
} }
} }

View file

@ -242,7 +242,7 @@ class LoginWebFragment2 @Inject constructor(
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) loginViewModel.handle(LoginAction2.ResetSignin)
} }
override fun onBackPressed(toolbarButton: Boolean): Boolean { override fun onBackPressed(toolbarButton: Boolean): Boolean {

View file

@ -114,6 +114,6 @@ class LoginTermsFragment2 @Inject constructor(
} }
override fun resetViewModel() { override fun resetViewModel() {
loginViewModel.handle(LoginAction2.ResetLogin) loginViewModel.handle(LoginAction2.ResetSignup)
} }
} }

View file

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/login_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/loginGenericTextInputFormTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:textAppearance="@style/TextAppearance.Vector.Login.Title"
tools:text="@string/login_set_email_title_2" />
<TextView
android:id="@+id/loginGenericTextInputFormNotice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:layout_marginBottom="@dimen/layout_vertical_margin"
android:gravity="start"
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
tools:text="@string/login_set_email_notice_2" />
<TextView
android:id="@+id/loginGenericTextInputFormMandatoryNotice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="26dp"
android:gravity="start"
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
android:visibility="gone"
tools:text="@string/login_set_email_mandatory_notice_2"
tools:visibility="visible" />
<TextView
android:id="@+id/loginGenericTextInputFormNotice2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
android:visibility="gone"
tools:text="@string/login_set_msisdn_notice2"
tools:visibility="visible" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginGenericTextInputFormTil"
style="@style/VectorTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
tools:hint="@string/login_set_email_optional_hint">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginGenericTextInputFormTextInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:maxLines="1"
tools:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/loginGenericTextInputFormOtherButton"
style="@style/Style.Vector.Login.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:visibility="gone"
tools:text="@string/login_msisdn_confirm_send_again"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginGenericTextInputFormLater"
style="@style/Style.Vector.Login.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="@string/later"
android:visibility="gone"
tools:layout_marginEnd="100dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginGenericTextInputFormSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:enabled="false"
tools:ignore="RelativeOverlap"
tools:text="@string/login_set_email_submit" />
</FrameLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>

View file

@ -35,4 +35,12 @@
<string name="login_account_created_message">Hello Matrix world!</string> <string name="login_account_created_message">Hello Matrix world!</string>
<string name="login_account_created_instruction">Click on the image and on your name to configure them.</string> <string name="login_account_created_instruction">Click on the image and on your name to configure them.</string>
<string name="login_set_email_title_2">Associate an email</string>
<string name="login_set_email_notice_2">Associate an email to be able to later recover your account, in case you forget your password.</string>
<string name="login_set_email_mandatory_notice_2">The server %s requires you to associate an email to create an account.</string>
<string name="login_set_msisdn_title_2">Associate a phone number</string>
<string name="login_set_msisdn_notice_2">Associate a phone number to optionally allow people you know to discover you.</string>
<string name="login_set_msisdn_mandatory_notice_2">The server %s requires you to associate a phone number to create an account.</string>
</resources> </resources>