Trying ai generated test from gherkin

This commit is contained in:
Sylvain Berfini 2026-03-31 09:54:15 +02:00
parent bdbdfda4b7
commit c17b028113
23 changed files with 733 additions and 235 deletions

View file

@ -98,10 +98,6 @@ tasks.register("linphoneSdkSource") {
}
project.tasks.preBuild.dependsOn("linphoneSdkSource")
configurations {
implementation { isCanBeResolved = true }
}
android {
namespace = "org.linphone"
compileSdk = 36
@ -254,6 +250,8 @@ dependencies {
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso)
androidTestImplementation(libs.androidx.espresso.intents)
androidTestImplementation(libs.androidx.uiautomator)
androidTestImplementation(libs.cucumber.android)
androidTestImplementation(libs.cucumber.junit)

View file

@ -0,0 +1,103 @@
# language: fr
Fonctionnalité: Création de compte
Background:
Etant donné que lutilisateur vient de lancer l'app pour la première fois
Scénario: Accorder les permissions
Etant donné que toutes les permissions ne sont pas accordées
Quand lutilisateur clique sur le bouton "OK"
Alors l'utilisateur est redirigé vers la page d'accueil
Scénario: Créer un compte
Etant donné que lutilisateur est sur la vue de login
Quand lutilisateur clique sur le bouton "Créer un compte"
Alors l'utilisateur est redirigé vers le formulaire d'inscription.
#Tel#
Scénario: Bouton "Créer" inactif sans champs remplis
Etant donné que lutilisateur clique sur le bouton "S'inscrire"
Quand lutilisateur clique sur "Créer" sans remplir les champs
Alors le bouton "Créer" est grisé et non cliquable
Plan du Scénario: Validation des champs lors de la création de compte par téléphone
Etant donné que lutilisateur clique sur le bouton "S'inscrire"
Quand lutilisateur saisit "<Nom utilisateur>" dans le champ Nom utilisateur
Et saisit "<Indicatif international>" dans le champ Numéro de téléphone
Et saisit "<Numéro de téléphone>" dans le champ Numéro de téléphone
Et saisit "<Password>" dans le champ mot de passe
Et clique sur "Créer"
Alors "<expectedResult>"
Exemples:
| Nom utilisateur | Numéro de téléphone | Indicatif international | Password | expectedResult |
| TestAccount666 | 0600000000 | +33 | azertyiop123 | No uppercase letters are allowed. (and 1 more error) |
| testaccoun/t | 0600000000 | +33 | azertyiop123 | The pseudo should be a valid SIP username |
| testaccount | 0600000000 | +33 | azertyiop123 | Le nom d'utilisateur est déjà pris |
| unused_account | 0600000000 | +33 | azertyiop123 | L'utilisateur est redirigé vers la validation par SMS |
Scénario: Validation du numéro de téléphone avec code
Etant donné que lutilisateur est sur la vue de validation du phone number
Quand lutilisateur saisit "1111" comme code
Alors l'utilisateur n'est pas redirigé vers l'application.
Scénario: Création de compte validée
Etant donné que lutilisateur est sur la vue de validation du phone number
Quand lutilisateur saisit le code reçu par SMS
Alors l'utilisateur est redirigé vers l'application sur la vue Appels
#MAIL#
Scénario: Formulaire inscription par email
Etant donné que lutilisateur est sur la vue de login
Quand lutilisateur clique sur le bouton "Créer un compte"
Quand lutilisateur clique sur le bouton "subscribe.linphone.org"
Alors l'utilisateur et redirigé sur son navigateur.
#SIP tiers
Scénario: Connexion via un compte SIP tiers
Etant donné que lutilisateur est sur la vue de login
Quand Je clique sur le bouton "J'ai un compte SIP tiers" depuis la vue login
Alors Une vue minforme que certaines fonctionnalités ne seront pas disponibles
Quand Je clique sur "je préfère créer un compte"
Alors Je suis redirigé vers la création de compte
Quand Je clique sur la flèche en haut à gauche
Et Je clique de nouveau sur "J'ai un compte SIP tiers"
Et Je clique sur "Jai compris"
Alors La vue de connexion avec un compte SIP tiers est affichée
Et Le bouton connexion est désactivé tant que tous les champs obligatoires ne sont pas remplis
Quand Je saisis un username "dummy_account"
Et Je saisis un mot de passe "invalid"
Et Je saisis un nom de domaine "sip.linphone.org"
Et Je clique sur connexion
Alors un dialogue apparait "opération en cours, merci de patienter..."
Et Un message indique "Erreur durant la connexion"
Quand Je saisis un username "dummy_account"
Et Je saisis un mot de passe "3V3ee@dummy!"
Et Je saisis un nom de domaine "sip.linphone.org"
Et Je clique sur connexion
Alors On sort de l'assistant
#QRcode
Scénario: Connexion via QR code
Quand Je clique sur "Scanner un QR code"
Alors La vue de scan de QR code souvre
#compte linphone
Scénario: Connexion via formulaire classique
Quand Je clique sur "Connexion" sans saisir de username ni mot de passe
Alors Le bouton "connexion" est grisé.
Quand Je saisis une adresse SIP "dummy_account_2"
Et Je saisis un mot de passe "invalid"
Et Je clique sur "Connexion"
Alors Un toast indique "Mauvais nom d'utilisateur ou mot de passe"
Quand Je saisis une adresse SIP "dummy_account_2"
Et Je saisis un mot de passe "3V3ee@dummy!"
Et Je clique sur "Connexion"
Alors On sort de l'assistant

View file

@ -1,7 +0,0 @@
Feature:Welcome
When app starts the first time, it must display the welcome screens
Scenario Outline:Welcome screens displayed at first start
Given I have a welcome Activity
When I press skip
Then I should be sent to the assistant Activity

View file

@ -0,0 +1,404 @@
/*
* 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.test
import android.content.Intent
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
import androidx.test.espresso.intent.matcher.UriMatchers.hasHost
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import io.cucumber.java.After
import io.cucumber.java.Before
import io.cucumber.java.en.Given
import io.cucumber.java.en.Then
import io.cucumber.java.en.When
import junit.framework.TestCase.assertTrue
import org.hamcrest.Matchers.*
import org.junit.Rule
import org.junit.runner.RunWith
import org.linphone.R
import org.linphone.ui.assistant.AssistantActivity
import kotlin.random.Random
import kotlin.random.nextInt
@LargeTest
@RunWith(AndroidJUnit4::class)
class AssistantActivityTest {
@JvmField
@Rule
var activityTestRule: ActivityTestRule<AssistantActivity> =
ActivityTestRule(AssistantActivity::class.java, false, false)
@Before
fun setup() {
Intents.init()
activityTestRule.launchActivity(Intent())
}
@After
fun finish() {
Intents.release()
activityTestRule.finishActivity()
}
@Given("^toutes les permissions ne sont pas accordées$")
fun grant_all_permissions() {
onView(withId(R.id.grant_permissions_title)).check(matches(isDisplayed()))
}
@When("^lutilisateur clique sur le bouton \"OK\"$")
fun user_clicks_on_grant_permissions() {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
try {
// Check if we are on the permissions fragment
onView(withId(R.id.grant_all_permissions)).check(matches(isDisplayed()))
// Click the grant all button
onView(withId(R.id.grant_all_permissions)).perform(click())
// Handle system permission dialogs if they appear
var allowButton =
device.findObject(UiSelector().textMatches("(?i)ALLOW|AUTORISER|ACCEPTER|WHILE USING THE APP|LORSQUE VOUS UTILISEZ L'APPLI"))
var tries = 0
var clicks = 0
while (allowButton.exists() && tries < 10) {
allowButton.click()
clicks += 1
tries++
// Wait a bit for the next one
Thread.sleep(1000)
allowButton =
device.findObject(UiSelector().textMatches("(?i)ALLOW|AUTORISER|ACCEPTER|WHILE USING THE APP|LORSQUE VOUS UTILISEZ L'APPLI"))
}
assertTrue(clicks == 4)
} catch (e: Exception) {
// If the view is not found, permissions might already be granted
}
}
@Then("^l'utilisateur est redirigé vers la page d'accueil$")
fun user_is_redirected_to_landing_page() {
onView(withId(R.id.login_title)).check(matches(isDisplayed()))
}
@Given("^lutilisateur est sur la vue de login$")
fun user_is_on_login_page() {
onView(withId(R.id.login_title)).check(matches(isDisplayed()))
}
@When("^lutilisateur clique sur le bouton \"Créer un compte\"$")
fun user_clicks_on_create_account() {
onView(withId(R.id.register)).perform(click())
// Handle app accept conditions & terms dialog if they appear
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
var allowButton = device.findObject(UiSelector().textMatches("(?i)Accept|Accepter"))
var count = 0
while (allowButton.exists() && count < 10) {
allowButton.click()
count++
// Wait a bit for the next one
Thread.sleep(1000)
allowButton = device.findObject(UiSelector().textMatches("(?i)Accept|Accepter"))
}
}
@Then("^l'utilisateur est redirigé vers le formulaire d'inscription.$")
fun user_is_redirected_to_registration_form() {
onView(withId(R.id.register_title)).check(matches(isDisplayed()))
}
@Given("^lutilisateur clique sur le bouton \"S'inscrire\"$")
fun user_clicks_on_signup_button() {
// Dans LandingFragment, "Créer un compte" correspond au bouton d'inscription
onView(withId(R.id.register)).perform(click())
}
@When("^lutilisateur clique sur \"Créer\" sans remplir les champs$")
fun user_clicks_create_without_filling_fields() {
// On s'assure juste que le bouton est présent
onView(withId(R.id.create)).check(matches(isDisplayed()))
}
@Then("^le bouton \"Créer\" est grisé et non cliquable$")
fun create_button_is_disabled() {
onView(withId(R.id.create)).check(matches(not(isEnabled())))
}
@When("^lutilisateur saisit \"([^\"]*)\" dans le champ Nom utilisateur$")
fun user_enters_username(username: String) {
val usernameToSet = if (username == "unused_account") {
val randInt = Random.nextInt(0..1000000)
"usernameToSet-$randInt"
} else {
username
}
onView(withId(R.id.username)).perform(replaceText(usernameToSet))
}
@When("^saisit \"([^\"]*)\" dans le champ Numéro de téléphone$")
fun user_enters_phone_info(value: String) {
if (value.startsWith("+")) {
// C'est l'indicatif (Spinner)
onView(withId(R.id.prefix)).perform(click())
onData(
allOf(
`is`(instanceOf(String::class.java)),
containsString(value)
)
).perform(click())
} else {
// C'est le numéro
onView(withId(R.id.phone_number)).perform(replaceText(value))
}
}
@When("^saisit \"([^\"]*)\" dans le champ mot de passe$")
fun user_enters_password(password: String) {
onView(withId(R.id.password)).perform(replaceText(password))
}
@When("^clique sur \"Créer\"$")
fun user_clicks_create() {
onView(withId(R.id.create)).perform(click())
// Une popup de confirmation s'affiche d'abord
onView(withText(R.string.assistant_dialog_confirm_phone_number_title)).check(
matches(
isDisplayed()
)
)
onView(withId(R.id.confirm)).perform(click())
}
@Then("^\"([^\"]*)\"$")
fun check_expected_result(expectedResult: String) {
Thread.sleep(1000)
when (expectedResult) {
"no upper case letters are allowed",
"Special characters are not allowed" -> {
// On vérifie que le message d'erreur contient le texte attendu
onView(withId(R.id.username_error)).check(
matches(
withText(
containsString(
expectedResult
)
)
)
)
}
"L'utilisateur est redirigé vers la validation par SMS" -> {
// On arrive sur la vue du code
onView(withId(R.id.register_code_confirmation_title)).check(matches(isDisplayed()))
}
}
}
@Given("^lutilisateur est sur la vue de validation du phone number$")
fun user_is_on_sms_validation_view() {
user_is_on_login_page()
onView(withId(R.id.register)).perform(click())
user_enters_username("testaccount")
user_enters_phone_info("+33")
user_enters_phone_info("0600000000")
user_enters_password("azertyiop123")
onView(withId(R.id.create)).perform(click())
onView(withId(R.id.confirm)).perform(click())
onView(withId(R.id.register_code_confirmation_title)).check(matches(isDisplayed()))
}
@When("^lutilisateur saisit \"([^\"]*)\" comme code$")
fun user_enters_code(code: String) {
if (code.length == 4) {
onView(withId(R.id.code_first_digit)).perform(replaceText(code[0].toString()))
onView(withId(R.id.code_second_digit)).perform(replaceText(code[1].toString()))
onView(withId(R.id.code_third_digit)).perform(replaceText(code[2].toString()))
onView(withId(R.id.code_last_digit)).perform(replaceText(code[3].toString()))
}
}
@Then("^l'utilisateur n'est pas redirigé vers l'application.$")
fun user_is_not_redirected() {
onView(withId(R.id.register_code_confirmation_title)).check(matches(isDisplayed()))
}
@When("^lutilisateur saisit le code reçu par SMS$")
fun user_enters_correct_sms_code() {
// Simulation de saisie d'un code (dépend de votre environnement de test pour le code réel)
user_enters_code("0000")
}
@Then("^l'utilisateur est redirigé vers l'application sur la vue Appels$")
fun user_is_redirected_to_calls_view() {
// Une fois validé, l'assistant se ferme et on arrive sur l'historique des appels
onView(withId(R.id.history_list)).check(matches(isDisplayed()))
}
@When("^lutilisateur clique sur le bouton \"subscribe.linphone.org\"$")
fun user_clicks_on_subscribe_link() {
onView(withId(R.id.create_email_account)).perform(click())
}
@Then("^l'utilisateur et redirigé sur son navigateur.$")
fun user_is_redirected_to_browser() {
intended(
allOf(
hasAction(Intent.ACTION_VIEW),
hasData(hasHost("subscribe.linphone.org"))
)
)
}
@When("^Je clique sur le bouton \"J'ai un compte SIP tiers\" depuis la vue login$")
fun click_third_party_sip_account() {
onView(withId(R.id.third_party_sip_account)).perform(click())
}
@Then("^Une vue minforme que certaines fonctionnalités ne seront pas disponibles$")
fun check_third_party_warning_view() {
onView(withId(R.id.third_party_warning_title)).check(matches(isDisplayed()))
}
@When("^Je clique sur \"je préfère créer un compte\"$")
fun click_prefer_create_account() {
onView(withId(R.id.create_linphone_account)).perform(click())
}
@Then("^Je suis redirigé vers la création de compte$")
fun check_redirected_to_creation() {
onView(withId(R.id.register_title)).check(matches(isDisplayed()))
}
@When("^Je clique sur la flèche en haut à gauche$")
fun click_back_arrow() {
onView(withId(R.id.back)).perform(click())
}
@When("^Je clique de nouveau sur \"J'ai un compte SIP tiers\"$")
fun click_third_party_sip_account_again() {
onView(withId(R.id.third_party_sip_account)).perform(click())
}
@When("^Je clique sur \"Jai compris\"$")
fun click_understood() {
onView(withId(R.id.continue_third_party_account_login)).perform(click())
}
@Then("^La vue de connexion avec un compte SIP tiers est affichée$")
fun check_third_party_login_view() {
onView(withId(R.id.third_party_login_title)).check(matches(isDisplayed()))
}
@Then("^Le bouton connexion est désactivé tant que tous les champs obligatoires ne sont pas remplis$")
fun check_login_button_disabled() {
onView(withId(R.id.login)).check(matches(not(isEnabled())))
}
@When("^Je saisis un username \"([^\"]*)\"$")
fun enter_valid_username(user: String) {
onView(withId(R.id.username)).perform(replaceText(user))
}
@When("^Je saisis un nom de domaine \"([^\"]*)\"$")
fun enter_domain(user: String) {
onView(withId(R.id.domain)).perform(replaceText(user))
}
@When("^Je clique sur connexion$")
fun click_login() {
onView(withId(R.id.login)).perform(click())
}
@Then("^un dialogue apparait \"opération en cours, merci de patienter...\"$")
fun check_operation_in_progress() {
onView(withText(R.string.operation_in_progress_overlay)).check(matches(isDisplayed()))
}
@Then("^Un message indique \"Erreur durant la connexion\"$")
fun check_login_error_message() {
// TODO
Thread.sleep(3000)
}
@Then("^On sort de l'assistant$")
fun check_call_view() {
// This is a workaround since the MainActivity wasn't started before the Assistant one...
// Can't check for Paused activity, have to wait for destroyed lifecycle state hence the long sleep
Thread.sleep(3000)
assertTrue(activityTestRule.getActivity().isDestroyed)
}
@When("^Je clique sur \"Scanner un QR code\"$")
fun click_scan_qr_code() {
onView(withId(R.id.scan_qr_code)).perform(click())
}
@Then("^La vue de scan de QR code souvre$")
fun check_qr_scanner_view() {
onView(withId(R.id.scan_qr_code_title)).check(matches(isDisplayed()))
}
@When("^Je clique sur \"Connexion\" sans saisir de username ni mot de passe$")
fun click_login_empty() {
onView(withId(R.id.sip_identity)).perform(replaceText(""))
onView(withId(R.id.password)).perform(replaceText(""), closeSoftKeyboard())
}
@Then("^Le bouton \"connexion\" est grisé.$")
fun check_login_button_grayed() {
onView(withId(R.id.login)).check(matches(not(isEnabled())))
}
@When("^Je saisis un mot de passe \"([^\"]*)\"$")
fun enter_password(pass: String) {
onView(withId(R.id.password)).perform(replaceText(pass))
}
@When("^Je clique sur \"Connexion\"$")
fun click_login_linphone() {
onView(withId(R.id.login)).perform(click())
}
@Then("^Un toast indique \"Mauvais nom d'utilisateur ou mot de passe\"$")
fun check_wrong_credentials_popup() {
// TODO
Thread.sleep(3000)
}
@When("^Je saisis une adresse SIP \"([^\"]*)\"$")
fun enter_sip_address(user: String) {
onView(withId(R.id.sip_identity)).perform(replaceText(user))
}
}

View file

@ -1,71 +0,0 @@
/*
* 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.test
import android.content.Intent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import io.cucumber.java.After
import io.cucumber.java.Before
import io.cucumber.java.en.Given
import io.cucumber.java.en.Then
import io.cucumber.java.en.When
import junit.framework.TestCase.assertNotNull
import org.junit.Rule
import org.junit.runner.RunWith
import org.linphone.ui.welcome.WelcomeActivity
@SmallTest
@RunWith(AndroidJUnit4::class)
class WelcomeActivityTest {
@JvmField
@Rule
var activityTestRule: ActivityTestRule<WelcomeActivity> = ActivityTestRule(WelcomeActivity::class.java)
@Rule
lateinit var activity: WelcomeActivity
@Before()
fun setup() {
activityTestRule.launchActivity(Intent())
activity = activityTestRule.activity
}
@After()
fun finish() {
activityTestRule.finishActivity()
}
@Given("^I have a welcome Activity")
fun I_have_a_login_activity() {
assertNotNull(activity)
}
@When("^I press skip")
fun I_press_skip() {
}
@Then("^I should be sent to the assistant Activity")
fun I_should_be_sent_to_the_assistant_activity() {
}
}

View file

@ -55,6 +55,10 @@ class AssistantActivity : GenericActivity() {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
while (!coreContext.isReady()) {
Thread.sleep(50)
}
binding = DataBindingUtil.setContentView(this, R.layout.assistant_activity)
binding.lifecycleOwner = this
setUpToastsArea(binding.toastsArea)

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.ui.main.sso
import android.net.Uri
import net.openid.appauth.connectivity.ConnectionBuilder
import java.net.HttpURLConnection
import java.net.URL
import java.security.cert.X509Certificate
import javax.net.ssl.*
class AppAuthConnectionBuilder(private val trustAll: Boolean) : ConnectionBuilder {
override fun openConnection(uri: Uri): HttpURLConnection {
val conn = URL(uri.toString()).openConnection() as HttpURLConnection
if (trustAll && conn is HttpsURLConnection) {
conn.sslSocketFactory = TRUSTING_CONTEXT.socketFactory
conn.hostnameVerifier = HostnameVerifier { _, _ -> true }
}
return conn
}
companion object {
private val TRUSTING_CONTEXT: SSLContext by lazy {
val context = SSLContext.getInstance("TLS")
context.init(
null,
arrayOf<TrustManager>(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}),
java.security.SecureRandom()
)
context
}
}
}

View file

@ -21,10 +21,12 @@ package org.linphone.ui.main.sso.viewmodel
import android.content.Intent
import androidx.annotation.UiThread
import androidx.core.net.toUri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import java.io.File
import kotlinx.coroutines.launch
import net.openid.appauth.AppAuthConfiguration
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationRequest
@ -39,10 +41,10 @@ import org.linphone.R
import org.linphone.core.Factory
import org.linphone.core.tools.Log
import org.linphone.ui.GenericViewModel
import org.linphone.ui.main.sso.AppAuthConnectionBuilder
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
import org.linphone.utils.TimestampUtils
import androidx.core.net.toUri
class SingleSignOnViewModel
@UiThread
@ -51,19 +53,7 @@ class SingleSignOnViewModel
private const val TAG = "[Single Sign On ViewModel]"
}
val operationInProgress = MutableLiveData<Boolean>()
val singleSignOnProcessCompletedEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData()
}
val startAuthIntentEvent: MutableLiveData<Event<Intent>> by lazy {
MutableLiveData()
}
val onErrorEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData()
}
val singleSignOnProcessCompletedEvent = MutableLiveData<Event<Boolean>>()
private var clientId: String
private val redirectUri: String
@ -72,6 +62,16 @@ class SingleSignOnViewModel
private var username: String = ""
val operationInProgress = MutableLiveData<Boolean>()
val startAuthIntentEvent: MutableLiveData<Event<Intent>> by lazy {
MutableLiveData<Event<Intent>>()
}
val onErrorEvent: MutableLiveData<Event<String>> by lazy {
MutableLiveData<Event<String>>()
}
private lateinit var authState: AuthState
private lateinit var authService: AuthorizationService
@ -133,64 +133,72 @@ class SingleSignOnViewModel
@UiThread
private fun singleSignOn() {
Log.i("$TAG Fetch from issuer [$singleSignOnUrl]")
operationInProgress.postValue(true)
val connectionBuilder = AppAuthConnectionBuilder(true)
val callback = AuthorizationServiceConfiguration.RetrieveConfigurationCallback { serviceConfiguration, ex ->
if (ex != null) {
Log.e(
"$TAG Failed to fetch configuration from issuer [$singleSignOnUrl]: ${ex.errorDescription}"
)
onErrorEvent.postValue(
Event("Failed to fetch configuration from issuer $singleSignOnUrl")
)
operationInProgress.postValue(false)
return@RetrieveConfigurationCallback
}
if (serviceConfiguration == null) {
Log.e("$TAG Service configuration is null!")
onErrorEvent.postValue(Event("Service configuration is null"))
operationInProgress.postValue(false)
return@RetrieveConfigurationCallback
}
if (!::authState.isInitialized) {
Log.i("$TAG Initializing AuthState object")
authState = AuthState(serviceConfiguration)
storeAuthStateAsJsonFile()
}
val authRequestBuilder = AuthorizationRequest.Builder(
serviceConfiguration, // the authorization service configuration
clientId, // the client ID, typically pre-registered and static
ResponseTypeValues.CODE, // the response_type value: we want a code
redirectUri.toUri() // the redirect URI to which the auth response is sent
)
// Needed for SDK to be able to refresh the token, otherwise it will return
// an invalid grant error with description "Session not active"
authRequestBuilder.setScopes("offline_access")
if (username.isNotEmpty() && corePreferences.useUsernameAsSingleSignOnLoginHint) {
Log.i("$TAG Using username [$username] as login hint")
authRequestBuilder.setLoginHint(username)
}
val authRequest = authRequestBuilder.build()
val authConfig = AppAuthConfiguration.Builder()
.setConnectionBuilder(connectionBuilder)
.build()
authService = AuthorizationService(coreContext.context, authConfig)
val authIntent = authService.getAuthorizationRequestIntent(authRequest)
startAuthIntentEvent.postValue(Event(authIntent))
}
AuthorizationServiceConfiguration.fetchFromIssuer(
singleSignOnUrl.toUri(),
AuthorizationServiceConfiguration.RetrieveConfigurationCallback { serviceConfiguration, ex ->
if (ex != null) {
Log.e(
"$TAG Failed to fetch configuration from issuer [$singleSignOnUrl]: ${ex.errorDescription}"
)
onErrorEvent.postValue(
Event("Failed to fetch configuration from issuer $singleSignOnUrl")
)
operationInProgress.postValue(false)
return@RetrieveConfigurationCallback
}
if (serviceConfiguration == null) {
Log.e("$TAG Service configuration is null!")
onErrorEvent.postValue(Event("Service configuration is null"))
operationInProgress.postValue(false)
return@RetrieveConfigurationCallback
}
if (!::authState.isInitialized) {
Log.i("$TAG Initializing AuthState object")
authState = AuthState(serviceConfiguration)
storeAuthStateAsJsonFile()
}
val authRequestBuilder = AuthorizationRequest.Builder(
serviceConfiguration, // the authorization service configuration
clientId, // the client ID, typically pre-registered and static
ResponseTypeValues.CODE, // the response_type value: we want a code
redirectUri.toUri() // the redirect URI to which the auth response is sent
)
// Needed for SDK to be able to refresh the token, otherwise it will return
// an invalid grant error with description "Session not active"
authRequestBuilder.setScopes("offline_access")
if (username.isNotEmpty() && corePreferences.useUsernameAsSingleSignOnLoginHint) {
Log.i("$TAG Using username [$username] as login hint")
authRequestBuilder.setLoginHint(username)
}
val authRequest = authRequestBuilder.build()
authService = AuthorizationService(coreContext.context)
val authIntent = authService.getAuthorizationRequestIntent(authRequest)
startAuthIntentEvent.postValue(Event(authIntent))
}
callback,
connectionBuilder
)
}
@UiThread
private fun performRefreshToken() {
operationInProgress.postValue(true)
if (::authState.isInitialized) {
if (!::authService.isInitialized) {
authService = AuthorizationService(coreContext.context)
val authConfig = AppAuthConfiguration.Builder()
.setConnectionBuilder(AppAuthConnectionBuilder(true))
.build()
authService = AuthorizationService(coreContext.context, authConfig)
}
val authStateJsonFile = File(corePreferences.ssoCacheFile)

View file

@ -68,13 +68,13 @@
android:background="@drawable/circle_transparent_button_background"
android:visibility="@{viewModel.showBackButton ? View.VISIBLE : View.INVISIBLE, default=invisible}"
app:tint="?attr/color_main2_500"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintTop_toTopOf="@id/login_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/title"/>
app:layout_constraintEnd_toStartOf="@id/login_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/login_title"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
@ -146,7 +146,7 @@
android:labelFor="@id/sip_identity"
android:text="@{@string/username + `*`, default=`Username*`}"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/login_title"
app:layout_constraintStart_toStartOf="@id/sip_identity"
app:layout_constraintBottom_toTopOf="@id/sip_identity"/>
@ -166,8 +166,8 @@
android:inputType="text|textNoSuggestions"
android:hint="@string/username"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toStartOf="@id/login_title"
app:layout_constraintEnd_toEndOf="@id/login_title"
app:layout_constraintTop_toBottomOf="@id/sip_identity_label"
app:layout_constraintBottom_toTopOf="@id/password_label"/>
@ -201,8 +201,8 @@
android:hint="@string/password"
passwordInputType="@{viewModel.showPassword ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD, default=textPassword}"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toStartOf="@id/login_title"
app:layout_constraintEnd_toEndOf="@id/login_title"
app:layout_constraintTop_toBottomOf="@id/password_label"
app:layout_constraintBottom_toTopOf="@id/login"/>
@ -233,7 +233,7 @@
android:text="@string/assistant_account_login"
app:layout_constraintWidth_max="@dimen/button_max_width"
app:layout_constraintTop_toBottomOf="@id/password"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintStart_toStartOf="@id/login_title"
app:layout_constraintBottom_toTopOf="@id/scan_qr_code"/>
<androidx.appcompat.widget.AppCompatTextView
@ -269,8 +269,8 @@
android:drawablePadding="8dp"
android:visibility="@{viewModel.hideScanQrCode ? View.GONE : View.VISIBLE}"
app:drawableTint="@color/secondary_button_label_color"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toStartOf="@id/login_title"
app:layout_constraintEnd_toEndOf="@id/login_title"
app:layout_constraintTop_toBottomOf="@id/login"
app:layout_constraintBottom_toTopOf="@id/third_party_sip_account" />
@ -285,8 +285,8 @@
android:layout_marginEnd="16dp"
android:text="@string/assistant_login_third_party_sip_account"
android:visibility="@{viewModel.hideThirdPartyAccount ? View.GONE : View.VISIBLE}"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toStartOf="@id/login_title"
app:layout_constraintEnd_toEndOf="@id/login_title"
app:layout_constraintTop_toBottomOf="@id/scan_qr_code"
app:layout_constraintBottom_toTopOf="@id/mountains"/>

View file

@ -49,13 +49,13 @@
android:contentDescription="@string/content_description_go_back_icon"
android:background="@drawable/circle_transparent_button_background"
app:tint="?attr/color_main2_500"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintTop_toTopOf="@id/grant_permissions_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/title"/>
app:layout_constraintEnd_toStartOf="@id/grant_permissions_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/grant_permissions_title"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
@ -82,7 +82,7 @@
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/grant_permissions_title"
app:layout_constraintBottom_toTopOf="@id/post_notifications_icon"/>
<ImageView

View file

@ -33,7 +33,7 @@
app:layout_constraintStart_toEndOf="@id/message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/mountains"
app:layout_constraintTop_toBottomOf="@id/title" />
app:layout_constraintTop_toBottomOf="@id/register_code_confirmation_title" />
<ImageView
android:id="@+id/back"
@ -47,13 +47,13 @@
android:background="@drawable/circle_transparent_button_background"
android:visibility="invisible"
app:tint="?attr/color_main2_500"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintTop_toTopOf="@id/register_code_confirmation_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/title"/>
app:layout_constraintEnd_toStartOf="@id/register_code_confirmation_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/register_code_confirmation_title"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
@ -79,10 +79,10 @@
android:textSize="14sp"
android:textColor="?attr/color_main2_600"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/register_code_confirmation_title"
app:layout_constraintBottom_toTopOf="@id/code_first_digit"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/register_code_confirmation_title"
app:layout_constraintEnd_toEndOf="@id/register_code_confirmation_title"/>
<androidx.appcompat.widget.AppCompatEditText
focusNextOnInput="@{true}"
@ -105,7 +105,7 @@
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintTop_toBottomOf="@id/message"
app:layout_constraintBottom_toTopOf="@id/wrong_number"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintStart_toStartOf="@id/register_code_confirmation_title"
app:layout_constraintEnd_toStartOf="@id/code_second_digit" />
<androidx.appcompat.widget.AppCompatEditText
@ -171,7 +171,7 @@
app:layout_constraintTop_toTopOf="@id/code_first_digit"
app:layout_constraintBottom_toBottomOf="@id/code_first_digit"
app:layout_constraintStart_toEndOf="@id/code_third_digit"
app:layout_constraintEnd_toEndOf="@id/title" />
app:layout_constraintEnd_toEndOf="@id/register_code_confirmation_title" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
@ -189,8 +189,8 @@
android:textColor="@color/secondary_button_label_color"
android:gravity="center"
android:background="@drawable/secondary_button_background"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toStartOf="@id/register_code_confirmation_title"
app:layout_constraintEnd_toEndOf="@id/register_code_confirmation_title"
app:layout_constraintTop_toBottomOf="@id/code_first_digit"
app:layout_constraintBottom_toTopOf="@id/mountains"/>

View file

@ -57,7 +57,7 @@
android:scaleType="centerCrop"
android:contentDescription="@null"
android:src="@drawable/assistant_logo"
app:layout_constraintStart_toEndOf="@id/title"
app:layout_constraintStart_toEndOf="@id/register_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/mountains"
app:layout_constraintTop_toBottomOf="@id/login" />
@ -73,13 +73,13 @@
android:contentDescription="@string/content_description_go_back_icon"
android:background="@drawable/circle_transparent_button_background"
app:tint="?attr/color_main2_500"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintTop_toTopOf="@id/register_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/title"/>
app:layout_constraintEnd_toStartOf="@id/register_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/register_title"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
@ -104,9 +104,9 @@
android:contentDescription="@null"
app:tint="?attr/color_main2_500"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="@id/register_title"
app:layout_constraintEnd_toEndOf="@id/register_title"
app:layout_constraintTop_toBottomOf="@id/register_title"
app:layout_constraintBottom_toTopOf="@id/no_push_label"/>
<androidx.appcompat.widget.AppCompatTextView
@ -121,8 +121,8 @@
android:text="@string/assistant_account_register_unavailable_no_push_warning"
app:layout_constraintTop_toBottomOf="@id/no_push"
app:layout_constraintBottom_toTopOf="@id/create_email_account_no_push"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/register_title"
app:layout_constraintEnd_toEndOf="@id/register_title"/>
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{openSubscribeWebPageClickListener}"
@ -141,8 +141,8 @@
android:textColor="@color/secondary_button_label_color"
android:gravity="center"
android:background="@drawable/secondary_button_background"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toStartOf="@id/register_title"
app:layout_constraintEnd_toEndOf="@id/register_title"
app:layout_constraintTop_toBottomOf="@id/no_push_label"
app:layout_constraintBottom_toTopOf="@id/mountains" />
@ -156,7 +156,7 @@
android:labelFor="@id/username"
android:text="@{@string/username + `*`}"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/register_title"
app:layout_constraintBottom_toTopOf="@id/username"
app:layout_constraintStart_toStartOf="@id/username"/>
@ -179,8 +179,8 @@
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/username_label"
app:layout_constraintBottom_toTopOf="@id/username_error"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/register_title"
app:layout_constraintEnd_toEndOf="@id/register_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
@ -319,8 +319,8 @@
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/password_label"
app:layout_constraintBottom_toTopOf="@id/password_error"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/register_title"
app:layout_constraintEnd_toEndOf="@id/register_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_600"
@ -364,8 +364,8 @@
app:layout_constraintWidth_max="@dimen/button_max_width"
app:layout_constraintTop_toBottomOf="@id/password_error"
app:layout_constraintBottom_toTopOf="@id/create_email_account"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title" />
app:layout_constraintStart_toStartOf="@id/register_title"
app:layout_constraintEnd_toEndOf="@id/register_title" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
@ -378,7 +378,7 @@
android:textColor="?attr/color_main2_700"
app:layout_constraintTop_toTopOf="@id/create_email_account"
app:layout_constraintBottom_toBottomOf="@id/create_email_account"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintStart_toStartOf="@id/register_title"
app:layout_constraintEnd_toStartOf="@id/create_email_account"/>
<androidx.appcompat.widget.AppCompatTextView
@ -427,8 +427,8 @@
android:text="@string/assistant_account_login"
app:layout_constraintVertical_bias="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title"/>
app:layout_constraintTop_toTopOf="@id/register_title"
app:layout_constraintBottom_toBottomOf="@id/register_title"/>
<ImageView
android:id="@+id/mountains"

View file

@ -55,13 +55,13 @@
android:contentDescription="@string/content_description_go_back_icon"
android:background="@drawable/circle_transparent_button_background"
app:tint="?attr/color_main2_500"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintTop_toTopOf="@id/third_party_login_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/title"/>
app:layout_constraintEnd_toStartOf="@id/third_party_login_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/third_party_login_title"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
@ -86,7 +86,7 @@
android:labelFor="@id/username"
android:text="@{@string/username + `*`}"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/third_party_login_title"
app:layout_constraintBottom_toTopOf="@id/username"
app:layout_constraintStart_toStartOf="@id/username"/>
@ -108,8 +108,8 @@
app:layout_constraintWidth_max="@dimen/button_max_width"
app:layout_constraintTop_toBottomOf="@id/username_label"
app:layout_constraintBottom_toTopOf="@id/password_label"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/third_party_login_title"
app:layout_constraintEnd_toEndOf="@id/third_party_login_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
@ -143,8 +143,8 @@
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/password_label"
app:layout_constraintBottom_toTopOf="@id/domain_label"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/third_party_login_title"
app:layout_constraintEnd_toEndOf="@id/third_party_login_title"/>
<ImageView
android:onClick="@{() -> viewModel.toggleShowPassword()}"
@ -192,8 +192,8 @@
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/domain_label"
app:layout_constraintBottom_toTopOf="@id/display_name_label"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/third_party_login_title"
app:layout_constraintEnd_toEndOf="@id/third_party_login_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
@ -227,8 +227,8 @@
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/display_name_label"
app:layout_constraintBottom_toTopOf="@id/transport_label"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/third_party_login_title"
app:layout_constraintEnd_toEndOf="@id/third_party_login_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
@ -260,8 +260,8 @@
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/transport_label"
app:layout_constraintBottom_toTopOf="@id/login"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toEndOf="@id/title"/>
app:layout_constraintStart_toStartOf="@id/third_party_login_title"
app:layout_constraintEnd_toEndOf="@id/third_party_login_title"/>
<ImageView
android:id="@+id/transport_caret"
@ -290,7 +290,7 @@
app:layout_constraintWidth_max="@dimen/button_max_width"
app:layout_constraintTop_toBottomOf="@id/transport"
app:layout_constraintBottom_toTopOf="@id/mountains"
app:layout_constraintStart_toStartOf="@id/title" />
app:layout_constraintStart_toStartOf="@id/third_party_login_title" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/section_header_style"
@ -307,9 +307,9 @@
android:drawablePadding="10dp"
android:background="@drawable/squircle_transparent_button_background"
app:layout_constraintWidth_max="@dimen/button_max_width"
app:layout_constraintStart_toEndOf="@id/title"
app:layout_constraintStart_toEndOf="@id/third_party_login_title"
app:layout_constraintEnd_toStartOf="@id/logo"
app:layout_constraintTop_toBottomOf="@id/title" />
app:layout_constraintTop_toBottomOf="@id/third_party_login_title" />
<androidx.constraintlayout.widget.Group
android:id="@+id/advanced_settings_group"

View file

@ -53,13 +53,13 @@
android:contentDescription="@string/content_description_go_back_icon"
android:background="@drawable/circle_transparent_button_background"
app:tint="?attr/color_main2_500"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintTop_toTopOf="@id/third_party_warning_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/title"/>
app:layout_constraintEnd_toStartOf="@id/third_party_warning_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/third_party_warning_title"
android:layout_width="wrap_content"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
@ -86,9 +86,9 @@
app:tint="?attr/color_main2_500"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintStart_toStartOf="@id/third_party_warning_title"
app:layout_constraintEnd_toStartOf="@id/no_video"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/third_party_warning_title"
app:layout_constraintBottom_toTopOf="@id/message"/>
<ImageView
@ -114,7 +114,7 @@
android:textSize="14sp"
android:gravity="start"
app:layout_constraintWidth_max="@dimen/button_max_width"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintStart_toStartOf="@id/third_party_warning_title"
app:layout_constraintTop_toBottomOf="@id/no_chat"
app:layout_constraintBottom_toTopOf="@id/contact"/>
@ -151,7 +151,7 @@
android:text="@string/assistant_third_party_sip_account_create_linphone_account"
app:layout_constraintWidth_max="@dimen/button_max_width"
app:layout_constraintVertical_bias="1"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintStart_toStartOf="@id/third_party_warning_title"
app:layout_constraintEnd_toEndOf="@id/message"
app:layout_constraintTop_toBottomOf="@id/contact"
app:layout_constraintBottom_toTopOf="@id/continue_third_party_account_login" />
@ -169,7 +169,7 @@
app:layout_constraintWidth_max="@dimen/button_max_width"
app:layout_constraintTop_toBottomOf="@id/create_linphone_account"
app:layout_constraintBottom_toTopOf="@id/mountains"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintStart_toStartOf="@id/third_party_warning_title"
app:layout_constraintEnd_toEndOf="@id/message" />
<ImageView

View file

@ -58,7 +58,7 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/login_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/assistant_account_login"
@ -84,8 +84,8 @@
android:background="@drawable/squircle_transparent_button_background"
app:drawableTint="?attr/color_main2_500"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title"/>
app:layout_constraintTop_toTopOf="@id/login_title"
app:layout_constraintBottom_toBottomOf="@id/login_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
@ -96,7 +96,7 @@
android:layout_marginEnd="16dp"
android:labelFor="@id/sip_identity"
android:text="@{@string/username + `*`, default=`Username*`}"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/login_title"
app:layout_constraintStart_toStartOf="@id/sip_identity"/>
<androidx.appcompat.widget.AppCompatEditText

View file

@ -55,7 +55,7 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/grant_permissions_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/assistant_permissions_title"

View file

@ -43,7 +43,7 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/scan_qr_code_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"

View file

@ -36,7 +36,7 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/register_code_confirmation_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/assistant_account_creation_sms_confirmation_title"
@ -58,7 +58,7 @@
android:textSize="14sp"
android:textColor="?attr/color_main2_600"
android:gravity="center_horizontal"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/register_code_confirmation_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

View file

@ -70,7 +70,7 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/register_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/assistant_account_register"
@ -92,7 +92,7 @@
app:tint="?attr/color_main2_500"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"/>
app:layout_constraintTop_toBottomOf="@id/register_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
@ -117,7 +117,7 @@
android:layout_marginEnd="16dp"
android:labelFor="@id/username"
android:text="@{@string/username + `*`}"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/register_title"
app:layout_constraintStart_toStartOf="@id/username"/>
<androidx.appcompat.widget.AppCompatEditText

View file

@ -45,7 +45,7 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/third_party_login_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/assistant_login_third_party_sip_account_title"
@ -65,7 +65,7 @@
android:layout_marginEnd="16dp"
android:labelFor="@id/username"
android:text="@{@string/username + `*`}"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/third_party_login_title"
app:layout_constraintStart_toStartOf="@id/username"/>
<androidx.appcompat.widget.AppCompatEditText

View file

@ -43,7 +43,7 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/assistant_page_title_style"
android:id="@+id/title"
android:id="@+id/third_party_warning_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/assistant_login_third_party_sip_account_title"
@ -67,7 +67,7 @@
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/no_video"
app:layout_constraintTop_toBottomOf="@id/title"/>
app:layout_constraintTop_toBottomOf="@id/third_party_warning_title"/>
<ImageView
android:id="@+id/no_video"
@ -81,7 +81,7 @@
app:tint="?attr/color_main2_500"
app:layout_constraintStart_toEndOf="@id/no_chat"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"/>
app:layout_constraintTop_toBottomOf="@id/third_party_warning_title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"

View file

@ -38,6 +38,7 @@ test = "1.7.0"
monitor = "1.8.0"
junit = "1.3.0"
espresso = "3.7.0"
uiautomator = "2.3.0"
[libraries]
androidx-annotations = { group = "androidx.annotation", name = "annotation", version.ref = "annotations" }
@ -83,6 +84,8 @@ androidx-test-runner = { group = "androidx.test", name = "runner", version.ref =
androidx-monitor = { group = "androidx.test", name = "monitor", version.ref = "monitor" }
androidx-junit = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "junit" }
androidx-espresso = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
androidx-espresso-intents = { group = "androidx.test.espresso", name = "espresso-intents", version.ref = "espresso" }
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }