Ajout de la connexion d’un utilisateur au serveur Palto et connexion anonyme hors ligne.y
This commit is contained in:
parent
69d1880295
commit
e81f70f045
17 changed files with 207 additions and 310 deletions
|
@ -1,9 +0,0 @@
|
||||||
package com.example.palto
|
|
||||||
|
|
||||||
import android.nfc.Tag
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class PaltoViewModel: ViewModel() {
|
|
||||||
val tagLiveData = MutableLiveData<Tag>()
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package com.example.palto.data.local
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class that handles authentication w/ login credentials and retrieves user information.
|
|
||||||
*/
|
|
||||||
class LocalDataSource {
|
|
||||||
/*
|
|
||||||
fun login(username: String, password: String): Result<LoggedInUser> {
|
|
||||||
try {
|
|
||||||
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun logout() {
|
|
||||||
// TODO: revoke authentication
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
|
@ -1,10 +1,3 @@
|
||||||
package com.example.palto.data.repository
|
package com.example.palto.data.repository
|
||||||
|
|
||||||
import com.example.palto.data.network.PaltoApiService
|
class AttendanceRepository
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class AttendanceRepository() {
|
|
||||||
// private val cards
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
package com.example.palto.data.repository
|
|
||||||
|
|
||||||
import com.example.palto.data.Result
|
|
||||||
import com.example.palto.data.network.ServerDataSource
|
|
||||||
import com.example.palto.model.LoggedInUser
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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: ServerDataSource) {
|
|
||||||
|
|
||||||
var user: LoggedInUser? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
val isLoggedIn: Boolean
|
|
||||||
get() = user != null
|
|
||||||
|
|
||||||
init {
|
|
||||||
user = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun logout() {
|
|
||||||
user = null
|
|
||||||
dataSource.logout()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun login(
|
|
||||||
hostname: String,
|
|
||||||
username: String,
|
|
||||||
password: String
|
|
||||||
): Result<LoggedInUser> {
|
|
||||||
// handle login
|
|
||||||
val result = dataSource.login(hostname, username, password)
|
|
||||||
|
|
||||||
if (result is Result.Success) {
|
|
||||||
setLoggedInUser(result.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setLoggedInUser(loggedInUser: LoggedInUser) {
|
|
||||||
this.user = loggedInUser
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,3 @@
|
||||||
package com.example.palto.data.repository
|
package com.example.palto.data.repository
|
||||||
|
|
||||||
import com.example.palto.data.network.PaltoApiService
|
class SessionRepository
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class SessionRepository(val dataSource: PaltoApiService) {
|
|
||||||
// private val cards
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.example.palto.data.repository
|
||||||
|
|
||||||
|
class UserRepository
|
|
@ -1,14 +1,13 @@
|
||||||
package com.example.palto.model
|
package com.example.palto.domain
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class that captures user information for logged in users retrieved from LoginRepository
|
* Data class that captures user information for logged in users retrieved from LoginRepository
|
||||||
*/
|
*/
|
||||||
data class LoggedInUser(
|
data class User(
|
||||||
val id: String,
|
//Not in the domain layer
|
||||||
|
//val id: String,
|
||||||
val username: String,
|
val username: String,
|
||||||
val first_name: String,
|
val first_name: String,
|
||||||
val last_name: String,
|
val last_name: String,
|
||||||
val email: String
|
val email: String
|
||||||
) : Serializable
|
)
|
78
app/src/main/java/com/example/palto/ui/UserViewModel.kt
Normal file
78
app/src/main/java/com/example/palto/ui/UserViewModel.kt
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package com.example.palto.ui
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.example.palto.R
|
||||||
|
import com.example.palto.data.repository.TokenRepository
|
||||||
|
import com.example.palto.data.repository.UserRepository
|
||||||
|
import com.example.palto.domain.User
|
||||||
|
import com.example.palto.ui.login.LoginResult
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class UserViewModel(
|
||||||
|
private val tokenRepository: TokenRepository,
|
||||||
|
private val userRepository: UserRepository
|
||||||
|
): ViewModel() {
|
||||||
|
|
||||||
|
private var _result = MutableLiveData<LoginResult>()
|
||||||
|
val result = _result as LiveData<LoginResult>
|
||||||
|
|
||||||
|
// User is initially set to null to be disconnected.
|
||||||
|
private var _user = MutableLiveData<User?>(null)
|
||||||
|
val user = _user as LiveData<User?>
|
||||||
|
|
||||||
|
fun login(
|
||||||
|
hostname: String,
|
||||||
|
username: String,
|
||||||
|
password: String) {
|
||||||
|
|
||||||
|
// Coroutine runs in background.
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
tokenRepository.authenticate(hostname, username, password)
|
||||||
|
_user.value = User(
|
||||||
|
username,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
_result.value = LoginResult(success = true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Palto", "Connection error: " + e.message)
|
||||||
|
_result.value = LoginResult(
|
||||||
|
success = false,
|
||||||
|
error = R.string.login_failed,
|
||||||
|
exception = e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loginAnonymous() {
|
||||||
|
_user.value = User(
|
||||||
|
"anonymous", "", "", ""
|
||||||
|
)
|
||||||
|
_result.value = LoginResult(success = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logout() {
|
||||||
|
_user.value = null
|
||||||
|
_result.value = LoginResult(success = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return UserViewModel(
|
||||||
|
tokenRepository = TokenRepository(),
|
||||||
|
userRepository = UserRepository()
|
||||||
|
) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
package com.example.palto.ui.login
|
|
||||||
|
|
||||||
/* Est-ce que c’est util ?
|
|
||||||
* Updater la vue dans le fragment
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User details post authentication that is exposed to the UI
|
|
||||||
*/
|
|
||||||
data class LoggedInUserView(
|
|
||||||
val displayName: String
|
|
||||||
//... other data fields that may be accessible to the UI
|
|
||||||
)
|
|
|
@ -1,42 +1,68 @@
|
||||||
package com.example.palto.ui.login
|
package com.example.palto.ui.login
|
||||||
|
|
||||||
import androidx.lifecycle.Observer
|
import android.annotation.SuppressLint
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.navGraphViewModels
|
|
||||||
import com.example.palto.databinding.FragmentLoginBinding
|
import com.example.palto.databinding.FragmentLoginBinding
|
||||||
|
import com.example.palto.ui.UserViewModel
|
||||||
import com.example.palto.R
|
|
||||||
|
|
||||||
class LoginFragment : Fragment() {
|
class LoginFragment : Fragment() {
|
||||||
|
|
||||||
private val loginViewModel: LoginViewModel by
|
// loginViewModel is used to update the login screen dynamically.
|
||||||
navGraphViewModels(R.id.nav_graph) { LoginViewModelFactory() }
|
private val loginViewModel: LoginViewModel by viewModels()
|
||||||
|
|
||||||
private var _binding: FragmentLoginBinding? = null
|
// userViewModel is where the user is logged in, at the activity level.
|
||||||
|
private val userViewModel: UserViewModel by activityViewModels() { UserViewModel.Factory }
|
||||||
|
|
||||||
// This property is only valid between onCreateView and onDestroyView.
|
private lateinit var binding: FragmentLoginBinding
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
|
binding = FragmentLoginBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
val navController = findNavController()
|
||||||
|
|
||||||
|
// Bind the login button.
|
||||||
|
binding.login.setOnClickListener {
|
||||||
|
binding.loading.visibility = View.VISIBLE
|
||||||
|
userViewModel.login(
|
||||||
|
binding.hostname.text.toString(),
|
||||||
|
binding.username.text.toString(),
|
||||||
|
binding.password.text.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind anonymous login clickable text.
|
||||||
|
binding.loginAnonymous.setOnClickListener {
|
||||||
|
userViewModel.loginAnonymous()
|
||||||
|
}
|
||||||
|
|
||||||
|
// On result of logging.
|
||||||
|
userViewModel.result.observe(viewLifecycleOwner) {
|
||||||
|
binding.loading.visibility = View.GONE
|
||||||
|
if (it.success) {
|
||||||
|
navController.popBackStack()
|
||||||
|
} else if (it.error != null) {
|
||||||
|
binding.loginError.text = "Exception : " + it.exception.toString()
|
||||||
|
Toast.makeText(activity, it.error, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_binding = FragmentLoginBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
@ -46,23 +72,21 @@ class LoginFragment : Fragment() {
|
||||||
val loginButton = binding.login
|
val loginButton = binding.login
|
||||||
val loadingProgressBar = binding.loading
|
val loadingProgressBar = binding.loading
|
||||||
|
|
||||||
//
|
loginViewModel.loginFormState.observe(viewLifecycleOwner) {
|
||||||
loginViewModel.loginFormState.observe(viewLifecycleOwner,
|
if (it == null) {
|
||||||
Observer { loginFormState ->
|
return@Observer
|
||||||
if (loginFormState == null) {
|
}
|
||||||
return@Observer
|
loginButton.isEnabled = it.isDataValid
|
||||||
}
|
it.hostnameError?.let {
|
||||||
loginButton.isEnabled = loginFormState.isDataValid
|
hostnameEditText.error = getString(it)
|
||||||
loginFormState.hostnameError?.let {
|
}
|
||||||
hostnameEditText.error = getString(it)
|
it.usernameError?.let {
|
||||||
}
|
usernameEditText.error = getString(it)
|
||||||
loginFormState.usernameError?.let {
|
}
|
||||||
usernameEditText.error = getString(it)
|
it.passwordError?.let {
|
||||||
}
|
passwordEditText.error = getString(it)
|
||||||
loginFormState.passwordError?.let {
|
}
|
||||||
passwordEditText.error = getString(it)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
loginViewModel.loginResult.observe(viewLifecycleOwner,
|
loginViewModel.loginResult.observe(viewLifecycleOwner,
|
||||||
Observer { loginResult ->
|
Observer { loginResult ->
|
||||||
|
@ -103,17 +127,8 @@ class LoginFragment : Fragment() {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Damien : Le setOnClickListener est là !
|
|
||||||
loginButton.setOnClickListener {
|
|
||||||
loadingProgressBar.visibility = View.VISIBLE
|
|
||||||
loginViewModel.login(
|
|
||||||
hostnameEditText.text.toString(),
|
|
||||||
usernameEditText.text.toString(),
|
|
||||||
passwordEditText.text.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private fun updateUiWithUser(model: LoggedInUserView) {
|
private fun updateUiWithUser(model: LoggedInUserView) {
|
||||||
|
@ -122,15 +137,11 @@ class LoginFragment : Fragment() {
|
||||||
val appContext = context?.applicationContext ?: return
|
val appContext = context?.applicationContext ?: return
|
||||||
Toast.makeText(appContext, welcome, Toast.LENGTH_LONG).show()
|
Toast.makeText(appContext, welcome, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
private fun showLoginFailed(@StringRes errorString: Int) {
|
private fun showLoginFailed(@StringRes errorString: Int) {
|
||||||
val appContext = context?.applicationContext ?: return
|
val appContext = context?.applicationContext ?: return
|
||||||
Toast.makeText(appContext, errorString, Toast.LENGTH_LONG).show()
|
Toast.makeText(appContext, errorString, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
*/
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
package com.example.palto.ui.login
|
package com.example.palto.ui.login
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication result : success (user details) or error message.
|
* Authentication result : success is true if connected or error message with exception.
|
||||||
*/
|
*/
|
||||||
data class LoginResult(
|
data class LoginResult(
|
||||||
val success: LoggedInUserView? = null,
|
val success: Boolean,
|
||||||
val error: Int? = null
|
val error: Int? = null, // Id of the string resource to display to the user.
|
||||||
|
val exception: Exception? = null
|
||||||
)
|
)
|
|
@ -1,40 +1,14 @@
|
||||||
package com.example.palto.ui.login
|
package com.example.palto.ui.login
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import android.util.Patterns
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.example.palto.data.repository.LoginRepository
|
|
||||||
|
|
||||||
import com.example.palto.R
|
/**
|
||||||
import com.example.palto.data.network.ServerDataSource
|
* View Model to check dynamically the values of the form.
|
||||||
|
*/
|
||||||
class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
|
class LoginViewModel() : ViewModel() {
|
||||||
|
/*
|
||||||
private val _loginForm = MutableLiveData<LoginFormState>()
|
private val _loginFormState = MutableLiveData<LoginFormState>()
|
||||||
val loginFormState: LiveData<LoginFormState> = _loginForm
|
val loginFormState: LiveData<LoginFormState> = _loginFormState
|
||||||
|
|
||||||
private val _loginResult = MutableLiveData<LoginResult>()
|
|
||||||
val loginResult: LiveData<LoginResult> = _loginResult
|
|
||||||
|
|
||||||
fun login(
|
|
||||||
hostname: String,
|
|
||||||
username: String,
|
|
||||||
password: String) {
|
|
||||||
// can be launched in a separate asynchronous job
|
|
||||||
val result = loginRepository.login(hostname, username, password)
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (result is Result.Success) {
|
|
||||||
_loginResult.value =
|
|
||||||
LoginResult(success = LoggedInUserView(
|
|
||||||
displayName = result.data.displayName))
|
|
||||||
} else {
|
|
||||||
_loginResult.value = LoginResult(error = R.string.login_failed)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loginDataChanged(
|
fun loginDataChanged(
|
||||||
hostname: String,
|
hostname: String,
|
||||||
|
@ -50,35 +24,18 @@ class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel()
|
||||||
_loginForm.value = LoginFormState(isDataValid = true)
|
_loginForm.value = LoginFormState(isDataValid = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isHostNameValid(hostname: String): Boolean {
|
private fun isHostNameValid(hostname: String): Boolean {
|
||||||
return hostname.isNotBlank()
|
return hostname.isNotBlank()
|
||||||
}
|
}
|
||||||
|
|
||||||
// A placeholder username validation check
|
|
||||||
private fun isUserNameValid(username: String): Boolean {
|
private fun isUserNameValid(username: String): Boolean {
|
||||||
return if (username.contains("@")) {
|
return username.isNotBlank()
|
||||||
Patterns.EMAIL_ADDRESS.matcher(username).matches()
|
|
||||||
} else {
|
|
||||||
username.isNotBlank()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A placeholder password validation check
|
|
||||||
private fun isPasswordValid(password: String): Boolean {
|
private fun isPasswordValid(password: String): Boolean {
|
||||||
return password.length > 5
|
return password.length > 5
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoginViewModelFactory : ViewModelProvider.Factory {
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
|
|
||||||
return LoginViewModel(
|
|
||||||
loginRepository = LoginRepository(
|
|
||||||
dataSource = ServerDataSource()
|
|
||||||
)
|
|
||||||
) as T
|
|
||||||
}
|
|
||||||
throw IllegalArgumentException("Unknown ViewModel class")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -27,8 +27,20 @@ class MenuFragment : Fragment() {
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
_binding = FragmentMenuListBinding.inflate(inflater, container, false)
|
binding = FragmentMenuListBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
val navController = findNavController()
|
||||||
|
|
||||||
|
// Connect the user.
|
||||||
|
userViewModel.user.observe(viewLifecycleOwner) {
|
||||||
|
if (it != null) {
|
||||||
|
// Get sessions of the user from remote.
|
||||||
|
} else {
|
||||||
|
navController.navigate(R.id.loginFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the list of sessions.
|
||||||
val adapter = MenuAdapter()
|
val adapter = MenuAdapter()
|
||||||
binding.menuList.adapter = adapter
|
binding.menuList.adapter = adapter
|
||||||
menuViewModel.sessions.observe(viewLifecycleOwner) {
|
menuViewModel.sessions.observe(viewLifecycleOwner) {
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
package com.example.palto.ui.sessionList.placeholder
|
|
||||||
|
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.HashMap
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for providing sample content for user interfaces created by
|
|
||||||
* Android template wizards.
|
|
||||||
*
|
|
||||||
* TODO: Replace all uses of this class before publishing your app.
|
|
||||||
*/
|
|
||||||
object PlaceholderContent {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of sample (placeholder) items.
|
|
||||||
*/
|
|
||||||
val ITEMS: MutableList<PlaceholderItem> = ArrayList()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of sample (placeholder) items, by ID.
|
|
||||||
*/
|
|
||||||
val ITEM_MAP: MutableMap<String, PlaceholderItem> = HashMap()
|
|
||||||
|
|
||||||
private val COUNT = 25
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Add some sample items.
|
|
||||||
for (i in 1..COUNT) {
|
|
||||||
addItem(createPlaceholderItem(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addItem(item: PlaceholderItem) {
|
|
||||||
ITEMS.add(item)
|
|
||||||
ITEM_MAP.put(item.id, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createPlaceholderItem(position: Int): PlaceholderItem {
|
|
||||||
return PlaceholderItem(position.toString(), "Item " + position, makeDetails(position))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeDetails(position: Int): String {
|
|
||||||
val builder = StringBuilder()
|
|
||||||
builder.append("Details about Item: ").append(position)
|
|
||||||
for (i in 0..position - 1) {
|
|
||||||
builder.append("\nMore details information here.")
|
|
||||||
}
|
|
||||||
return builder.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A placeholder item representing a piece of content.
|
|
||||||
*/
|
|
||||||
data class PlaceholderItem(val id: String, val content: String, val details: String) {
|
|
||||||
override fun toString(): String = content
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,11 +13,8 @@
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/hostname"
|
android:id="@+id/hostname"
|
||||||
android:layout_width="0dp"
|
android:layout_width="331dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginTop="64dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:autofillHints="@string/prompt_hostname"
|
android:autofillHints="@string/prompt_hostname"
|
||||||
android:hint="@string/prompt_hostname"
|
android:hint="@string/prompt_hostname"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
|
@ -30,10 +27,8 @@
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/username"
|
android:id="@+id/username"
|
||||||
android:layout_width="0dp"
|
android:layout_width="331dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:autofillHints="@string/prompt_username"
|
android:autofillHints="@string/prompt_username"
|
||||||
android:hint="@string/prompt_username"
|
android:hint="@string/prompt_username"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
|
@ -46,29 +41,28 @@
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/password"
|
android:id="@+id/password"
|
||||||
android:layout_width="0dp"
|
android:layout_width="331dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="50dp"
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:autofillHints="@string/prompt_password"
|
android:autofillHints="@string/prompt_password"
|
||||||
android:hint="@string/prompt_password"
|
android:hint="@string/prompt_password"
|
||||||
android:imeActionLabel="@string/action_sign_in_short"
|
android:imeActionLabel="@string/action_sign_in_short"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/help_message"
|
app:layout_constraintBottom_toTopOf="@+id/login_error"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/username" />
|
app:layout_constraintTop_toBottomOf="@+id/username" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/help_message"
|
android:id="@+id/login_error"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/help_message"
|
android:text=""
|
||||||
app:layout_constraintBottom_toTopOf="@+id/login"
|
app:layout_constraintBottom_toTopOf="@+id/login"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/password" />
|
app:layout_constraintTop_toBottomOf="@+id/password" />
|
||||||
|
|
||||||
|
@ -77,33 +71,38 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start"
|
android:layout_gravity="start"
|
||||||
android:layout_marginStart="48dp"
|
android:enabled="true"
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="48dp"
|
|
||||||
android:layout_marginBottom="64dp"
|
|
||||||
android:enabled="false"
|
|
||||||
android:text="@string/action_sign_in"
|
android:text="@string/action_sign_in"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/login_anonymous"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/login_error" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading"
|
||||||
|
android:layout_width="39dp"
|
||||||
|
android:layout_height="47dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/help_message"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_bias="0.2" />
|
app:layout_constraintVertical_bias="0.5" />
|
||||||
|
|
||||||
<ProgressBar
|
<TextView
|
||||||
android:id="@+id/loading"
|
android:id="@+id/login_anonymous"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:clickable="true"
|
||||||
android:layout_marginStart="32dp"
|
android:text="@string/login_anonymous_text"
|
||||||
android:layout_marginTop="64dp"
|
android:textColor="#F4511E"
|
||||||
android:layout_marginEnd="32dp"
|
|
||||||
android:layout_marginBottom="64dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/password"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@+id/password"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintVertical_bias="0.3" />
|
app:layout_constraintTop_toBottomOf="@+id/login" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -5,6 +5,7 @@
|
||||||
<string name="prompt_username">Nom d’utilisateur</string>
|
<string name="prompt_username">Nom d’utilisateur</string>
|
||||||
<string name="prompt_password">Mot de passe</string>
|
<string name="prompt_password">Mot de passe</string>
|
||||||
<string name="action_sign_in">Connexion</string>
|
<string name="action_sign_in">Connexion</string>
|
||||||
|
<string name="login_anonymous_text">Connexion anonyme</string>
|
||||||
<string name="action_sign_in_short">Sign in</string>
|
<string name="action_sign_in_short">Sign in</string>
|
||||||
<string name="welcome">"Bienvenue !"</string>
|
<string name="welcome">"Bienvenue !"</string>
|
||||||
<string name="invalid_hostname">Serveur inaccessible</string>
|
<string name="invalid_hostname">Serveur inaccessible</string>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.2.0" apply false
|
id("com.android.application") version "8.2.1" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
|
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
|
||||||
}
|
}
|
Loading…
Reference in a new issue