Écran pour ajouter un étudiant à une liste.
Sauvegarde d’une fiche de présence dans une session.
This commit is contained in:
parent
035030ca0f
commit
acb73364a1
28 changed files with 562 additions and 208 deletions
22
app/src/main/java/com/example/palto/NfcViewModel.kt
Normal file
22
app/src/main/java/com/example/palto/NfcViewModel.kt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package com.example.palto
|
||||||
|
|
||||||
|
import android.nfc.Tag
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.example.palto.domain.Card
|
||||||
|
import com.example.palto.domain.Event
|
||||||
|
import com.example.palto.domain.User
|
||||||
|
|
||||||
|
class NfcViewModel: ViewModel() {
|
||||||
|
|
||||||
|
private var _tagId = MutableLiveData<Event<String>>()
|
||||||
|
val tagId: LiveData<Event<String>> = _tagId
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
fun setTag(tag: Tag) {
|
||||||
|
Log.d("Nfc", "A new tag has been set.")
|
||||||
|
_tagId.postValue(Event(tag.id.toHexString()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,7 @@ import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.AppBarConfiguration
|
import androidx.navigation.ui.AppBarConfiguration
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import com.example.palto.databinding.ActivityPaltoBinding
|
import com.example.palto.databinding.ActivityPaltoBinding
|
||||||
import com.example.palto.ui.NfcViewModel
|
import com.example.palto.ui.CardViewModel
|
||||||
import com.example.palto.ui.UserViewModel
|
|
||||||
|
|
||||||
|
|
||||||
class PaltoActivity : AppCompatActivity() {
|
class PaltoActivity : AppCompatActivity() {
|
||||||
|
@ -21,8 +20,6 @@ class PaltoActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val nfcViewModel: NfcViewModel by viewModels()
|
private val nfcViewModel: NfcViewModel by viewModels()
|
||||||
|
|
||||||
private val userViewModel: UserViewModel by viewModels() { UserViewModel.Factory }
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityPaltoBinding
|
private lateinit var binding: ActivityPaltoBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -81,7 +78,7 @@ class PaltoActivity : AppCompatActivity() {
|
||||||
// Begin to read NFC Cards.
|
// Begin to read NFC Cards.
|
||||||
nfcAdapter?.enableReaderMode(
|
nfcAdapter?.enableReaderMode(
|
||||||
this,
|
this,
|
||||||
nfcViewModel.tag::postValue,
|
nfcViewModel::setTag,
|
||||||
NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
|
NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package com.example.palto.domain
|
package com.example.palto.domain
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class that captures tokens for logged in users retrieved from LoginRepository
|
* Data class that captures tokens for logged in users retrieved from LoginRepository
|
||||||
*/
|
*/
|
||||||
data class Attendance(
|
data class Attendance(
|
||||||
val date: String,
|
val id: Int,
|
||||||
val access: String
|
val student: User,
|
||||||
|
val date: LocalTime
|
||||||
) : Serializable
|
) : Serializable
|
|
@ -1,22 +1,7 @@
|
||||||
package com.example.palto.domain
|
package com.example.palto.domain
|
||||||
|
|
||||||
data class Card(
|
data class Card(
|
||||||
val id: String,
|
val id: Int,
|
||||||
val uid: ByteArray,
|
val tagId: String,
|
||||||
val department: String,
|
val user: User,
|
||||||
val owner: String
|
)
|
||||||
) {
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Card
|
|
||||||
|
|
||||||
return uid.contentEquals(other.uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return uid.contentHashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
18
app/src/main/java/com/example/palto/domain/Event.kt
Normal file
18
app/src/main/java/com/example/palto/domain/Event.kt
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package com.example.palto.domain
|
||||||
|
|
||||||
|
class Event<out T>(private val content: T) {
|
||||||
|
|
||||||
|
var hasBeenHandled = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun getContentIfNotHandled(): T? {
|
||||||
|
return if (hasBeenHandled) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
hasBeenHandled = true
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun peekContent(): T = content
|
||||||
|
}
|
|
@ -7,5 +7,6 @@ import java.io.Serializable
|
||||||
data class Session(
|
data class Session(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
var attendances: List<User>
|
var attendances: List<Attendance>
|
||||||
|
// When the list is updated, it is replaced by a new one.
|
||||||
)
|
)
|
|
@ -4,10 +4,9 @@ package com.example.palto.domain
|
||||||
* 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 User(
|
data class User(
|
||||||
//Not in the domain layer
|
val id: Int,
|
||||||
//val id: String,
|
|
||||||
val username: String,
|
val username: String,
|
||||||
val first_name: String,
|
val firstName: String,
|
||||||
val last_name: String,
|
val lastName: String,
|
||||||
val email: String
|
val email: String
|
||||||
)
|
)
|
||||||
|
|
34
app/src/main/java/com/example/palto/ui/CardViewModel.kt
Normal file
34
app/src/main/java/com/example/palto/ui/CardViewModel.kt
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package com.example.palto.ui
|
||||||
|
|
||||||
|
import android.nfc.Tag
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.example.palto.domain.Card
|
||||||
|
import com.example.palto.domain.User
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CardViewModel maintain a list of cards application wide.
|
||||||
|
* May be converted in a repository.
|
||||||
|
*/
|
||||||
|
class CardViewModel: ViewModel() {
|
||||||
|
|
||||||
|
private var _cards = MutableLiveData<List<Card>>()
|
||||||
|
private val cards: LiveData<List<Card>> = _cards
|
||||||
|
|
||||||
|
fun createCard(user: User, tagId: String): Card {
|
||||||
|
val list = _cards.value ?: emptyList()
|
||||||
|
val card = Card(list.size, tagId, user)
|
||||||
|
_cards.value = list + card
|
||||||
|
Log.d("Palto", "CardViewModel: a card has been added into the list.")
|
||||||
|
return card
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCard(tagId: String): Card? {
|
||||||
|
val card = _cards.value?.find() {
|
||||||
|
it.tagId == tagId
|
||||||
|
}
|
||||||
|
return card
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
package com.example.palto.ui
|
|
||||||
|
|
||||||
import android.nfc.Tag
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import com.example.palto.domain.User
|
|
||||||
|
|
||||||
class NfcViewModel: ViewModel() {
|
|
||||||
val tag = MutableLiveData<Tag>()
|
|
||||||
|
|
||||||
private var _user : MutableLiveData<User> = MutableLiveData<User>()
|
|
||||||
val user : LiveData<User> = _user
|
|
||||||
}
|
|
|
@ -4,75 +4,28 @@ import android.util.Log
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import com.example.palto.domain.Card
|
||||||
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.domain.User
|
||||||
import com.example.palto.ui.login.LoginResult
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class UserViewModel(
|
/**
|
||||||
private val tokenRepository: TokenRepository,
|
* UserViewModel maintain a list of users application wide.
|
||||||
private val userRepository: UserRepository
|
* May be converted into a repository.
|
||||||
): ViewModel() {
|
*/
|
||||||
|
class UserViewModel: ViewModel() {
|
||||||
|
|
||||||
private var _result = MutableLiveData<LoginResult>()
|
private var _users = MutableLiveData<List<User>>()
|
||||||
val result = _result as LiveData<LoginResult>
|
val users : LiveData<List<User>> = _users
|
||||||
|
|
||||||
// User is initially set to null to be disconnected.
|
fun createUser(username: String): User {
|
||||||
private var _user = MutableLiveData<User?>(null)
|
val list = _users.value ?: emptyList()
|
||||||
val user = _user as LiveData<User?>
|
val user = User(
|
||||||
|
id = list.size,
|
||||||
fun login(
|
username = username,
|
||||||
hostname: String,
|
firstName = "",
|
||||||
username: String,
|
lastName = "",
|
||||||
password: String) {
|
email = "")
|
||||||
|
_users.value = list + user
|
||||||
// Coroutine runs in background.
|
Log.d("Palto", "UserViewModel: a user has been added into the list.")
|
||||||
viewModelScope.launch {
|
return user
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,22 +8,16 @@ import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.example.palto.databinding.FragmentLoginBinding
|
import com.example.palto.databinding.FragmentLoginBinding
|
||||||
import com.example.palto.ui.UserViewModel
|
|
||||||
|
|
||||||
class LoginFragment : Fragment() {
|
class LoginFragment : Fragment() {
|
||||||
|
|
||||||
// loginViewModel is used to update the login screen dynamically.
|
|
||||||
private val loginViewModel: LoginViewModel by viewModels()
|
|
||||||
|
|
||||||
// userViewModel is where the user is logged in, at the activity level.
|
// userViewModel is where the user is logged in, at the activity level.
|
||||||
private val userViewModel: UserViewModel by activityViewModels() { UserViewModel.Factory }
|
private val loginViewModel: LoginViewModel by activityViewModels() { LoginViewModel.Factory }
|
||||||
|
|
||||||
private lateinit var binding: FragmentLoginBinding
|
private lateinit var binding: FragmentLoginBinding
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
@ -36,7 +30,7 @@ class LoginFragment : Fragment() {
|
||||||
// Bind the login button.
|
// Bind the login button.
|
||||||
binding.login.setOnClickListener {
|
binding.login.setOnClickListener {
|
||||||
binding.loading.visibility = View.VISIBLE
|
binding.loading.visibility = View.VISIBLE
|
||||||
userViewModel.login(
|
loginViewModel.login(
|
||||||
binding.hostname.text.toString(),
|
binding.hostname.text.toString(),
|
||||||
binding.username.text.toString(),
|
binding.username.text.toString(),
|
||||||
binding.password.text.toString()
|
binding.password.text.toString()
|
||||||
|
@ -45,16 +39,16 @@ class LoginFragment : Fragment() {
|
||||||
|
|
||||||
// Bind anonymous login clickable text.
|
// Bind anonymous login clickable text.
|
||||||
binding.loginAnonymous.setOnClickListener {
|
binding.loginAnonymous.setOnClickListener {
|
||||||
userViewModel.loginAnonymous()
|
loginViewModel.loginAnonymous()
|
||||||
}
|
}
|
||||||
|
|
||||||
// On result of logging.
|
// On result of logging.
|
||||||
userViewModel.result.observe(viewLifecycleOwner) {
|
loginViewModel.result.observe(viewLifecycleOwner) {
|
||||||
binding.loading.visibility = View.GONE
|
binding.loading.visibility = View.GONE
|
||||||
if (it.success) {
|
if (it.success) {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
} else if (it.error != null) {
|
} else if (it.error != null) {
|
||||||
binding.loginError.text = "Exception : " + it.exception.toString()
|
binding.loginError.text = "Exception : ${it.exception.toString()}"
|
||||||
Toast.makeText(activity, it.error, Toast.LENGTH_LONG).show()
|
Toast.makeText(activity, it.error, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,106 @@
|
||||||
package com.example.palto.ui.login
|
package com.example.palto.ui.login
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
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 kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View Model to check dynamically the values of the form.
|
* LoginViewModel to access information about the logged in user and login form.
|
||||||
*/
|
*/
|
||||||
class LoginViewModel() : ViewModel() {
|
class LoginViewModel(
|
||||||
|
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?>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private val _loginFormState = MutableLiveData<LoginFormState>()
|
private val _loginFormState = MutableLiveData<LoginFormState>()
|
||||||
val loginFormState: LiveData<LoginFormState> = _loginFormState
|
val loginFormState: LiveData<LoginFormState> = _loginFormState
|
||||||
|
*/
|
||||||
|
|
||||||
fun loginDataChanged(
|
fun login(
|
||||||
hostname: String,
|
hostname: String,
|
||||||
username: String,
|
username: String,
|
||||||
password: String) {
|
password: String) {
|
||||||
if (!isHostNameValid(hostname)) {
|
|
||||||
_loginForm.value = LoginFormState(hostnameError = R.string.invalid_hostname)
|
// Coroutine runs in background.
|
||||||
} else if (!isUserNameValid(username)) {
|
viewModelScope.launch {
|
||||||
_loginForm.value = LoginFormState(usernameError = R.string.invalid_username)
|
try {
|
||||||
} else if (!isPasswordValid(password)) {
|
tokenRepository.authenticate(hostname, username, password)
|
||||||
_loginForm.value = LoginFormState(passwordError = R.string.invalid_password)
|
_user.value = User(-1, username, "", "", "")
|
||||||
} else {
|
_result.value = LoginResult(success = true)
|
||||||
_loginForm.value = LoginFormState(isDataValid = 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(-2, "anonymous", "", "", "")
|
||||||
|
_result.value = LoginResult(success = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logout() {
|
||||||
|
_user.value = null
|
||||||
|
_result.value = LoginResult(success = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
_loginForm.value = LoginFormState(isDataValid = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
private fun isHostNameValid(hostname: String): Boolean {
|
private fun isHostNameValid(hostname: String): Boolean {
|
||||||
return hostname.isNotBlank()
|
return hostname.isNotBlank()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isUserNameValid(username: String): Boolean {
|
private fun isUserNameValid(username: String): Boolean {
|
||||||
return username.isNotBlank()
|
return username.isNotBlank()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isPasswordValid(password: String): Boolean {
|
private fun isPasswordValid(password: String): Boolean {
|
||||||
return password.length > 5
|
return password.length > 5
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return LoginViewModel(
|
||||||
|
tokenRepository = TokenRepository(),
|
||||||
|
userRepository = UserRepository()
|
||||||
|
) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import androidx.navigation.navGraphViewModels
|
||||||
import com.example.palto.R
|
import com.example.palto.R
|
||||||
import com.example.palto.databinding.FragmentMenuListBinding
|
import com.example.palto.databinding.FragmentMenuListBinding
|
||||||
import com.example.palto.domain.Session
|
import com.example.palto.domain.Session
|
||||||
import com.example.palto.ui.UserViewModel
|
import com.example.palto.ui.login.LoginViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment representing a list of Sessions.
|
* A fragment representing a list of Sessions.
|
||||||
|
@ -22,8 +22,8 @@ class MenuFragment : Fragment() {
|
||||||
private val menuViewModel: MenuViewModel by
|
private val menuViewModel: MenuViewModel by
|
||||||
navGraphViewModels(R.id.nav_graph) { MenuViewModel.Factory }
|
navGraphViewModels(R.id.nav_graph) { MenuViewModel.Factory }
|
||||||
|
|
||||||
private val userViewModel: UserViewModel by
|
private val loginViewModel: LoginViewModel by
|
||||||
activityViewModels() { UserViewModel.Factory }
|
activityViewModels() { LoginViewModel.Factory }
|
||||||
|
|
||||||
// This property is only valid between onCreateView and onDestroyView
|
// This property is only valid between onCreateView and onDestroyView
|
||||||
private lateinit var binding: FragmentMenuListBinding
|
private lateinit var binding: FragmentMenuListBinding
|
||||||
|
@ -38,7 +38,7 @@ class MenuFragment : Fragment() {
|
||||||
val navController = findNavController()
|
val navController = findNavController()
|
||||||
|
|
||||||
// Connect the user.
|
// Connect the user.
|
||||||
userViewModel.user.observe(viewLifecycleOwner) {
|
loginViewModel.user.observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
// Get sessions of the user from remote.
|
// Get sessions of the user from remote.
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,14 +5,16 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.example.palto.data.repository.TokenRepository
|
import com.example.palto.domain.Attendance
|
||||||
import com.example.palto.data.repository.UserRepository
|
|
||||||
import com.example.palto.domain.Session
|
import com.example.palto.domain.Session
|
||||||
import com.example.palto.ui.UserViewModel
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel for accessing all the sessions created.
|
||||||
|
*/
|
||||||
class MenuViewModel() : ViewModel() {
|
class MenuViewModel() : ViewModel() {
|
||||||
|
|
||||||
private var _sessions = MutableLiveData<List<Session>>(emptyList())
|
// A list of sessions.
|
||||||
|
private var _sessions = MutableLiveData<List<Session>>()
|
||||||
val sessions: LiveData<List<Session>> = _sessions
|
val sessions: LiveData<List<Session>> = _sessions
|
||||||
|
|
||||||
fun createSession(name: String) {
|
fun createSession(name: String) {
|
||||||
|
@ -26,6 +28,14 @@ class MenuViewModel() : ViewModel() {
|
||||||
Log.d("Palto", "MenuViewModel: A session has been added into the list.")
|
Log.d("Palto", "MenuViewModel: A session has been added into the list.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSession(id: Int): Session? {
|
||||||
|
return _sessions.value?.find { it.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAttendanceListSession(id: Int, list: List<Attendance>) {
|
||||||
|
getSession(id)?.let { it.attendances = list }
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.example.palto.ui.menu.new_session
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.view.menu.MenuView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.navGraphViewModels
|
||||||
|
import com.example.palto.R
|
||||||
|
import com.example.palto.databinding.FragmentNewSessionBinding
|
||||||
|
import com.example.palto.ui.menu.MenuViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class NewSessionFragment : Fragment() {
|
||||||
|
|
||||||
|
private val newSessionViewModel: NewSessionViewModel by viewModels()
|
||||||
|
|
||||||
|
private val menuViewModel: MenuViewModel by
|
||||||
|
navGraphViewModels(R.id.nav_graph) { MenuViewModel.Factory }
|
||||||
|
|
||||||
|
// This property is only valid between onCreateView and onDestroyView
|
||||||
|
private lateinit var binding: FragmentNewSessionBinding
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
binding = FragmentNewSessionBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
// Bind the create button
|
||||||
|
binding.newSessionCreate.setOnClickListener {
|
||||||
|
menuViewModel.createSession(
|
||||||
|
name = binding.newSessionName.text.toString()
|
||||||
|
)
|
||||||
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.example.palto.ui.menu.new_session
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel of the session creation form. Used for verification.
|
||||||
|
*/
|
||||||
|
class NewSessionViewModel() : ViewModel()
|
|
@ -7,47 +7,46 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.example.palto.databinding.FragmentSessionItemBinding
|
import com.example.palto.databinding.FragmentSessionItemBinding
|
||||||
|
import com.example.palto.domain.Attendance
|
||||||
import com.example.palto.domain.Card
|
import com.example.palto.domain.Card
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [ListAdapter] that can display [Card] items.
|
* A [ListAdapter] that can display [Attendance] items.
|
||||||
*/
|
*/
|
||||||
class SessionAdapter :
|
class SessionAdapter : ListAdapter<Attendance, SessionAdapter.ViewHolder>(AttendanceDiffCallback) {
|
||||||
ListAdapter<Card, SessionAdapter.ViewHolder>(CardDiffCallback) {
|
|
||||||
|
|
||||||
|
inner class ViewHolder(binding: FragmentSessionItemBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
private val attendanceUsernameText: TextView = binding.attendanceUsername
|
||||||
|
private val attendanceDate: TextView = binding.attendanceDate
|
||||||
|
|
||||||
|
fun bind(attendance: Attendance) {
|
||||||
|
attendanceUsernameText.text = attendance.student.username
|
||||||
|
attendanceDate.text = attendance.date.format(DateTimeFormatter.ofPattern("HH:mm:ss"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(
|
val binding = FragmentSessionItemBinding
|
||||||
FragmentSessionItemBinding.inflate(
|
.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
LayoutInflater.from(parent.context),
|
return ViewHolder(binding)
|
||||||
parent,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
holder.cardId.text = item.uid.toHexString()
|
holder.bind(item)
|
||||||
}
|
|
||||||
|
|
||||||
inner class ViewHolder(
|
|
||||||
binding: FragmentSessionItemBinding
|
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
|
||||||
|
|
||||||
val cardId: TextView = binding.cardId
|
|
||||||
override fun toString(): String {
|
|
||||||
return super.toString() + " '" + cardId.text + "'"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object CardDiffCallback : DiffUtil.ItemCallback<Card>() {
|
object AttendanceDiffCallback : DiffUtil.ItemCallback<Attendance>() {
|
||||||
override fun areItemsTheSame(oldItem: Card, newItem: Card): Boolean {
|
override fun areItemsTheSame(oldItem: Attendance, newItem: Attendance): Boolean {
|
||||||
return oldItem == newItem
|
return oldItem == newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: Card, newItem: Card): Boolean {
|
override fun areContentsTheSame(oldItem: Attendance, newItem: Attendance): Boolean {
|
||||||
return oldItem.id == newItem.id
|
return oldItem.id == newItem.id
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,12 +5,18 @@ import android.util.Log
|
||||||
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.widget.Toast
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.navGraphViewModels
|
import androidx.navigation.navGraphViewModels
|
||||||
import com.example.palto.ui.NfcViewModel
|
import com.example.palto.NfcViewModel
|
||||||
import com.example.palto.R
|
import com.example.palto.R
|
||||||
import com.example.palto.databinding.FragmentSessionListBinding
|
import com.example.palto.databinding.FragmentSessionListBinding
|
||||||
|
import com.example.palto.ui.CardViewModel
|
||||||
|
import com.example.palto.ui.menu.MenuViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment representing a list of attendances.
|
* A fragment representing a list of attendances.
|
||||||
|
@ -20,12 +26,16 @@ class SessionFragment : Fragment() {
|
||||||
private val sessionViewModel: SessionViewModel by
|
private val sessionViewModel: SessionViewModel by
|
||||||
navGraphViewModels(R.id.nav_graph) { SessionViewModel.Factory }
|
navGraphViewModels(R.id.nav_graph) { SessionViewModel.Factory }
|
||||||
|
|
||||||
private val nfcViewModel: NfcViewModel by
|
private val menuViewModel: MenuViewModel by
|
||||||
activityViewModels()
|
navGraphViewModels(R.id.nav_graph) { MenuViewModel.Factory }
|
||||||
|
|
||||||
|
private val cardViewModel: CardViewModel by
|
||||||
|
navGraphViewModels(R.id.nav_graph)
|
||||||
|
|
||||||
|
private val nfcViewModel: NfcViewModel by activityViewModels()
|
||||||
|
|
||||||
private var _binding: FragmentSessionListBinding? = null
|
|
||||||
// This property is only valid between onCreateView and onDestroyView
|
// This property is only valid between onCreateView and onDestroyView
|
||||||
private val binding get() = _binding!!
|
private lateinit var binding: FragmentSessionListBinding
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Have the fragment instantiate the user interface.
|
* Have the fragment instantiate the user interface.
|
||||||
|
@ -35,25 +45,62 @@ class SessionFragment : Fragment() {
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
_binding = FragmentSessionListBinding.inflate(inflater, container, false)
|
binding = FragmentSessionListBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
// If the bundle value id of key "session" is not empty,
|
||||||
|
// set the attendance list to that of the selected session.
|
||||||
|
arguments?.getInt("session")?.let { id ->
|
||||||
|
val session = menuViewModel.getSession(id)
|
||||||
|
if (session != null) {
|
||||||
|
Log.d("Palto", "SessionFragment: Session id ${session.id} has been found.")
|
||||||
|
sessionViewModel.session = session
|
||||||
|
sessionViewModel.setAttendanceList(session.attendances)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the adapter of the view for managing automatically the list of items on the screen.
|
// Set the adapter of the view for managing automatically the list of items on the screen.
|
||||||
val adapter = SessionAdapter()
|
val adapter = SessionAdapter()
|
||||||
binding.sessionList.adapter = adapter
|
binding.sessionList.adapter = adapter
|
||||||
sessionViewModel.cards.observe(viewLifecycleOwner) {
|
sessionViewModel.attendances.observe(viewLifecycleOwner) {
|
||||||
adapter.submitList(it)
|
adapter.submitList(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the listener for a new NFC tag.
|
// Set the listener for a new NFC tag.
|
||||||
nfcViewModel.tag.observe(viewLifecycleOwner) {
|
nfcViewModel.tagId.observe(viewLifecycleOwner) {
|
||||||
Log.d("NFC", "The tag observers has been notified.")
|
|
||||||
sessionViewModel.insertCard(it)
|
// If the NFC tag has not been handled.
|
||||||
|
it.getContentIfNotHandled()?.let { cardId ->
|
||||||
|
val card = cardViewModel.getCard(cardId)
|
||||||
|
// If a card with this tag exists, add this card.
|
||||||
|
if (card != null) {
|
||||||
|
sessionViewModel.addAttendance(card.user)
|
||||||
|
// Else go to the NewStudentFragment to create a new card and student.
|
||||||
|
} else {
|
||||||
|
val bundle = bundleOf("tagId" to cardId)
|
||||||
|
findNavController()
|
||||||
|
.navigate(R.id.action_sessionFragment_to_newStudentFragment, bundle)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.createCard.setOnClickListener {
|
// Manual add student button
|
||||||
//sessionViewModel.
|
binding.addStudent.setOnClickListener {
|
||||||
|
findNavController().navigate(R.id.action_sessionFragment_to_newStudentFragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the result of adding an attendance on the view.
|
||||||
|
sessionViewModel.result.observe(viewLifecycleOwner) {
|
||||||
|
// If the result has not been already shown
|
||||||
|
it.getContentIfNotHandled()?.let { result ->
|
||||||
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
getString(result.message, result.username),
|
||||||
|
Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.example.palto.ui.session
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication result : success is true if connected or error message with exception.
|
||||||
|
*/
|
||||||
|
data class SessionResult(
|
||||||
|
val success: Boolean,
|
||||||
|
val message: Int, // Id of the string resource to display to the user
|
||||||
|
val username: String
|
||||||
|
)
|
|
@ -1,37 +1,74 @@
|
||||||
package com.example.palto.ui.session
|
package com.example.palto.ui.session
|
||||||
|
|
||||||
import android.nfc.Tag
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.example.palto.data.repository.AttendanceRepository
|
import com.example.palto.R
|
||||||
import com.example.palto.domain.Card
|
import com.example.palto.domain.Attendance
|
||||||
|
import com.example.palto.domain.Event
|
||||||
|
import com.example.palto.domain.Session
|
||||||
|
import com.example.palto.domain.User
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel of a session which has a list of attendances.
|
* ViewModel of a session which has a list of attendances.
|
||||||
*/
|
*/
|
||||||
class SessionViewModel(
|
class SessionViewModel() : ViewModel() {
|
||||||
private val attendanceRepository: AttendanceRepository
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
private val _cards = MutableLiveData<List<Card>>(emptyList())
|
private var _result = MutableLiveData<Event<SessionResult>>()
|
||||||
val cards = _cards as LiveData<List<Card>>
|
val result: LiveData<Event<SessionResult>> = _result
|
||||||
|
|
||||||
fun insertCard(tag: Tag) {
|
private var _attendances = MutableLiveData<List<Attendance>>()
|
||||||
val card = Card(
|
val attendances: LiveData<List<Attendance>> = _attendances
|
||||||
"0",
|
|
||||||
tag.id,
|
// The opened session which have been selected in the menu.
|
||||||
"tmp",
|
var session: Session? = null
|
||||||
"tmp"
|
|
||||||
)
|
/**
|
||||||
_cards.value = (_cards.value ?: emptyList()) + card
|
* Add the [student] in the attendance list [attendances].
|
||||||
Log.d("PALTO", "SessionViewModel: a card has been added into the list.")
|
* Return true if it has been added, else return false.
|
||||||
|
*/
|
||||||
|
fun addAttendance(student: User) {
|
||||||
|
val list = _attendances.value ?: emptyList()
|
||||||
|
|
||||||
|
// If the list already contains the user, return false
|
||||||
|
if (list.any { it.student == student }) {
|
||||||
|
Log.d("Palto", "SessionViewModel: User already in the list.")
|
||||||
|
_result.value = Event(SessionResult(
|
||||||
|
false,
|
||||||
|
R.string.session_user_already_added,
|
||||||
|
student.username
|
||||||
|
))
|
||||||
|
|
||||||
|
// Else create a new attendance and add it into the list.
|
||||||
|
} else {
|
||||||
|
val attendance = Attendance(
|
||||||
|
id = list.size,
|
||||||
|
student = student,
|
||||||
|
date = LocalTime.now()
|
||||||
|
)
|
||||||
|
// Add the attendance in the attendance list, and trigger the observers.
|
||||||
|
_attendances.value = list + attendance
|
||||||
|
// Saved the list in the session.
|
||||||
|
session?.attendances = list + attendance
|
||||||
|
|
||||||
|
_result.value = Event(SessionResult(
|
||||||
|
true,
|
||||||
|
R.string.session_user_added,
|
||||||
|
student.username
|
||||||
|
))
|
||||||
|
Log.d("Palto", "SessionViewModel: An attendance has been added into the list.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAttendanceList(list: List<Attendance>) {
|
||||||
|
_attendances.value = list
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel Factory.
|
* ViewModel Factory.
|
||||||
*/
|
*/
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -40,9 +77,7 @@ class SessionViewModel(
|
||||||
override fun <T : ViewModel> create(
|
override fun <T : ViewModel> create(
|
||||||
modelClass: Class<T>
|
modelClass: Class<T>
|
||||||
): T {
|
): T {
|
||||||
return SessionViewModel(
|
return SessionViewModel() as T
|
||||||
AttendanceRepository()
|
|
||||||
) as T
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.example.palto.ui.session.new_student
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.navGraphViewModels
|
||||||
|
import com.example.palto.NfcViewModel
|
||||||
|
import com.example.palto.R
|
||||||
|
import com.example.palto.databinding.FragmentNewSessionBinding
|
||||||
|
import com.example.palto.databinding.FragmentNewStudentBinding
|
||||||
|
import com.example.palto.ui.CardViewModel
|
||||||
|
import com.example.palto.ui.UserViewModel
|
||||||
|
import com.example.palto.ui.menu.MenuViewModel
|
||||||
|
import com.example.palto.ui.session.SessionViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class NewStudentFragment : Fragment() {
|
||||||
|
|
||||||
|
private val newStudentViewModel: NewStudentViewModel by viewModels()
|
||||||
|
|
||||||
|
private val sessionViewModel: SessionViewModel by navGraphViewModels(R.id.nav_graph)
|
||||||
|
|
||||||
|
private val userViewModel: UserViewModel by navGraphViewModels(R.id.nav_graph)
|
||||||
|
|
||||||
|
private val cardViewModel: CardViewModel by navGraphViewModels(R.id.nav_graph)
|
||||||
|
|
||||||
|
// This property is only valid between onCreateView and onDestroyView
|
||||||
|
private lateinit var binding: FragmentNewStudentBinding
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
binding = FragmentNewStudentBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
// If the bundle value tagId of key "tagId" exists,
|
||||||
|
// Set it on the screen.
|
||||||
|
val tagId = arguments?.getString("tagId")
|
||||||
|
if (tagId != null) {
|
||||||
|
binding.newStudentCardId.text = tagId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind the create button.
|
||||||
|
binding.newStudentCreate.setOnClickListener {
|
||||||
|
val user = userViewModel.createUser(binding.newStudentName.text.toString())
|
||||||
|
|
||||||
|
// If a tag has been provided, create the card.
|
||||||
|
// The user would not need to create his account afterward.
|
||||||
|
if (tagId != null) {
|
||||||
|
cardViewModel.createCard(user, tagId)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionViewModel.addAttendance(user)
|
||||||
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.example.palto.ui.session.new_student
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel of the session creation form. Used for verifications.
|
||||||
|
*/
|
||||||
|
class NewStudentViewModel() : ViewModel()
|
44
app/src/main/res/layout/fragment_new_student.xml
Normal file
44
app/src/main/res/layout/fragment_new_student.xml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/new_student_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ems="10"
|
||||||
|
android:hint="@string/new_student_name"
|
||||||
|
android:inputType="text"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/new_student_card_id"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/new_student_card_id"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/new_student_card_id_text"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/new_student_create"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/new_student_name" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/new_student_create"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/new_student_button_create"
|
||||||
|
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/new_student_card_id" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -5,7 +5,14 @@
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/card_̤id"
|
android:id="@+id/attendance_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/text_margin"
|
||||||
|
android:textAppearance="?attr/textAppearanceListItem" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/attendance_date"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="@dimen/text_margin"
|
android:layout_margin="@dimen/text_margin"
|
||||||
|
|
|
@ -17,10 +17,11 @@
|
||||||
tools:listitem="@layout/fragment_session_item" />
|
tools:listitem="@layout/fragment_session_item" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/create_card"
|
android:id="@+id/add_student"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="16dp"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:contentDescription="@string/create_card"
|
android:contentDescription="@string/create_card"
|
||||||
app:srcCompat="@android:drawable/ic_input_add" />
|
app:srcCompat="@android:drawable/ic_input_add" />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item
|
<!--<item
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:title="@string/menu_item_logout" />
|
android:title="@string/menu_item_logout" />-->
|
||||||
</menu>
|
</menu>
|
|
@ -34,7 +34,16 @@
|
||||||
android:id="@+id/sessionFragment"
|
android:id="@+id/sessionFragment"
|
||||||
android:name="com.example.palto.ui.session.SessionFragment"
|
android:name="com.example.palto.ui.session.SessionFragment"
|
||||||
android:label="Fiche de présence"
|
android:label="Fiche de présence"
|
||||||
tools:layout="@layout/fragment_session_list" />
|
tools:layout="@layout/fragment_session_list" >
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_sessionFragment_to_newStudentFragment"
|
||||||
|
app:destination="@id/newStudentFragment" />
|
||||||
|
</fragment>
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/newStudentFragment"
|
||||||
|
android:name="com.example.palto.ui.session.new_student.NewStudentFragment"
|
||||||
|
android:label="Nouvel étudiant"
|
||||||
|
tools:layout="@layout/fragment_new_student" />
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
|
@ -19,4 +19,9 @@
|
||||||
<string name="new_session_name_hint">Nom de la fiche</string>
|
<string name="new_session_name_hint">Nom de la fiche</string>
|
||||||
<string name="new_session_button_create">Créer</string>
|
<string name="new_session_button_create">Créer</string>
|
||||||
<string name="menu_item_logout">Déconnexion</string>
|
<string name="menu_item_logout">Déconnexion</string>
|
||||||
|
<string name="new_student_button_create">Ajouter</string>
|
||||||
|
<string name="new_student_name">Nom de l’étudiant</string>
|
||||||
|
<string name="new_student_card_id_text">ID Carte : Pas de carte</string>
|
||||||
|
<string name="session_user_already_added">L’étudiant %1$s existe déjà dans la liste !\n</string>
|
||||||
|
<string name="session_user_added">L’étudiant %1$s a été ajouté</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue