Écran pour ajouter un étudiant à une liste.

Sauvegarde d’une fiche de présence dans une session.
This commit is contained in:
biloute02 2024-01-13 17:09:45 +01:00
parent 035030ca0f
commit acb73364a1
28 changed files with 562 additions and 208 deletions

View 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()))
}
}

View file

@ -11,8 +11,7 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import com.example.palto.databinding.ActivityPaltoBinding
import com.example.palto.ui.NfcViewModel
import com.example.palto.ui.UserViewModel
import com.example.palto.ui.CardViewModel
class PaltoActivity : AppCompatActivity() {
@ -21,8 +20,6 @@ class PaltoActivity : AppCompatActivity() {
private val nfcViewModel: NfcViewModel by viewModels()
private val userViewModel: UserViewModel by viewModels() { UserViewModel.Factory }
private lateinit var binding: ActivityPaltoBinding
override fun onCreate(savedInstanceState: Bundle?) {
@ -81,7 +78,7 @@ class PaltoActivity : AppCompatActivity() {
// Begin to read NFC Cards.
nfcAdapter?.enableReaderMode(
this,
nfcViewModel.tag::postValue,
nfcViewModel::setTag,
NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
null
)

View file

@ -1,10 +1,12 @@
package com.example.palto.domain
import java.io.Serializable
import java.time.LocalTime
/**
* Data class that captures tokens for logged in users retrieved from LoginRepository
*/
data class Attendance(
val date: String,
val access: String
val id: Int,
val student: User,
val date: LocalTime
) : Serializable

View file

@ -1,22 +1,7 @@
package com.example.palto.domain
data class Card(
val id: String,
val uid: ByteArray,
val department: String,
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()
}
}
val id: Int,
val tagId: String,
val user: User,
)

View 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
}

View file

@ -7,5 +7,6 @@ import java.io.Serializable
data class Session(
val id: Int,
val name: String,
var attendances: List<User>
var attendances: List<Attendance>
// When the list is updated, it is replaced by a new one.
)

View file

@ -4,10 +4,9 @@ package com.example.palto.domain
* Data class that captures user information for logged in users retrieved from LoginRepository
*/
data class User(
//Not in the domain layer
//val id: String,
val id: Int,
val username: String,
val first_name: String,
val last_name: String,
val firstName: String,
val lastName: String,
val email: String
)

View 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
}
}

View file

@ -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
}

View file

@ -4,75 +4,28 @@ 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.Card
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() {
/**
* UserViewModel maintain a list of users application wide.
* May be converted into a repository.
*/
class UserViewModel: ViewModel() {
private var _result = MutableLiveData<LoginResult>()
val result = _result as LiveData<LoginResult>
private var _users = MutableLiveData<List<User>>()
val users : LiveData<List<User>> = _users
// 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
}
}
fun createUser(username: String): User {
val list = _users.value ?: emptyList()
val user = User(
id = list.size,
username = username,
firstName = "",
lastName = "",
email = "")
_users.value = list + user
Log.d("Palto", "UserViewModel: a user has been added into the list.")
return user
}
}

View file

@ -8,22 +8,16 @@ import android.view.ViewGroup
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 com.example.palto.databinding.FragmentLoginBinding
import com.example.palto.ui.UserViewModel
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.
private val userViewModel: UserViewModel by activityViewModels() { UserViewModel.Factory }
private val loginViewModel: LoginViewModel by activityViewModels() { LoginViewModel.Factory }
private lateinit var binding: FragmentLoginBinding
@SuppressLint("SetTextI18n")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -36,7 +30,7 @@ class LoginFragment : Fragment() {
// Bind the login button.
binding.login.setOnClickListener {
binding.loading.visibility = View.VISIBLE
userViewModel.login(
loginViewModel.login(
binding.hostname.text.toString(),
binding.username.text.toString(),
binding.password.text.toString()
@ -45,16 +39,16 @@ class LoginFragment : Fragment() {
// Bind anonymous login clickable text.
binding.loginAnonymous.setOnClickListener {
userViewModel.loginAnonymous()
loginViewModel.loginAnonymous()
}
// On result of logging.
userViewModel.result.observe(viewLifecycleOwner) {
loginViewModel.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()
binding.loginError.text = "Exception : ${it.exception.toString()}"
Toast.makeText(activity, it.error, Toast.LENGTH_LONG).show()
}
}

View file

@ -1,41 +1,106 @@
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.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>()
val loginFormState: LiveData<LoginFormState> = _loginFormState
*/
fun loginDataChanged(
fun login(
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)
// Coroutine runs in background.
viewModelScope.launch {
try {
tokenRepository.authenticate(hostname, username, password)
_user.value = User(-1, 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(-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 {
return hostname.isNotBlank()
return hostname.isNotBlank()
}
private fun isUserNameValid(username: String): Boolean {
return username.isNotBlank()
return username.isNotBlank()
}
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
}
}
}
}

View file

@ -12,7 +12,7 @@ import androidx.navigation.navGraphViewModels
import com.example.palto.R
import com.example.palto.databinding.FragmentMenuListBinding
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.
@ -22,8 +22,8 @@ class MenuFragment : Fragment() {
private val menuViewModel: MenuViewModel by
navGraphViewModels(R.id.nav_graph) { MenuViewModel.Factory }
private val userViewModel: UserViewModel by
activityViewModels() { UserViewModel.Factory }
private val loginViewModel: LoginViewModel by
activityViewModels() { LoginViewModel.Factory }
// This property is only valid between onCreateView and onDestroyView
private lateinit var binding: FragmentMenuListBinding
@ -38,7 +38,7 @@ class MenuFragment : Fragment() {
val navController = findNavController()
// Connect the user.
userViewModel.user.observe(viewLifecycleOwner) {
loginViewModel.user.observe(viewLifecycleOwner) {
if (it != null) {
// Get sessions of the user from remote.
} else {

View file

@ -5,14 +5,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.palto.data.repository.TokenRepository
import com.example.palto.data.repository.UserRepository
import com.example.palto.domain.Attendance
import com.example.palto.domain.Session
import com.example.palto.ui.UserViewModel
/**
* ViewModel for accessing all the sessions created.
*/
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
fun createSession(name: String) {
@ -26,6 +28,14 @@ class MenuViewModel() : ViewModel() {
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 {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {

View file

@ -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
}
}

View file

@ -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()

View file

@ -7,47 +7,46 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.palto.databinding.FragmentSessionItemBinding
import com.example.palto.domain.Attendance
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 :
ListAdapter<Card, SessionAdapter.ViewHolder>(CardDiffCallback) {
class SessionAdapter : ListAdapter<Attendance, SessionAdapter.ViewHolder>(AttendanceDiffCallback) {
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 {
return ViewHolder(
FragmentSessionItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
val binding = FragmentSessionItemBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
@OptIn(ExperimentalStdlibApi::class)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.cardId.text = item.uid.toHexString()
}
inner class ViewHolder(
binding: FragmentSessionItemBinding
) : RecyclerView.ViewHolder(binding.root) {
val cardId: TextView = binding.cardId
override fun toString(): String {
return super.toString() + " '" + cardId.text + "'"
}
holder.bind(item)
}
}
object CardDiffCallback : DiffUtil.ItemCallback<Card>() {
override fun areItemsTheSame(oldItem: Card, newItem: Card): Boolean {
object AttendanceDiffCallback : DiffUtil.ItemCallback<Attendance>() {
override fun areItemsTheSame(oldItem: Attendance, newItem: Attendance): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Card, newItem: Card): Boolean {
override fun areContentsTheSame(oldItem: Attendance, newItem: Attendance): Boolean {
return oldItem.id == newItem.id
}
}

View file

@ -5,12 +5,18 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import com.example.palto.ui.NfcViewModel
import com.example.palto.NfcViewModel
import com.example.palto.R
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.
@ -20,12 +26,16 @@ class SessionFragment : Fragment() {
private val sessionViewModel: SessionViewModel by
navGraphViewModels(R.id.nav_graph) { SessionViewModel.Factory }
private val nfcViewModel: NfcViewModel by
activityViewModels()
private val menuViewModel: MenuViewModel by
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
private val binding get() = _binding!!
private lateinit var binding: FragmentSessionListBinding
/**
* Have the fragment instantiate the user interface.
@ -35,25 +45,62 @@ class SessionFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): 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.
val adapter = SessionAdapter()
binding.sessionList.adapter = adapter
sessionViewModel.cards.observe(viewLifecycleOwner) {
sessionViewModel.attendances.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
// Set the listener for a new NFC tag.
nfcViewModel.tag.observe(viewLifecycleOwner) {
Log.d("NFC", "The tag observers has been notified.")
sessionViewModel.insertCard(it)
nfcViewModel.tagId.observe(viewLifecycleOwner) {
// 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 {
//sessionViewModel.
// Manual add student button
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
}
}

View file

@ -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
)

View file

@ -1,37 +1,74 @@
package com.example.palto.ui.session
import android.nfc.Tag
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.palto.data.repository.AttendanceRepository
import com.example.palto.domain.Card
import com.example.palto.R
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.
*/
class SessionViewModel(
private val attendanceRepository: AttendanceRepository
) : ViewModel() {
class SessionViewModel() : ViewModel() {
private val _cards = MutableLiveData<List<Card>>(emptyList())
val cards = _cards as LiveData<List<Card>>
private var _result = MutableLiveData<Event<SessionResult>>()
val result: LiveData<Event<SessionResult>> = _result
fun insertCard(tag: Tag) {
val card = Card(
"0",
tag.id,
"tmp",
"tmp"
)
_cards.value = (_cards.value ?: emptyList()) + card
Log.d("PALTO", "SessionViewModel: a card has been added into the list.")
private var _attendances = MutableLiveData<List<Attendance>>()
val attendances: LiveData<List<Attendance>> = _attendances
// The opened session which have been selected in the menu.
var session: Session? = null
/**
* Add the [student] in the attendance list [attendances].
* 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 {
@ -40,9 +77,7 @@ class SessionViewModel(
override fun <T : ViewModel> create(
modelClass: Class<T>
): T {
return SessionViewModel(
AttendanceRepository()
) as T
return SessionViewModel() as T
}
}
}

View file

@ -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
}
}

View file

@ -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()

View 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>

View file

@ -5,7 +5,14 @@
android:orientation="horizontal">
<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_height="wrap_content"
android:layout_margin="@dimen/text_margin"

View file

@ -17,10 +17,11 @@
tools:listitem="@layout/fragment_session_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/create_card"
android:id="@+id/add_student"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:clickable="true"
android:contentDescription="@string/create_card"
app:srcCompat="@android:drawable/ic_input_add" />

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
<!--<item
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:title="@string/menu_item_logout" />
android:title="@string/menu_item_logout" />-->
</menu>

View file

@ -34,7 +34,16 @@
android:id="@+id/sessionFragment"
android:name="com.example.palto.ui.session.SessionFragment"
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>

View file

@ -19,4 +19,9 @@
<string name="new_session_name_hint">Nom de la fiche</string>
<string name="new_session_button_create">Créer</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>