Ajout de l’hostname et d’un répository pour le Token
This commit is contained in:
parent
ee751bdddb
commit
a911de59b1
16 changed files with 264 additions and 45 deletions
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
|
|
10
app/src/main/java/com/example/palto/data/model/Tokens.kt
Normal file
10
app/src/main/java/com/example/palto/data/model/Tokens.kt
Normal 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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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("@")) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.example.palto.ui.sheet
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class SheetDetailViewModel : ViewModel() {
|
||||
// TODO: Implement the ViewModel
|
||||
}
|
|
@ -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>
|
13
app/src/main/res/layout/fragment_sheet_detail.xml
Normal file
13
app/src/main/res/layout/fragment_sheet_detail.xml
Normal 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>
|
|
@ -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>
|
|
@ -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 d’utilisateur</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 d’utilisateur non valide</string>
|
||||
<string name="invalid_password">Mot de passe invalide</string>
|
||||
<string name="login_failed">"Erreur de connexion !"</string>
|
||||
</resources>
|
Loading…
Reference in a new issue