Ajout de l’hostname et d’un répository pour le Token

This commit is contained in:
biloute02 2023-12-18 22:46:26 +01:00
parent ee751bdddb
commit a911de59b1
16 changed files with 264 additions and 45 deletions

View file

@ -1,18 +1,21 @@
package com.example.palto.data
package com.example.palto.data.local
import com.example.palto.data.Result
import com.example.palto.data.model.LoggedInUser
import java.io.IOException
/**
* Class that handles authentication w/ login credentials and retrieves user information.
*/
class LoginDataSource {
class LocalDataSource {
fun login(username: String, password: String): Result<LoggedInUser> {
try {
// TODO: handle loggedInUser authentication
val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
return Result.Success(fakeUser)
} catch (e: Throwable) {
return Result.Error(IOException("Error logging in", e))
}

View file

@ -1,9 +1,14 @@
package com.example.palto.data.model
import java.io.Serializable
/**
* Data class that captures user information for logged in users retrieved from LoginRepository
*/
data class LoggedInUser(
val userId: String,
val displayName: String
)
val id: String,
val username: String,
val first_name: String,
val last_name: String,
val email: String
) : Serializable

View file

@ -0,0 +1,10 @@
package com.example.palto.data.model
import java.io.Serializable
/**
* Data class that captures tokens for logged in users retrieved from LoginRepository
*/
data class Tokens(
val refresh: String,
val access: String
) : Serializable

View file

@ -0,0 +1,47 @@
package com.example.palto.data.network
import com.example.palto.data.Result
import com.example.palto.data.model.LoggedInUser
import com.example.palto.data.model.Tokens
import java.io.IOException
/**
* Class that handles API calls.
*/
class ServerDataSource {
private var hostname: String? = null
fun requestToken(hostname: String, username: String, password: String): Result<Tokens> {
try {
val tokens = Tokens()
return Result.Success()
} catch () {
return Result.Error()
}
}
fun refreshToken(): Result<Tokens> {
}
fun verifyToken(): Boolean {
}
fun login(hostname: String, username: String, password: String): Result<LoggedInUser> {
try {
/*
val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
return Result.Success(fakeUser)
*/
return
} catch (e: Throwable) {
return Result.Error(IOException("Error logging in", e))
}
}
fun logout() {
// TODO: revoke authentication
}
}

View file

@ -1,15 +1,17 @@
package com.example.palto.data
package com.example.palto.data.repository
import com.example.palto.data.Result
import com.example.palto.data.network.ServerDataSource
import com.example.palto.data.model.LoggedInUser
import com.example.palto.data.model.Tokens
/**
* Class that requests authentication and user information from the remote data source and
* maintains an in-memory cache of login status and user credentials information.
*/
class LoginRepository(val dataSource: LoginDataSource) {
class LoginRepository(val dataSource: ServerDataSource) {
// in-memory cache of the loggedInUser object
var user: LoggedInUser? = null
private set
@ -27,12 +29,16 @@ class LoginRepository(val dataSource: LoginDataSource) {
dataSource.logout()
}
fun login(username: String, password: String): Result<LoggedInUser> {
fun login(
hostname: String,
username: String,
password: String
): Result<LoggedInUser> {
// handle login
val result = dataSource.login(username, password)
val result = dataSource.login(hostname, username, password)
if (result is Result.Success) {
setLoggedInUser(result.data)
setTokens(result.data)
}
return result

View file

@ -0,0 +1,45 @@
package com.example.palto.data.repository
import com.example.palto.data.Result
import com.example.palto.data.network.ServerDataSource
import com.example.palto.data.model.LoggedInUser
import com.example.palto.data.model.Tokens
/**
* Class that requests authentication and user information from the remote data source and
* maintains an in-memory cache of login status and user credentials information.
*/
class TokensRepository(val dataSource: ServerDataSource) {
var tokens: Tokens? = null
private set
/*
val isLoggedIn: Boolean
get() = user != null
*/
init {
// If user credentials will be cached in local storage, it is recommended it be encrypted
// @see https://developer.android.com/training/articles/keystore
tokens = null
}
/*
fun login(username: String, password: String): Result<LoggedInUser> {
// handle login
val result = dataSource.login(username, password)
if (result is Result.Success) {
setLoggedInUser(result.data)
}
return result
}
*/
private fun setTokens(tokens: Tokens) {
this.tokens = tokens
}
}

View file

@ -4,6 +4,7 @@ package com.example.palto.ui.login
* Data validation state of the login form.
*/
data class LoginFormState(
val hostnameError: Int? = null,
val usernameError: Int? = null,
val passwordError: Int? = null,
val isDataValid: Boolean = false

View file

@ -45,6 +45,7 @@ class LoginFragment : Fragment() {
loginViewModel = ViewModelProvider(this, LoginViewModelFactory())
.get(LoginViewModel::class.java)
val hostnameEditText = binding.hostname
val usernameEditText = binding.username
val passwordEditText = binding.password
val loginButton = binding.login
@ -57,6 +58,9 @@ class LoginFragment : Fragment() {
return@Observer
}
loginButton.isEnabled = loginFormState.isDataValid
loginFormState.hostnameError?.let {
hostnameEditText.error = getString(it)
}
loginFormState.usernameError?.let {
usernameEditText.error = getString(it)
}
@ -79,26 +83,25 @@ class LoginFragment : Fragment() {
})
val afterTextChangedListener = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// ignore
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
// ignore
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(s: Editable) {
loginViewModel.loginDataChanged(
hostnameEditText.text.toString(),
usernameEditText.text.toString(),
passwordEditText.text.toString()
)
}
}
hostnameEditText.addTextChangedListener(afterTextChangedListener)
usernameEditText.addTextChangedListener(afterTextChangedListener)
passwordEditText.addTextChangedListener(afterTextChangedListener)
passwordEditText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
loginViewModel.login(
hostnameEditText.text.toString(),
usernameEditText.text.toString(),
passwordEditText.text.toString()
)
@ -110,6 +113,7 @@ class LoginFragment : Fragment() {
loginButton.setOnClickListener {
loadingProgressBar.visibility = View.VISIBLE
loginViewModel.login(
hostnameEditText.text.toString(),
usernameEditText.text.toString(),
passwordEditText.text.toString()
)

View file

@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import android.util.Patterns
import com.example.palto.data.LoginRepository
import com.example.palto.data.repository.LoginRepository
import com.example.palto.data.Result
import com.example.palto.R
@ -17,7 +17,10 @@ class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel()
private val _loginResult = MutableLiveData<LoginResult>()
val loginResult: LiveData<LoginResult> = _loginResult
fun login(username: String, password: String) {
fun login(
hostname: String,
username: String,
password: String) {
// can be launched in a separate asynchronous job
val result = loginRepository.login(username, password)
@ -29,8 +32,13 @@ class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel()
}
}
fun loginDataChanged(username: String, password: String) {
if (!isUserNameValid(username)) {
fun loginDataChanged(
hostname: String,
username: String,
password: String) {
if (!isHostNameValid(hostname)) {
_loginForm.value = LoginFormState(hostnameError = R.string.invalid_hostname)
} else if (!isUserNameValid(username)) {
_loginForm.value = LoginFormState(usernameError = R.string.invalid_username)
} else if (!isPasswordValid(password)) {
_loginForm.value = LoginFormState(passwordError = R.string.invalid_password)
@ -39,6 +47,10 @@ class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel()
}
}
private fun isHostNameValid(hostname: String): Boolean {
return hostname.isNotBlank()
}
// A placeholder username validation check
private fun isUserNameValid(username: String): Boolean {
return if (username.contains("@")) {

View file

@ -2,8 +2,8 @@ package com.example.palto.ui.login
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.palto.data.LoginDataSource
import com.example.palto.data.LoginRepository
import com.example.palto.data.network.ServerDataSource
import com.example.palto.data.repository.LoginRepository
/**
* ViewModel provider factory to instantiate LoginViewModel.
@ -16,7 +16,7 @@ class LoginViewModelFactory : ViewModelProvider.Factory {
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
return LoginViewModel(
loginRepository = LoginRepository(
dataSource = LoginDataSource()
dataSource = ServerDataSource()
)
) as T
}

View file

@ -0,0 +1,32 @@
package com.example.palto.ui.sheet
import androidx.lifecycle.ViewModelProvider
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.palto.R
class SheetDetailFragment : Fragment() {
companion object {
fun newInstance() = SheetDetailFragment()
}
private lateinit var viewModel: SheetDetailViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_sheet_detail, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(SheetDetailViewModel::class.java)
// TODO: Use the ViewModel
}
}

View file

@ -0,0 +1,7 @@
package com.example.palto.ui.sheet
import androidx.lifecycle.ViewModel
class SheetDetailViewModel : ViewModel() {
// TODO: Implement the ViewModel
}

View file

@ -11,27 +11,44 @@
android:paddingBottom="@dimen/fragment_vertical_margin"
tools:context=".ui.login.LoginFragment">
<EditText
android:id="@+id/hostname"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="24dp"
android:autofillHints="@string/prompt_server"
android:hint="@string/prompt_hostname"
android:inputType="text"
android:selectAllOnFocus="true"
app:layout_constraintBottom_toTopOf="@+id/username"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
android:autofillHints="@string/prompt_email"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:autofillHints="@string/prompt_username"
android:hint="@string/prompt_username"
android:inputType="text"
android:selectAllOnFocus="true"
app:layout_constraintBottom_toTopOf="@+id/password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@+id/hostname" />
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:autofillHints="@string/prompt_password"
android:hint="@string/prompt_password"
@ -39,25 +56,38 @@
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
app:layout_constraintBottom_toTopOf="@+id/helpmsg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username" />
<TextView
android:id="@+id/helpmsg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Identifiants Invalides"
app:layout_constraintBottom_toTopOf="@+id/login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password" />
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="48dp"
android:layout_marginTop="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="48dp"
android:layout_marginBottom="64dp"
android:enabled="false"
android:text="@string/action_sign_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password"
app:layout_constraintTop_toBottomOf="@+id/helpmsg"
app:layout_constraintVertical_bias="0.2" />
<ProgressBar
@ -75,4 +105,5 @@
app:layout_constraintStart_toStartOf="@+id/password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.sheet.SheetDetailFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello" />
</FrameLayout>

View file

@ -21,9 +21,10 @@
android:id="@+id/itemFragment"
android:name="com.example.palto.ItemFragment"
android:label="fragment_item_list"
tools:layout="@layout/fragment_item_list" >
<action
android:id="@+id/action_itemFragment_to_ficheFragment"
app:destination="@id/ficheFragment" />
</fragment>
tools:layout="@layout/fragment_item_list" />
<fragment
android:id="@+id/sheetDetailFragment"
android:name="com.example.palto.ui.sheet.SheetDetailFragment"
android:label="fragment_sheet_detail"
tools:layout="@layout/fragment_sheet_detail" />
</navigation>

View file

@ -1,12 +1,14 @@
<resources>
<string name="app_name">Palto</string>
<!-- Strings related to login -->
<string name="prompt_email">Email</string>
<string name="prompt_password">Password</string>
<string name="action_sign_in">Sign in or register</string>
<string name="prompt_hostname">Serveur</string>
<string name="prompt_username">Nom dutilisateur</string>
<string name="prompt_password">Mot de passe</string>
<string name="action_sign_in">Connexion</string>
<string name="action_sign_in_short">Sign in</string>
<string name="welcome">"Welcome!"</string>
<string name="invalid_username">Not a valid username</string>
<string name="invalid_password">Password must be >5 characters</string>
<string name="login_failed">"Login failed"</string>
<string name="welcome">"Bienvenue !"</string>
<string name="invalid_hostname">Serveur inaccessible</string>
<string name="invalid_username">Nom dutilisateur non valide</string>
<string name="invalid_password">Mot de passe invalide</string>
<string name="login_failed">"Erreur de connexion !"</string>
</resources>