diff --git a/.gitignore b/.gitignore
index aa724b7..ae8485f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,15 +1,9 @@
*.iml
.gradle
-/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
+local.properties
+.idea
.DS_Store
-/build
-/captures
+build
.externalNativeBuild
.cxx
local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 7768bde..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-Palto
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index b589d56..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index ae388c2..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index fdf8d99..0000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index ac801d8..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2402d4b..39d63cb 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -30,14 +30,23 @@ android {
jvmTarget = "1.8"
}
buildToolsVersion = "33.0.1"
+ buildFeatures {
+ viewBinding = true
+ }
}
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("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.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")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7f7b9da..fc2cb02 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
-
@@ -16,14 +17,20 @@
android:theme="@style/Theme.Palto"
tools:targetApi="31">
+ android:name=".PaltoActivity"
+ android:exported="true"
+ android:launchMode="singleTop">
+
+
+
+
+
diff --git a/app/src/main/java/com/example/palto/MainActivity.kt b/app/src/main/java/com/example/palto/PaltoActivity.kt
similarity index 56%
rename from app/src/main/java/com/example/palto/MainActivity.kt
rename to app/src/main/java/com/example/palto/PaltoActivity.kt
index 7936f6c..8a70afa 100644
--- a/app/src/main/java/com/example/palto/MainActivity.kt
+++ b/app/src/main/java/com/example/palto/PaltoActivity.kt
@@ -1,48 +1,50 @@
package com.example.palto
-import android.app.PendingIntent
-import android.content.Intent
-import android.content.IntentFilter
import android.nfc.NfcAdapter
import android.nfc.Tag
-import android.nfc.tech.IsoDep
-import android.nfc.tech.NdefFormatable
-import android.nfc.tech.NfcA
-import android.os.Build
import android.os.Bundle
import android.util.Log
-import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
+import androidx.activity.viewModels
-class MainActivity : AppCompatActivity() {
+class PaltoActivity : AppCompatActivity() {
+
private var nfcAdapter: NfcAdapter? = null
+ private val paltoViewModel: PaltoViewModel by viewModels()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
// get the NFC Adapter
- this.nfcAdapter = NfcAdapter.getDefaultAdapter(this)
+ nfcAdapter = NfcAdapter.getDefaultAdapter(this)
// check if NFC is supported
- if (this.nfcAdapter == null) {
+ if (nfcAdapter == null) {
Log.e("NFC", "NFC is not supported")
return
}
// check if NFC is disabled
- if (!(this.nfcAdapter!!.isEnabled)) {
+ 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)
+ */
}
override fun onResume() {
super.onResume()
- nfcAdapter!!.enableReaderMode(
+ nfcAdapter?.enableReaderMode(
this,
- this::processTag,
+ paltoViewModel.tagLiveData::postValue,
NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
null
)
@@ -52,11 +54,11 @@ class MainActivity : AppCompatActivity() {
super.onPause()
// disable the NFC discovery
- this.nfcAdapter!!.disableReaderMode(this)
+ nfcAdapter?.disableReaderMode(this)
}
@OptIn(ExperimentalStdlibApi::class)
fun processTag(tag: Tag) {
- Log.d("NFC", "Tag ID : ${tag.id.toHexString()}")
+ Log.d("NFC", "Tag ID : " + tag.id.toHexString())
}
}
diff --git a/app/src/main/java/com/example/palto/PaltoViewModel.kt b/app/src/main/java/com/example/palto/PaltoViewModel.kt
new file mode 100644
index 0000000..9d69dc4
--- /dev/null
+++ b/app/src/main/java/com/example/palto/PaltoViewModel.kt
@@ -0,0 +1,9 @@
+package com.example.palto
+
+import android.nfc.Tag
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class PaltoViewModel: ViewModel() {
+ val tagLiveData = MutableLiveData()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/data/Result.kt b/app/src/main/java/com/example/palto/data/Result.kt
new file mode 100644
index 0000000..be27f93
--- /dev/null
+++ b/app/src/main/java/com/example/palto/data/Result.kt
@@ -0,0 +1,18 @@
+package com.example.palto.data
+
+/**
+ * A generic class that holds a value with its loading status.
+ * @param
+ */
+sealed class Result {
+
+ data class Success(val data: T) : Result()
+ data class Error(val exception: Exception) : Result()
+
+ override fun toString(): String {
+ return when (this) {
+ is Success<*> -> "Success[data=$data]"
+ is Error -> "Error[exception=$exception]"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/data/local/LocalDataSource.kt b/app/src/main/java/com/example/palto/data/local/LocalDataSource.kt
new file mode 100644
index 0000000..d05487e
--- /dev/null
+++ b/app/src/main/java/com/example/palto/data/local/LocalDataSource.kt
@@ -0,0 +1,24 @@
+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 {
+ 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
+ }
+ */
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/data/network/ServerDataSource.kt b/app/src/main/java/com/example/palto/data/network/ServerDataSource.kt
new file mode 100644
index 0000000..8c68892
--- /dev/null
+++ b/app/src/main/java/com/example/palto/data/network/ServerDataSource.kt
@@ -0,0 +1,60 @@
+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 {
+ 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 {
+ return Result.Success(current_tokens)
+ }
+
+ fun verifyToken(): Boolean {
+ return true
+ }
+
+ fun login(
+ hostname: String,
+ username: String,
+ password: String
+ ): Result {
+ 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() { }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/data/repository/AttendanceRepository.kt b/app/src/main/java/com/example/palto/data/repository/AttendanceRepository.kt
new file mode 100644
index 0000000..13a8631
--- /dev/null
+++ b/app/src/main/java/com/example/palto/data/repository/AttendanceRepository.kt
@@ -0,0 +1,10 @@
+package com.example.palto.data.repository
+
+import com.example.palto.data.network.ServerDataSource
+
+/**
+ *
+ */
+class AttendanceRepository(val dataSource: ServerDataSource) {
+ // private val cards
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/data/repository/LoginRepository.kt b/app/src/main/java/com/example/palto/data/repository/LoginRepository.kt
new file mode 100644
index 0000000..ad61888
--- /dev/null
+++ b/app/src/main/java/com/example/palto/data/repository/LoginRepository.kt
@@ -0,0 +1,47 @@
+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 {
+ // 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/data/repository/SessionRepository.kt b/app/src/main/java/com/example/palto/data/repository/SessionRepository.kt
new file mode 100644
index 0000000..3b4b4a7
--- /dev/null
+++ b/app/src/main/java/com/example/palto/data/repository/SessionRepository.kt
@@ -0,0 +1,10 @@
+package com.example.palto.data.repository
+
+import com.example.palto.data.network.ServerDataSource
+
+/**
+ *
+ */
+class SessionRepository(val dataSource: ServerDataSource) {
+ // private val cards
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/data/repository/TokensRepository.kt b/app/src/main/java/com/example/palto/data/repository/TokensRepository.kt
new file mode 100644
index 0000000..184f04c
--- /dev/null
+++ b/app/src/main/java/com/example/palto/data/repository/TokensRepository.kt
@@ -0,0 +1,43 @@
+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 {
+ // 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/model/Attendance.kt b/app/src/main/java/com/example/palto/model/Attendance.kt
new file mode 100644
index 0000000..1288725
--- /dev/null
+++ b/app/src/main/java/com/example/palto/model/Attendance.kt
@@ -0,0 +1,10 @@
+package com.example.palto.model
+import java.io.Serializable
+
+/**
+ * Data class that captures tokens for logged in users retrieved from LoginRepository
+ */
+data class Attendance(
+ val date: String,
+ val access: String
+) : Serializable
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/model/Card.kt b/app/src/main/java/com/example/palto/model/Card.kt
new file mode 100644
index 0000000..8c55422
--- /dev/null
+++ b/app/src/main/java/com/example/palto/model/Card.kt
@@ -0,0 +1,23 @@
+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()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/model/LoggedInUser.kt b/app/src/main/java/com/example/palto/model/LoggedInUser.kt
new file mode 100644
index 0000000..6f60811
--- /dev/null
+++ b/app/src/main/java/com/example/palto/model/LoggedInUser.kt
@@ -0,0 +1,14 @@
+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
diff --git a/app/src/main/java/com/example/palto/model/Session.kt b/app/src/main/java/com/example/palto/model/Session.kt
new file mode 100644
index 0000000..8a6827f
--- /dev/null
+++ b/app/src/main/java/com/example/palto/model/Session.kt
@@ -0,0 +1,9 @@
+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
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/model/Tokens.kt b/app/src/main/java/com/example/palto/model/Tokens.kt
new file mode 100644
index 0000000..fa90475
--- /dev/null
+++ b/app/src/main/java/com/example/palto/model/Tokens.kt
@@ -0,0 +1,10 @@
+package com.example.palto.model
+import java.io.Serializable
+
+/**
+ * Data class that captures tokens for logged in users retrieved from LoginRepository
+ */
+data class Tokens(
+ val refresh: String,
+ val access: String
+) : Serializable
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListAdapter.kt b/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListAdapter.kt
new file mode 100644
index 0000000..a7788b1
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListAdapter.kt
@@ -0,0 +1,58 @@
+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(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() {
+ override fun areItemsTheSame(oldItem: Card, newItem: Card): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areContentsTheSame(oldItem: Card, newItem: Card): Boolean {
+ return oldItem.id == newItem.id
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListFragment.kt b/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListFragment.kt
new file mode 100644
index 0000000..68773ab
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListFragment.kt
@@ -0,0 +1,69 @@
+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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListViewModel.kt b/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListViewModel.kt
new file mode 100644
index 0000000..a4efa38
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/attendanceList/AttendanceListViewModel.kt
@@ -0,0 +1,49 @@
+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> = 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 create(
+ modelClass: Class
+ ): T {
+ return AttendanceListViewModel(
+ AttendanceRepository(ServerDataSource())
+ ) as T
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/palto/ui/login/LoggedInUserView.kt b/app/src/main/java/com/example/palto/ui/login/LoggedInUserView.kt
new file mode 100644
index 0000000..8cddb61
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/login/LoggedInUserView.kt
@@ -0,0 +1,13 @@
+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
+)
diff --git a/app/src/main/java/com/example/palto/ui/login/LoginFormState.kt b/app/src/main/java/com/example/palto/ui/login/LoginFormState.kt
new file mode 100644
index 0000000..8edce28
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/login/LoginFormState.kt
@@ -0,0 +1,11 @@
+package com.example.palto.ui.login
+
+/**
+ * Data validation state of the login form.
+ */
+data class LoginFormState(
+ val hostnameError: Int? = null,
+ val usernameError: Int? = null,
+ val passwordError: Int? = null,
+ val isDataValid: Boolean = false
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/login/LoginFragment.kt b/app/src/main/java/com/example/palto/ui/login/LoginFragment.kt
new file mode 100644
index 0000000..acf12be
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/login/LoginFragment.kt
@@ -0,0 +1,136 @@
+package com.example.palto.ui.login
+
+import androidx.lifecycle.Observer
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+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.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() }
+
+ private var _binding: FragmentLoginBinding? = null
+
+ // This property is only valid between onCreateView and onDestroyView.
+ private val binding get() = _binding!!
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+
+ _binding = FragmentLoginBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val hostnameEditText = binding.hostname
+ val usernameEditText = binding.username
+ val passwordEditText = binding.password
+ 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.loginResult.observe(viewLifecycleOwner,
+ Observer { loginResult ->
+ loginResult ?: return@Observer
+ loadingProgressBar.visibility = View.GONE
+ loginResult.error?.let {
+ showLoginFailed(it)
+ }
+ loginResult.success?.let {
+ //findNavController().navigate(R.id.action_loginFragment_to_attendanceFragment)
+ //updateUiWithUser(it)
+ }
+ })
+
+ val afterTextChangedListener = object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
+
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
+
+ override fun afterTextChanged(s: Editable) {
+ loginViewModel.loginDataChanged(
+ hostnameEditText.text.toString(),
+ usernameEditText.text.toString(),
+ passwordEditText.text.toString()
+ )
+ }
+ }
+ hostnameEditText.addTextChangedListener(afterTextChangedListener)
+ usernameEditText.addTextChangedListener(afterTextChangedListener)
+ passwordEditText.addTextChangedListener(afterTextChangedListener)
+ passwordEditText.setOnEditorActionListener { _, actionId, _ ->
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ loginViewModel.login(
+ hostnameEditText.text.toString(),
+ usernameEditText.text.toString(),
+ passwordEditText.text.toString()
+ )
+ }
+ 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) {
+ val welcome = getString(R.string.welcome) + model.displayName
+ // TODO : initiate successful logged in experience
+ 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/login/LoginResult.kt b/app/src/main/java/com/example/palto/ui/login/LoginResult.kt
new file mode 100644
index 0000000..9367f89
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/login/LoginResult.kt
@@ -0,0 +1,9 @@
+package com.example.palto.ui.login
+
+/**
+ * Authentication result : success (user details) or error message.
+ */
+data class LoginResult(
+ val success: LoggedInUserView? = null,
+ val error: Int? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/login/LoginViewModel.kt b/app/src/main/java/com/example/palto/ui/login/LoginViewModel.kt
new file mode 100644
index 0000000..99ea964
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/login/LoginViewModel.kt
@@ -0,0 +1,84 @@
+package com.example.palto.ui.login
+
+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 com.example.palto.R
+import com.example.palto.data.network.ServerDataSource
+
+class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
+
+ private val _loginForm = MutableLiveData()
+ val loginFormState: LiveData = _loginForm
+
+ private val _loginResult = MutableLiveData()
+ val loginResult: LiveData = _loginResult
+
+ fun login(
+ hostname: String,
+ username: String,
+ password: String) {
+ // can be launched in a separate asynchronous job
+ val result = loginRepository.login(hostname, username, password)
+
+ /*
+ if (result is Result.Success) {
+ _loginResult.value =
+ LoginResult(success = LoggedInUserView(
+ displayName = result.data.displayName))
+ } else {
+ _loginResult.value = LoginResult(error = R.string.login_failed)
+ }
+ */
+ }
+
+ fun loginDataChanged(
+ 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 create(modelClass: Class): T {
+ if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
+ return LoginViewModel(
+ loginRepository = LoginRepository(
+ dataSource = ServerDataSource()
+ )
+ ) as T
+ }
+ throw IllegalArgumentException("Unknown ViewModel class")
+ }
+}
diff --git a/app/src/main/java/com/example/palto/ui/sessionList/SessionListAdapter.kt b/app/src/main/java/com/example/palto/ui/sessionList/SessionListAdapter.kt
new file mode 100644
index 0000000..24f492f
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/sessionList/SessionListAdapter.kt
@@ -0,0 +1,44 @@
+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) :
+ RecyclerView.Adapter() {
+
+ 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
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/sessionList/SessionListFragment.kt b/app/src/main/java/com/example/palto/ui/sessionList/SessionListFragment.kt
new file mode 100644
index 0000000..03424e1
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/sessionList/SessionListFragment.kt
@@ -0,0 +1,31 @@
+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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/sessionList/SessionListViewModel.kt b/app/src/main/java/com/example/palto/ui/sessionList/SessionListViewModel.kt
new file mode 100644
index 0000000..d127a36
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/sessionList/SessionListViewModel.kt
@@ -0,0 +1,14 @@
+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()
+ val loginFormState: LiveData = _loginForm
+
+ private val _loginResult = MutableLiveData()
+ val loginResult: LiveData = _loginResult
+ */
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/palto/ui/sessionList/placeholder/PlaceholderContent.kt b/app/src/main/java/com/example/palto/ui/sessionList/placeholder/PlaceholderContent.kt
new file mode 100644
index 0000000..b085b2c
--- /dev/null
+++ b/app/src/main/java/com/example/palto/ui/sessionList/placeholder/PlaceholderContent.kt
@@ -0,0 +1,57 @@
+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 = ArrayList()
+
+ /**
+ * A map of sample (placeholder) items, by ID.
+ */
+ val ITEM_MAP: MutableMap = 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_palto.xml
similarity index 55%
rename from app/src/main/res/layout/activity_main.xml
rename to app/src/main/res/layout/activity_palto.xml
index 1b3bb02..3a70781 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_palto.xml
@@ -3,17 +3,17 @@
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"
- tools:context=".MainActivity">
+ android:layout_height="match_parent">
-
-
+ app:layout_constraintTop_toTopOf="parent"
+ app:navGraph="@navigation/nav_graph" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_attendance_item.xml b/app/src/main/res/layout/fragment_attendance_item.xml
new file mode 100644
index 0000000..b975c33
--- /dev/null
+++ b/app/src/main/res/layout/fragment_attendance_item.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_attendance_list.xml b/app/src/main/res/layout/fragment_attendance_list.xml
new file mode 100644
index 0000000..318836a
--- /dev/null
+++ b/app/src/main/res/layout/fragment_attendance_list.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml
new file mode 100644
index 0000000..96f7159
--- /dev/null
+++ b/app/src/main/res/layout/fragment_login.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_session_item.xml b/app/src/main/res/layout/fragment_session_item.xml
new file mode 100644
index 0000000..1877568
--- /dev/null
+++ b/app/src/main/res/layout/fragment_session_item.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_session_list.xml b/app/src/main/res/layout/fragment_session_list.xml
new file mode 100644
index 0000000..6e305ab
--- /dev/null
+++ b/app/src/main/res/layout/fragment_session_list.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..4b9c3fd
--- /dev/null
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..8448934
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 16dp
+ 16dp
+ 16dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d07bee8..51b4ac6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,15 @@
Palto
+
+ Serveur
+ Nom d’utilisateur
+ Mot de passe
+ Connexion
+ Sign in
+ "Bienvenue !"
+ Serveur inaccessible
+ Nom d’utilisateur non valide
+ Mot de passe invalide
+ "Erreur de connexion !"
+ Identifiants Invalides
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index df06d44..8e8f4ab 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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.1.2" apply false
+ id("com.android.application") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
}
\ No newline at end of file