commit
3b88177aa7
68 changed files with 1283 additions and 837 deletions
23
README.md
23
README.md
|
@ -1 +1,24 @@
|
|||
# M1-Projet
|
||||
|
||||
Cette application est la partie cliente du projet Palto.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
- Se connecter sur un serveur Palto avec ses identifiants, ou bien se connecter en anonyme.
|
||||
- Sur le menu :
|
||||
- Voir la liste des sessions créées.
|
||||
- Créer une nouvelle session en lui donnant un nom.
|
||||
- Ouvrir une session en cliquant sur l’item.
|
||||
- Lorsqu’une session est ouverte :
|
||||
- Voir la list des présences.
|
||||
- Scanner une carte NFC et créer un nouvel étudiant associé à cette carte.
|
||||
- Ajouter manuellement un étudiant à la liste.
|
||||
|
||||
## Prérequis
|
||||
|
||||
- Android 8.1 SDK 27.
|
||||
- Support du NFC
|
||||
|
||||
## Utilisation
|
||||
|
||||
Importer le projet dans Android Studio.
|
||||
|
|
|
@ -29,7 +29,6 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
buildToolsVersion = "33.0.1"
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
@ -38,16 +37,21 @@ android {
|
|||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.10.0")
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
|
||||
implementation("androidx.annotation:annotation:1.7.0")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
|
||||
implementation("androidx.annotation:annotation:1.7.1")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
|
||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
||||
// Retrofit and Moshi for API requests.
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
|
||||
implementation("com.squareup.moshi:moshi-kotlin:1.13.0")
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
@ -15,6 +16,7 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Palto"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".PaltoActivity"
|
||||
|
|
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()))
|
||||
}
|
||||
}
|
|
@ -1,64 +1,96 @@
|
|||
package com.example.palto
|
||||
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
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.CardViewModel
|
||||
|
||||
|
||||
class PaltoActivity : AppCompatActivity() {
|
||||
|
||||
private var nfcAdapter: NfcAdapter? = null
|
||||
|
||||
private val paltoViewModel: PaltoViewModel by viewModels()
|
||||
private val nfcViewModel: NfcViewModel by viewModels()
|
||||
|
||||
private lateinit var binding: ActivityPaltoBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// get the NFC Adapter
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
binding = ActivityPaltoBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// check if NFC is supported
|
||||
//
|
||||
// Toolbar
|
||||
//
|
||||
|
||||
// Set the toolbar as the app bar for the activity.
|
||||
setSupportActionBar(binding.paltoToolbar)
|
||||
|
||||
// Configure the app bar
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.palto_nav_host_fragment) as NavHostFragment
|
||||
val navController = navHostFragment.navController
|
||||
val appBarConfiguration = AppBarConfiguration(
|
||||
setOf(
|
||||
R.id.menuFragment, R.id.loginFragment
|
||||
)
|
||||
)
|
||||
binding.paltoToolbar.setupWithNavController(navController, appBarConfiguration)
|
||||
|
||||
//
|
||||
// NFC Adapter
|
||||
//
|
||||
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
// Check if NFC is supported (already checked in the app manifest).
|
||||
if (nfcAdapter == null) {
|
||||
Log.e("NFC", "NFC is not supported")
|
||||
return
|
||||
}
|
||||
|
||||
// check if NFC is disabled
|
||||
if (nfcAdapter?.isEnabled == false) {
|
||||
Log.w("NFC", "NFC is not enabled")
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_palto)
|
||||
/*
|
||||
val url = URL("https://www.faraphel.fr/palto/api/auth/token/")
|
||||
val connection = url.openConnection()
|
||||
val auth_data = Json.decodeFromString(connection.content)
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the options menu for the Activity.
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
val inflater: MenuInflater = menuInflater
|
||||
inflater.inflate(R.menu.palto_menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Just before the application is displayed.
|
||||
*/
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Begin to read NFC Cards.
|
||||
nfcAdapter?.enableReaderMode(
|
||||
this,
|
||||
paltoViewModel.tagLiveData::postValue,
|
||||
nfcViewModel::setTag,
|
||||
NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Just after the application has been quit.
|
||||
*/
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// disable the NFC discovery
|
||||
// Disable the NFC discovery.
|
||||
nfcAdapter?.disableReaderMode(this)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun processTag(tag: Tag) {
|
||||
Log.d("NFC", "Tag ID : " + tag.id.toHexString())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,18 +0,0 @@
|
|||
package com.example.palto.data
|
||||
|
||||
/**
|
||||
* A generic class that holds a value with its loading status.
|
||||
* @param <T>
|
||||
*/
|
||||
sealed class Result<out T : Any> {
|
||||
|
||||
data class Success<out T : Any>(val data: T) : Result<T>()
|
||||
data class Error(val exception: Exception) : Result<Nothing>()
|
||||
|
||||
override fun toString(): String {
|
||||
return when (this) {
|
||||
is Success<*> -> "Success[data=$data]"
|
||||
is Error -> "Error[exception=$exception]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.example.palto.data.network
|
||||
|
||||
import com.example.palto.data.network.model.UserCredentials
|
||||
import com.example.palto.domain.Tokens
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* A public Api object that exposes the lazy-initialized Retrofit service
|
||||
*/
|
||||
object PaltoApi {
|
||||
|
||||
// Build the Moshi object that Retrofit will be using, making sure to add the Kotlin adapter for
|
||||
// full Kotlin compatibility.
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
fun createService(url: String) {
|
||||
// Use the Retrofit builder to build a retrofit object using a Moshi converter
|
||||
// with our Moshi object.
|
||||
val retrofit = Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.baseUrl(url)
|
||||
.build()
|
||||
retrofitService = retrofit.create(PaltoApiService::class.java)
|
||||
}
|
||||
|
||||
// Retrofit service that Palto will use to do requests.
|
||||
// Make sure to call createService once before using it.
|
||||
lateinit var retrofitService: PaltoApiService
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Functions to query the API.
|
||||
*/
|
||||
interface PaltoApiService {
|
||||
|
||||
@POST("api/auth/jwt/token/")
|
||||
suspend fun getTokens(@Body userCredentials: UserCredentials): Tokens
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package com.example.palto.data.network
|
||||
|
||||
import com.example.palto.data.Result
|
||||
import com.example.palto.model.LoggedInUser
|
||||
import com.example.palto.model.Tokens
|
||||
import java.io.IOException
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Class that handles API calls.
|
||||
*/
|
||||
class ServerDataSource {
|
||||
|
||||
private var hostname: String? = null
|
||||
|
||||
fun requestToken(
|
||||
hostname: String,
|
||||
username: String,
|
||||
password: String
|
||||
): Result<Tokens> {
|
||||
try {
|
||||
val tokens = Tokens(
|
||||
refresh = "aa",
|
||||
access = "bb"
|
||||
)
|
||||
return Result.Success(tokens)
|
||||
} catch (e: Throwable) {
|
||||
return Result.Error(IOException("Error logging in", e))
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshToken(current_tokens: Tokens): Result<Tokens> {
|
||||
return Result.Success(current_tokens)
|
||||
}
|
||||
|
||||
fun verifyToken(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun login(
|
||||
hostname: String,
|
||||
username: String,
|
||||
password: String
|
||||
): Result<LoggedInUser> {
|
||||
try {
|
||||
val fakeUser = LoggedInUser(
|
||||
UUID.randomUUID().toString(),
|
||||
"dede",
|
||||
"Lucie",
|
||||
"Doe",
|
||||
"aa@free.fr",
|
||||
)
|
||||
return Result.Success(fakeUser)
|
||||
} catch (e: Throwable) {
|
||||
return Result.Error(IOException("Error logging in", e))
|
||||
}
|
||||
}
|
||||
|
||||
fun logout() { }
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.example.palto.data.network.model
|
||||
|
||||
data class UserCredentials(
|
||||
val username: String,
|
||||
val password: String
|
||||
)
|
|
@ -1,10 +1,3 @@
|
|||
package com.example.palto.data.repository
|
||||
|
||||
import com.example.palto.data.network.ServerDataSource
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class AttendanceRepository(val dataSource: ServerDataSource) {
|
||||
// private val cards
|
||||
}
|
||||
class AttendanceRepository
|
|
@ -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
|
||||
|
||||
import com.example.palto.data.network.ServerDataSource
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class SessionRepository(val dataSource: ServerDataSource) {
|
||||
// private val cards
|
||||
}
|
||||
class SessionRepository
|
|
@ -0,0 +1,23 @@
|
|||
package com.example.palto.data.repository
|
||||
|
||||
import com.example.palto.data.network.PaltoApi
|
||||
import com.example.palto.data.network.model.UserCredentials
|
||||
import com.example.palto.domain.Tokens
|
||||
|
||||
/**
|
||||
* Class that requests authentication tokens from Palto server.
|
||||
*/
|
||||
class TokenRepository() {
|
||||
|
||||
private var tokens: Tokens? = null
|
||||
|
||||
suspend fun authenticate(
|
||||
hostname: String,
|
||||
username: String,
|
||||
password: String
|
||||
) {
|
||||
PaltoApi.createService("http://$hostname:8000/")
|
||||
val tokens = PaltoApi.retrofitService.getTokens((UserCredentials(username, password)))
|
||||
this.tokens = tokens
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package com.example.palto.data.repository
|
||||
|
||||
import com.example.palto.data.network.ServerDataSource
|
||||
import com.example.palto.model.Tokens
|
||||
|
||||
/**
|
||||
* Class that requests authentication and user information from the remote data source and
|
||||
* maintains an in-memory cache of login status and user credentials information.
|
||||
*/
|
||||
|
||||
class TokensRepository(val dataSource: ServerDataSource) {
|
||||
|
||||
var tokens: Tokens? = null
|
||||
private set
|
||||
|
||||
/*
|
||||
val isLoggedIn: Boolean
|
||||
get() = user != null
|
||||
*/
|
||||
|
||||
init {
|
||||
// If user credentials will be cached in local storage, it is recommended it be encrypted
|
||||
// @see https://developer.android.com/training/articles/keystore
|
||||
tokens = null
|
||||
}
|
||||
|
||||
/*
|
||||
fun login(username: String, password: String): Result<LoggedInUser> {
|
||||
// handle login
|
||||
val result = dataSource.login(username, password)
|
||||
|
||||
if (result is Result.Success) {
|
||||
setLoggedInUser(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
*/
|
||||
|
||||
private fun setTokens(tokens: Tokens) {
|
||||
this.tokens = tokens
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package com.example.palto.data.repository
|
||||
|
||||
class UserRepository
|
|
@ -1,10 +1,12 @@
|
|||
package com.example.palto.model
|
||||
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
|
7
app/src/main/java/com/example/palto/domain/Card.kt
Normal file
7
app/src/main/java/com/example/palto/domain/Card.kt
Normal file
|
@ -0,0 +1,7 @@
|
|||
package com.example.palto.domain
|
||||
|
||||
data class Card(
|
||||
val id: Int,
|
||||
val tagId: String,
|
||||
val user: User,
|
||||
)
|
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
|
||||
}
|
12
app/src/main/java/com/example/palto/domain/Session.kt
Normal file
12
app/src/main/java/com/example/palto/domain/Session.kt
Normal file
|
@ -0,0 +1,12 @@
|
|||
package com.example.palto.domain
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Data class that captures tokens for logged in users retrieved from LoginRepository
|
||||
*/
|
||||
data class Session(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
var attendances: List<Attendance>
|
||||
// When the list is updated, it is replaced by a new one.
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
package com.example.palto.model
|
||||
package com.example.palto.domain
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
|
@ -7,4 +7,4 @@ import java.io.Serializable
|
|||
data class Tokens(
|
||||
val refresh: String,
|
||||
val access: String
|
||||
) : Serializable
|
||||
)
|
12
app/src/main/java/com/example/palto/domain/User.kt
Normal file
12
app/src/main/java/com/example/palto/domain/User.kt
Normal file
|
@ -0,0 +1,12 @@
|
|||
package com.example.palto.domain
|
||||
|
||||
/**
|
||||
* Data class that captures user information for logged in users retrieved from LoginRepository
|
||||
*/
|
||||
data class User(
|
||||
val id: Int,
|
||||
val username: String,
|
||||
val firstName: String,
|
||||
val lastName: String,
|
||||
val email: String
|
||||
)
|
|
@ -1,23 +0,0 @@
|
|||
package com.example.palto.model
|
||||
import java.io.Serializable
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.example.palto.model
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Data class that captures user information for logged in users retrieved from LoginRepository
|
||||
*/
|
||||
data class LoggedInUser(
|
||||
val id: String,
|
||||
val username: String,
|
||||
val first_name: String,
|
||||
val last_name: String,
|
||||
val email: String
|
||||
) : Serializable
|
|
@ -1,9 +0,0 @@
|
|||
package com.example.palto.model
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Data class that captures tokens for logged in users retrieved from LoginRepository
|
||||
*/
|
||||
data class Session(
|
||||
val id: String
|
||||
) : Serializable
|
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
|
||||
}
|
||||
}
|
31
app/src/main/java/com/example/palto/ui/UserViewModel.kt
Normal file
31
app/src/main/java/com/example/palto/ui/UserViewModel.kt
Normal file
|
@ -0,0 +1,31 @@
|
|||
package com.example.palto.ui
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* UserViewModel maintain a list of users application wide.
|
||||
* May be converted into a repository.
|
||||
*/
|
||||
class UserViewModel: ViewModel() {
|
||||
|
||||
private var _users = MutableLiveData<List<User>>()
|
||||
val users : LiveData<List<User>> = _users
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package com.example.palto.ui.attendanceList
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.example.palto.databinding.FragmentAttendanceItemBinding
|
||||
import com.example.palto.model.Card
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class AttendanceListAdapter :
|
||||
ListAdapter<Card, AttendanceListAdapter.ViewHolder>(CardDiffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): ViewHolder {
|
||||
|
||||
return ViewHolder(
|
||||
FragmentAttendanceItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
holder.cardId.text = item.uid.toHexString()
|
||||
//holder.contentView.text = item.content
|
||||
}
|
||||
|
||||
inner class ViewHolder(
|
||||
binding: FragmentAttendanceItemBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
val cardId: TextView = binding.cardId
|
||||
override fun toString(): String {
|
||||
return super.toString() + " '" + cardId.text + "'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object CardDiffCallback : DiffUtil.ItemCallback<Card>() {
|
||||
override fun areItemsTheSame(oldItem: Card, newItem: Card): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Card, newItem: Card): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package com.example.palto.ui.attendanceList
|
||||
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
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.lifecycle.Observer
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import com.example.palto.PaltoViewModel
|
||||
import com.example.palto.R
|
||||
import com.example.palto.databinding.FragmentAttendanceListBinding
|
||||
|
||||
/**
|
||||
* A fragment representing a list of attendances.
|
||||
*/
|
||||
class AttendanceListFragment : Fragment() {
|
||||
|
||||
private val attendanceListViewModel: AttendanceListViewModel by
|
||||
navGraphViewModels(R.id.nav_graph) { AttendanceListViewModel.Factory }
|
||||
|
||||
private val paltoViewModel: PaltoViewModel by
|
||||
activityViewModels()
|
||||
|
||||
private var _binding: FragmentAttendanceListBinding? = null
|
||||
// This property is only valid between onCreateView and onDestroyView
|
||||
private val binding get() = _binding!!
|
||||
|
||||
/**
|
||||
* Only inflate the view.
|
||||
*/
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
|
||||
_binding = FragmentAttendanceListBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic on the returned view of onCreateView.
|
||||
*/
|
||||
override fun onViewCreated(
|
||||
view: View,
|
||||
savedInstanceState: Bundle?
|
||||
) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Set the adapter of the view for managing automatically the list of items on the screen.
|
||||
val adapter = AttendanceListAdapter()
|
||||
binding.list.adapter = adapter
|
||||
attendanceListViewModel.cardsLiveData.observe(viewLifecycleOwner) {
|
||||
Log.d("NFC", "A card has been had to the list")
|
||||
adapter.submitList(it)
|
||||
}
|
||||
|
||||
// Set the listener for a new NFC tag.
|
||||
paltoViewModel.tagLiveData.observe(viewLifecycleOwner) {
|
||||
Log.d("NFC", "tag observer has been notified")
|
||||
attendanceListViewModel.insertCard(it)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package com.example.palto.ui.attendanceList
|
||||
|
||||
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.network.ServerDataSource
|
||||
import com.example.palto.data.repository.AttendanceRepository
|
||||
import com.example.palto.model.Card
|
||||
|
||||
/**
|
||||
* ViewModel of a session which has a list of attendances.
|
||||
*/
|
||||
class AttendanceListViewModel(
|
||||
private val attendanceRepository: AttendanceRepository
|
||||
) : ViewModel() {
|
||||
|
||||
val cardsLiveData: MutableLiveData<List<Card>> = MutableLiveData(emptyList())
|
||||
|
||||
fun insertCard(tag: Tag) {
|
||||
val card = Card(
|
||||
"0",
|
||||
tag.id,
|
||||
"tmp",
|
||||
"tmp"
|
||||
)
|
||||
cardsLiveData.value = (cardsLiveData.value ?: emptyList()) + card
|
||||
Log.d("NFC", "view model: A card has been had to the list")
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewModel Factory.
|
||||
*/
|
||||
companion object {
|
||||
|
||||
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
modelClass: Class<T>
|
||||
): T {
|
||||
return AttendanceListViewModel(
|
||||
AttendanceRepository(ServerDataSource())
|
||||
) 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,62 @@
|
|||
package com.example.palto.ui.login
|
||||
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import com.example.palto.databinding.FragmentLoginBinding
|
||||
|
||||
import com.example.palto.R
|
||||
|
||||
class LoginFragment : Fragment() {
|
||||
|
||||
private val loginViewModel: LoginViewModel by
|
||||
navGraphViewModels(R.id.nav_graph) { LoginViewModelFactory() }
|
||||
// userViewModel is where the user is logged in, at the activity level.
|
||||
private val loginViewModel: LoginViewModel by activityViewModels() { LoginViewModel.Factory }
|
||||
|
||||
private var _binding: FragmentLoginBinding? = null
|
||||
|
||||
// This property is only valid between onCreateView and onDestroyView.
|
||||
private val binding get() = _binding!!
|
||||
private lateinit var binding: FragmentLoginBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentLoginBinding.inflate(inflater, container, false)
|
||||
|
||||
val navController = findNavController()
|
||||
|
||||
// Bind the login button.
|
||||
binding.login.setOnClickListener {
|
||||
binding.loading.visibility = View.VISIBLE
|
||||
loginViewModel.login(
|
||||
binding.hostname.text.toString(),
|
||||
binding.username.text.toString(),
|
||||
binding.password.text.toString()
|
||||
)
|
||||
}
|
||||
|
||||
// Bind anonymous login clickable text.
|
||||
binding.loginAnonymous.setOnClickListener {
|
||||
loginViewModel.loginAnonymous()
|
||||
}
|
||||
|
||||
// On result of logging.
|
||||
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()}"
|
||||
Toast.makeText(activity, it.error, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
_binding = FragmentLoginBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
/*
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
@ -46,23 +66,21 @@ class LoginFragment : Fragment() {
|
|||
val loginButton = binding.login
|
||||
val loadingProgressBar = binding.loading
|
||||
|
||||
//
|
||||
loginViewModel.loginFormState.observe(viewLifecycleOwner,
|
||||
Observer { loginFormState ->
|
||||
if (loginFormState == null) {
|
||||
return@Observer
|
||||
}
|
||||
loginButton.isEnabled = loginFormState.isDataValid
|
||||
loginFormState.hostnameError?.let {
|
||||
hostnameEditText.error = getString(it)
|
||||
}
|
||||
loginFormState.usernameError?.let {
|
||||
usernameEditText.error = getString(it)
|
||||
}
|
||||
loginFormState.passwordError?.let {
|
||||
passwordEditText.error = getString(it)
|
||||
}
|
||||
})
|
||||
loginViewModel.loginFormState.observe(viewLifecycleOwner) {
|
||||
if (it == null) {
|
||||
return@Observer
|
||||
}
|
||||
loginButton.isEnabled = it.isDataValid
|
||||
it.hostnameError?.let {
|
||||
hostnameEditText.error = getString(it)
|
||||
}
|
||||
it.usernameError?.let {
|
||||
usernameEditText.error = getString(it)
|
||||
}
|
||||
it.passwordError?.let {
|
||||
passwordEditText.error = getString(it)
|
||||
}
|
||||
}
|
||||
|
||||
loginViewModel.loginResult.observe(viewLifecycleOwner,
|
||||
Observer { loginResult ->
|
||||
|
@ -103,17 +121,8 @@ class LoginFragment : Fragment() {
|
|||
}
|
||||
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) {
|
||||
|
@ -122,15 +131,11 @@ class LoginFragment : Fragment() {
|
|||
val appContext = context?.applicationContext ?: return
|
||||
Toast.makeText(appContext, welcome, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
*/
|
||||
|
||||
private fun showLoginFailed(@StringRes errorString: Int) {
|
||||
val appContext = context?.applicationContext ?: return
|
||||
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
|
||||
|
||||
/**
|
||||
* Authentication result : success (user details) or error message.
|
||||
* Authentication result : success is true if connected or error message with exception.
|
||||
*/
|
||||
data class LoginResult(
|
||||
val success: LoggedInUserView? = null,
|
||||
val error: Int? = null
|
||||
val success: Boolean,
|
||||
val error: Int? = null, // Id of the string resource to display to the user.
|
||||
val exception: Exception? = null
|
||||
)
|
|
@ -1,84 +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 android.util.Patterns
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.example.palto.data.repository.LoginRepository
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.example.palto.R
|
||||
import com.example.palto.data.network.ServerDataSource
|
||||
import com.example.palto.data.repository.TokenRepository
|
||||
import com.example.palto.data.repository.UserRepository
|
||||
import com.example.palto.domain.User
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
|
||||
/**
|
||||
* LoginViewModel to access information about the logged in user and login form.
|
||||
*/
|
||||
class LoginViewModel(
|
||||
private val tokenRepository: TokenRepository,
|
||||
private val userRepository: UserRepository
|
||||
): ViewModel() {
|
||||
|
||||
private val _loginForm = MutableLiveData<LoginFormState>()
|
||||
val loginFormState: LiveData<LoginFormState> = _loginForm
|
||||
private var _result = MutableLiveData<LoginResult>()
|
||||
val result = _result as LiveData<LoginResult>
|
||||
|
||||
private val _loginResult = MutableLiveData<LoginResult>()
|
||||
val loginResult: LiveData<LoginResult> = _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 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(
|
||||
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()
|
||||
}
|
||||
|
||||
// A placeholder username validation check
|
||||
private fun isUserNameValid(username: String): Boolean {
|
||||
return if (username.contains("@")) {
|
||||
Patterns.EMAIL_ADDRESS.matcher(username).matches()
|
||||
} else {
|
||||
username.isNotBlank()
|
||||
}
|
||||
}
|
||||
|
||||
// A placeholder password validation check
|
||||
private fun isPasswordValid(password: String): Boolean {
|
||||
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()
|
||||
// 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
|
||||
)
|
||||
) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
private fun isUserNameValid(username: String): Boolean {
|
||||
return username.isNotBlank()
|
||||
}
|
||||
|
||||
private fun isPasswordValid(password: String): Boolean {
|
||||
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
|
||||
}
|
||||
}
|
||||
throw IllegalArgumentException("Unknown ViewModel class")
|
||||
}
|
||||
}
|
58
app/src/main/java/com/example/palto/ui/menu/MenuAdapter.kt
Normal file
58
app/src/main/java/com/example/palto/ui/menu/MenuAdapter.kt
Normal file
|
@ -0,0 +1,58 @@
|
|||
package com.example.palto.ui.menu
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.example.palto.databinding.FragmentMenuItemBinding
|
||||
import com.example.palto.domain.Session
|
||||
|
||||
/**
|
||||
* A [ListAdapter] that can display [Session] items.
|
||||
*/
|
||||
class MenuAdapter(private val onClick: (Session) -> Unit) :
|
||||
ListAdapter<Session, MenuAdapter.ViewHolder>(SessionDiffCallback) {
|
||||
inner class ViewHolder(binding: FragmentMenuItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
private val sessionNameText: TextView = binding.sessionName
|
||||
private var currentSession: Session? = null
|
||||
|
||||
init {
|
||||
binding.root.setOnClickListener {
|
||||
currentSession?.let {
|
||||
onClick(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(session: Session) {
|
||||
currentSession = session
|
||||
sessionNameText.text = session.name
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = FragmentMenuItemBinding
|
||||
.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
holder.bind(item)
|
||||
}
|
||||
|
||||
override fun getItemCount() = currentList.size
|
||||
}
|
||||
|
||||
object SessionDiffCallback : DiffUtil.ItemCallback<Session>() {
|
||||
override fun areItemsTheSame(oldItem: Session, newItem: Session): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Session, newItem: Session): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
}
|
71
app/src/main/java/com/example/palto/ui/menu/MenuFragment.kt
Normal file
71
app/src/main/java/com/example/palto/ui/menu/MenuFragment.kt
Normal file
|
@ -0,0 +1,71 @@
|
|||
package com.example.palto.ui.menu
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
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.login.LoginViewModel
|
||||
|
||||
/**
|
||||
* A fragment representing a list of Sessions.
|
||||
*/
|
||||
class MenuFragment : Fragment() {
|
||||
|
||||
private val menuViewModel: MenuViewModel by
|
||||
navGraphViewModels(R.id.nav_graph) { MenuViewModel.Factory }
|
||||
|
||||
private val loginViewModel: LoginViewModel by
|
||||
activityViewModels() { LoginViewModel.Factory }
|
||||
|
||||
// This property is only valid between onCreateView and onDestroyView
|
||||
private lateinit var binding: FragmentMenuListBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentMenuListBinding.inflate(inflater, container, false)
|
||||
|
||||
val navController = findNavController()
|
||||
|
||||
// Connect the user.
|
||||
loginViewModel.user.observe(viewLifecycleOwner) {
|
||||
if (it != null) {
|
||||
// Get sessions of the user from remote.
|
||||
} else {
|
||||
navController.navigate(R.id.loginFragment)
|
||||
}
|
||||
}
|
||||
|
||||
// Display the list of sessions.
|
||||
|
||||
// Create a new MenuAdapter (list) with the given function when clicking an item.
|
||||
val adapter = MenuAdapter { adapterOnClick(it) }
|
||||
binding.menuList.adapter = adapter
|
||||
// Link the adapter with the session list in the menuViewMode.
|
||||
menuViewModel.sessions.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(it)
|
||||
}
|
||||
|
||||
// Bind the add button.
|
||||
binding.createSession.setOnClickListener {
|
||||
navController.navigate(R.id.action_menuFragment_to_newSessionFragment)
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun adapterOnClick(session: Session) {
|
||||
val bundle = bundleOf("session" to session.id)
|
||||
findNavController().navigate(R.id.action_menuFragment_to_sessionFragment, bundle)
|
||||
}
|
||||
}
|
47
app/src/main/java/com/example/palto/ui/menu/MenuViewModel.kt
Normal file
47
app/src/main/java/com/example/palto/ui/menu/MenuViewModel.kt
Normal file
|
@ -0,0 +1,47 @@
|
|||
package com.example.palto.ui.menu
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.example.palto.domain.Attendance
|
||||
import com.example.palto.domain.Session
|
||||
|
||||
/**
|
||||
* ViewModel for accessing all the sessions created.
|
||||
*/
|
||||
class MenuViewModel() : ViewModel() {
|
||||
|
||||
// A list of sessions.
|
||||
private var _sessions = MutableLiveData<List<Session>>()
|
||||
val sessions: LiveData<List<Session>> = _sessions
|
||||
|
||||
fun createSession(name: String) {
|
||||
val list = _sessions.value ?: emptyList()
|
||||
val session = Session(
|
||||
id = list.size,
|
||||
name = name,
|
||||
attendances = emptyList()
|
||||
)
|
||||
_sessions.value = list + session
|
||||
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 {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return MenuViewModel() as T
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
|
@ -0,0 +1,52 @@
|
|||
package com.example.palto.ui.session
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
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 [Attendance] items.
|
||||
*/
|
||||
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 {
|
||||
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.bind(item)
|
||||
}
|
||||
}
|
||||
|
||||
object AttendanceDiffCallback : DiffUtil.ItemCallback<Attendance>() {
|
||||
override fun areItemsTheSame(oldItem: Attendance, newItem: Attendance): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Attendance, newItem: Attendance): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package com.example.palto.ui.session
|
||||
|
||||
import android.os.Bundle
|
||||
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.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.
|
||||
*/
|
||||
class SessionFragment : Fragment() {
|
||||
|
||||
private val sessionViewModel: SessionViewModel by
|
||||
navGraphViewModels(R.id.nav_graph) { SessionViewModel.Factory }
|
||||
|
||||
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()
|
||||
|
||||
// This property is only valid between onCreateView and onDestroyView
|
||||
private lateinit var binding: FragmentSessionListBinding
|
||||
|
||||
/**
|
||||
* Have the fragment instantiate the user interface.
|
||||
*/
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
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.attendances.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(it)
|
||||
}
|
||||
|
||||
// Set the listener for a new NFC tag.
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -0,0 +1,84 @@
|
|||
package com.example.palto.ui.session
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
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() : ViewModel() {
|
||||
|
||||
private var _result = MutableLiveData<Event<SessionResult>>()
|
||||
val result: LiveData<Event<SessionResult>> = _result
|
||||
|
||||
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.
|
||||
*/
|
||||
companion object {
|
||||
|
||||
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
modelClass: Class<T>
|
||||
): T {
|
||||
return SessionViewModel() 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()
|
|
@ -1,44 +0,0 @@
|
|||
package com.example.palto.ui.sessionList
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
|
||||
import com.example.palto.ui.sessionList.placeholder.PlaceholderContent.PlaceholderItem
|
||||
import com.example.palto.databinding.FragmentSessionItemBinding
|
||||
|
||||
/**
|
||||
* [RecyclerView.Adapter] that can display a [PlaceholderItem].
|
||||
*/
|
||||
class SessionListAdapter(private val values: List<PlaceholderItem>) :
|
||||
RecyclerView.Adapter<SessionListAdapter.ViewHolder>() {
|
||||
|
||||
class ViewHolder(binding: FragmentSessionItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
val idView: TextView = binding.itemNumber
|
||||
val contentView: TextView = binding.content
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString() + " '" + contentView.text + "'"
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = FragmentSessionItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = values[position]
|
||||
holder.idView.text = item.id
|
||||
holder.contentView.text = item.content
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = values.size
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package com.example.palto.ui.sessionList
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.example.palto.R
|
||||
import com.example.palto.ui.sessionList.placeholder.PlaceholderContent
|
||||
|
||||
/**
|
||||
* A fragment representing a list of Sessions.
|
||||
*/
|
||||
class SessionListFragment : Fragment() {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_session_list, container, false)
|
||||
|
||||
if (view is RecyclerView) {
|
||||
view.layoutManager = LinearLayoutManager(context)
|
||||
view.adapter = SessionListAdapter(PlaceholderContent.ITEMS)
|
||||
}
|
||||
return view
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.example.palto.ui.sessionList
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.example.palto.data.repository.LoginRepository
|
||||
|
||||
class SessionListViewModel(private val loginRepository: LoginRepository) : ViewModel() {
|
||||
/*
|
||||
private val _loginForm = MutableLiveData<LoginFormState>()
|
||||
val loginFormState: LiveData<LoginFormState> = _loginForm
|
||||
|
||||
private val _loginResult = MutableLiveData<LoginResult>()
|
||||
val loginResult: LiveData<LoginResult> = _loginResult
|
||||
*/
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,12 +1,21 @@
|
|||
<?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">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/palto_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainerView"
|
||||
android:id="@+id/palto_nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -14,6 +23,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/palto_toolbar"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView 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:id="@+id/list"
|
||||
android:name="com.example.palto.ui.attendanceList.AttendanceFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:context=".ui.attendanceList.AttendanceListFragment"
|
||||
tools:listitem="@layout/fragment_attendance_item" />
|
|
@ -13,11 +13,8 @@
|
|||
|
||||
<EditText
|
||||
android:id="@+id/hostname"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="331dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="64dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:autofillHints="@string/prompt_hostname"
|
||||
android:hint="@string/prompt_hostname"
|
||||
android:inputType="text"
|
||||
|
@ -30,10 +27,8 @@
|
|||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="331dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:autofillHints="@string/prompt_username"
|
||||
android:hint="@string/prompt_username"
|
||||
android:inputType="text"
|
||||
|
@ -46,29 +41,28 @@
|
|||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_width="331dp"
|
||||
android:layout_height="50dp"
|
||||
android:autofillHints="@string/prompt_password"
|
||||
android:hint="@string/prompt_password"
|
||||
android:imeActionLabel="@string/action_sign_in_short"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textPassword"
|
||||
android:selectAllOnFocus="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/help_message"
|
||||
app:layout_constraintBottom_toTopOf="@+id/login_error"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/username" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/help_message"
|
||||
android:id="@+id/login_error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/help_message"
|
||||
android:text=""
|
||||
app:layout_constraintBottom_toTopOf="@+id/login"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/password" />
|
||||
|
||||
|
@ -77,33 +71,38 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:layout_marginBottom="64dp"
|
||||
android:enabled="false"
|
||||
android:enabled="true"
|
||||
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_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/help_message"
|
||||
app:layout_constraintVertical_bias="0.2" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.5" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading"
|
||||
<TextView
|
||||
android:id="@+id/login_anonymous"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="64dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="64dp"
|
||||
android:visibility="gone"
|
||||
android:clickable="true"
|
||||
android:text="@string/login_anonymous_text"
|
||||
android:textColor="#F4511E"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/password"
|
||||
app:layout_constraintStart_toStartOf="@+id/password"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.3" />
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/login" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -4,10 +4,17 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/card_̤id"
|
||||
<!--<TextView
|
||||
android:id="@+id/session_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/text_margin"
|
||||
android:textAppearance="?attr/textAppearanceListItem" />
|
||||
android:textAppearance="?attr/textAppearanceListItem" />-->
|
||||
<TextView
|
||||
android:id="@+id/session_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:textAppearance="?attr/textAppearanceListItem"/>
|
||||
|
||||
</LinearLayout>
|
29
app/src/main/res/layout/fragment_menu_list.xml
Normal file
29
app/src/main/res/layout/fragment_menu_list.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout 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">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/menu_list"
|
||||
android:name="com.example.palto.ui.menu.menuFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:context=".ui.menu.MenuFragment"
|
||||
tools:listitem="@layout/fragment_menu_item" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/create_session"
|
||||
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_session"
|
||||
app:srcCompat="@android:drawable/ic_input_add" />
|
||||
|
||||
</FrameLayout>
|
33
app/src/main/res/layout/fragment_new_session.xml
Normal file
33
app/src/main/res/layout/fragment_new_session.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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_session_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:hint="@string/new_session_name_hint"
|
||||
android:inputType="text"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/new_session_create"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/new_session_create"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/new_session_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_session_name" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
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,14 +5,14 @@
|
|||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_number"
|
||||
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/content"
|
||||
android:id="@+id/attendance_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/text_margin"
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout 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:id="@+id/list"
|
||||
android:name="com.example.palto.ui.session.SessionListFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:context=".ui.sessionList.SessionListFragment"
|
||||
tools:listitem="@layout/fragment_session_item" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/session_list"
|
||||
android:name="com.example.palto.ui.session.sessionFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:context=".ui.session.SessionFragment"
|
||||
tools:listitem="@layout/fragment_session_item" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
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" />
|
||||
|
||||
</FrameLayout>
|
||||
|
|
8
app/src/main/res/menu/palto_menu.xml
Normal file
8
app/src/main/res/menu/palto_menu.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!--<item
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:title="@string/menu_item_logout" />-->
|
||||
</menu>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<background android:drawable="@color/white" />
|
||||
<foreground android:drawable="@drawable/icon_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<background android:drawable="@color/white" />
|
||||
<foreground android:drawable="@drawable/icon_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
|
@ -3,20 +3,47 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/nav_graph"
|
||||
app:startDestination="@id/attendanceListFragment">
|
||||
app:startDestination="@id/menuFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/menuFragment"
|
||||
android:name="com.example.palto.ui.menu.MenuFragment"
|
||||
android:label="Menu"
|
||||
tools:layout="@layout/fragment_menu_list" >
|
||||
<action
|
||||
android:id="@+id/action_menuFragment_to_sessionFragment"
|
||||
app:destination="@id/sessionFragment" />
|
||||
<action
|
||||
android:id="@+id/action_menuFragment_to_newSessionFragment"
|
||||
app:destination="@id/newSessionFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/loginFragment"
|
||||
android:name="com.example.palto.ui.login.LoginFragment"
|
||||
android:label="fragment._login"
|
||||
android:label="Connexion"
|
||||
tools:layout="@layout/fragment_login" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/sessionListFragment"
|
||||
android:name="com.example.palto.ui.sessionList.SessionListFragment"
|
||||
android:label="fragment_session_list"
|
||||
tools:layout="@layout/fragment_session_list" />
|
||||
android:id="@+id/newSessionFragment"
|
||||
android:name="com.example.palto.ui.menu.new_session.NewSessionFragment"
|
||||
android:label="Nouvelle fiche"
|
||||
tools:layout="@layout/fragment_new_session"/>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/attendanceListFragment"
|
||||
android:name="com.example.palto.ui.attendanceList.AttendanceListFragment"
|
||||
android:label="fragment_attendance_list"
|
||||
tools:layout="@layout/fragment_attendance_list" />
|
||||
android:id="@+id/sessionFragment"
|
||||
android:name="com.example.palto.ui.session.SessionFragment"
|
||||
android:label="Fiche de présence"
|
||||
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>
|
|
@ -1,7 +0,0 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.Theme.Palto" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your dark theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
|
||||
</style>
|
||||
</resources>
|
|
@ -1,5 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#00345f</color>
|
||||
<color name="colorOnPrimary">#ffffff</color>
|
||||
<color name="colorSecondary">#ef800a</color>
|
||||
<color name="colorOnSecondary">#ffffff</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
|
@ -5,11 +5,23 @@
|
|||
<string name="prompt_username">Nom d’utilisateur</string>
|
||||
<string name="prompt_password">Mot de passe</string>
|
||||
<string name="action_sign_in">Connexion</string>
|
||||
<string name="action_sign_in_short">Sign in</string>
|
||||
<string name="login_anonymous_text">Connexion anonyme</string>
|
||||
<string name="action_sign_in_short">Connexion</string>
|
||||
<string name="welcome">"Bienvenue !"</string>
|
||||
<string name="invalid_hostname">Serveur inaccessible</string>
|
||||
<string name="invalid_username">Nom d’utilisateur non valide</string>
|
||||
<string name="invalid_password">Mot de passe invalide</string>
|
||||
<string name="login_failed">"Erreur de connexion !"</string>
|
||||
<string name="help_message">Identifiants Invalides</string>
|
||||
<string name="create_session">Créer une session</string>
|
||||
<string name="create_card">Créer une présence</string>
|
||||
<string name="nouvelle_fiche">Nouvelle Fiche</string>
|
||||
<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>
|
|
@ -1,8 +1,17 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.Theme.Palto" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your light theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
||||
<resources>
|
||||
<style name="Base.Theme.Palto" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorOnPrimary">@color/colorOnPrimary</item>
|
||||
<item name="colorAccent">@color/colorSecondary</item>
|
||||
<item name="android:textSize">24sp</item>
|
||||
|
||||
<item name="floatingActionButtonStyle">@style/actionButton</item>
|
||||
</style>
|
||||
|
||||
<style name="actionButton">
|
||||
<item name="maxImageSize">50dp</item>
|
||||
<item name="tint">@color/white</item>
|
||||
<item name="backgroundTint">@color/colorSecondary</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Palto" parent="Base.Theme.Palto" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
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
|
||||
}
|
Loading…
Reference in a new issue