diff --git a/app/src/main/java/com/example/palto/PaltoViewModel.kt b/app/src/main/java/com/example/palto/PaltoViewModel.kt deleted file mode 100644 index 9d69dc4..0000000 --- a/app/src/main/java/com/example/palto/PaltoViewModel.kt +++ /dev/null @@ -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() -} \ 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 deleted file mode 100644 index d05487e..0000000 --- a/app/src/main/java/com/example/palto/data/local/LocalDataSource.kt +++ /dev/null @@ -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 { - 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/repository/AttendanceRepository.kt b/app/src/main/java/com/example/palto/data/repository/AttendanceRepository.kt index a40f502..5ceb649 100644 --- a/app/src/main/java/com/example/palto/data/repository/AttendanceRepository.kt +++ b/app/src/main/java/com/example/palto/data/repository/AttendanceRepository.kt @@ -1,10 +1,3 @@ package com.example.palto.data.repository -import com.example.palto.data.network.PaltoApiService - -/** - * - */ -class AttendanceRepository() { - // private val cards -} \ No newline at end of file +class AttendanceRepository \ 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 deleted file mode 100644 index ad61888..0000000 --- a/app/src/main/java/com/example/palto/data/repository/LoginRepository.kt +++ /dev/null @@ -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 { - // 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 index 177dc55..45e122c 100644 --- a/app/src/main/java/com/example/palto/data/repository/SessionRepository.kt +++ b/app/src/main/java/com/example/palto/data/repository/SessionRepository.kt @@ -1,10 +1,3 @@ package com.example.palto.data.repository -import com.example.palto.data.network.PaltoApiService - -/** - * - */ -class SessionRepository(val dataSource: PaltoApiService) { - // private val cards -} \ No newline at end of file +class SessionRepository \ No newline at end of file diff --git a/app/src/main/java/com/example/palto/data/repository/UserRepository.kt b/app/src/main/java/com/example/palto/data/repository/UserRepository.kt new file mode 100644 index 0000000..2c58c10 --- /dev/null +++ b/app/src/main/java/com/example/palto/data/repository/UserRepository.kt @@ -0,0 +1,3 @@ +package com.example.palto.data.repository + +class UserRepository \ 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/domain/User.kt similarity index 62% rename from app/src/main/java/com/example/palto/model/LoggedInUser.kt rename to app/src/main/java/com/example/palto/domain/User.kt index 6f60811..60fd4b2 100644 --- a/app/src/main/java/com/example/palto/model/LoggedInUser.kt +++ b/app/src/main/java/com/example/palto/domain/User.kt @@ -1,14 +1,13 @@ -package com.example.palto.model - -import java.io.Serializable +package com.example.palto.domain /** * Data class that captures user information for logged in users retrieved from LoginRepository */ -data class LoggedInUser( - val id: String, +data class User( + //Not in the domain layer + //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/ui/UserViewModel.kt b/app/src/main/java/com/example/palto/ui/UserViewModel.kt new file mode 100644 index 0000000..a767420 --- /dev/null +++ b/app/src/main/java/com/example/palto/ui/UserViewModel.kt @@ -0,0 +1,78 @@ +package com.example.palto.ui + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.example.palto.R +import com.example.palto.data.repository.TokenRepository +import com.example.palto.data.repository.UserRepository +import com.example.palto.domain.User +import com.example.palto.ui.login.LoginResult +import kotlinx.coroutines.launch + +class UserViewModel( + private val tokenRepository: TokenRepository, + private val userRepository: UserRepository +): ViewModel() { + + private var _result = MutableLiveData() + val result = _result as LiveData + + // User is initially set to null to be disconnected. + private var _user = MutableLiveData(null) + val user = _user as LiveData + + fun login( + hostname: String, + username: String, + password: String) { + + // Coroutine runs in background. + viewModelScope.launch { + try { + tokenRepository.authenticate(hostname, username, password) + _user.value = User( + username, + "", + "", + "" + ) + _result.value = LoginResult(success = true) + } catch (e: Exception) { + Log.e("Palto", "Connection error: " + e.message) + _result.value = LoginResult( + success = false, + error = R.string.login_failed, + exception = e + ) + } + } + } + + fun loginAnonymous() { + _user.value = User( + "anonymous", "", "", "" + ) + _result.value = LoginResult(success = true) + } + + fun logout() { + _user.value = null + _result.value = LoginResult(success = false) + } + + companion object { + + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return UserViewModel( + tokenRepository = TokenRepository(), + userRepository = UserRepository() + ) as T + } + } + } +} \ No newline at end of file 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 deleted file mode 100644 index 8cddb61..0000000 --- a/app/src/main/java/com/example/palto/ui/login/LoggedInUserView.kt +++ /dev/null @@ -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 -) 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 index acf12be..85530cf 100644 --- a/app/src/main/java/com/example/palto/ui/login/LoginFragment.kt +++ b/app/src/main/java/com/example/palto/ui/login/LoginFragment.kt @@ -1,42 +1,68 @@ 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.fragment.app.viewModels import androidx.navigation.fragment.findNavController -import androidx.navigation.navGraphViewModels import com.example.palto.databinding.FragmentLoginBinding - -import com.example.palto.R +import com.example.palto.ui.UserViewModel class LoginFragment : Fragment() { - private val loginViewModel: LoginViewModel by - navGraphViewModels(R.id.nav_graph) { LoginViewModelFactory() } + // loginViewModel is used to update the login screen dynamically. + private val loginViewModel: LoginViewModel by viewModels() - private var _binding: FragmentLoginBinding? = null + // userViewModel is where the user is logged in, at the activity level. + private val userViewModel: UserViewModel by activityViewModels() { UserViewModel.Factory } - // This property is only valid between onCreateView and onDestroyView. - private val binding get() = _binding!! + private lateinit var binding: FragmentLoginBinding + @SuppressLint("SetTextI18n") 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 + userViewModel.login( + binding.hostname.text.toString(), + binding.username.text.toString(), + binding.password.text.toString() + ) + } + + // Bind anonymous login clickable text. + binding.loginAnonymous.setOnClickListener { + userViewModel.loginAnonymous() + } + + // On result of logging. + userViewModel.result.observe(viewLifecycleOwner) { + binding.loading.visibility = View.GONE + if (it.success) { + navController.popBackStack() + } else if (it.error != null) { + binding.loginError.text = "Exception : " + it.exception.toString() + Toast.makeText(activity, it.error, Toast.LENGTH_LONG).show() + } + } - _binding = FragmentLoginBinding.inflate(inflater, container, false) return binding.root } + /* override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -46,23 +72,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 +127,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 +137,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 - } + */ } \ 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 index 9367f89..1d748f2 100644 --- a/app/src/main/java/com/example/palto/ui/login/LoginResult.kt +++ b/app/src/main/java/com/example/palto/ui/login/LoginResult.kt @@ -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 ) \ 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 index 99ea964..1e79ad5 100644 --- a/app/src/main/java/com/example/palto/ui/login/LoginViewModel.kt +++ b/app/src/main/java/com/example/palto/ui/login/LoginViewModel.kt @@ -1,40 +1,14 @@ 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) - } - */ - } +/** + * View Model to check dynamically the values of the form. + */ +class LoginViewModel() : ViewModel() { + /* + private val _loginFormState = MutableLiveData() + val loginFormState: LiveData = _loginFormState fun loginDataChanged( hostname: String, @@ -50,35 +24,18 @@ class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() _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() - } + return 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/menu/MenuFragment.kt b/app/src/main/java/com/example/palto/ui/menu/MenuFragment.kt index f71b3ea..dca0377 100644 --- a/app/src/main/java/com/example/palto/ui/menu/MenuFragment.kt +++ b/app/src/main/java/com/example/palto/ui/menu/MenuFragment.kt @@ -27,8 +27,20 @@ class MenuFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = FragmentMenuListBinding.inflate(inflater, container, false) + binding = FragmentMenuListBinding.inflate(inflater, container, false) + val navController = findNavController() + + // Connect the user. + userViewModel.user.observe(viewLifecycleOwner) { + if (it != null) { + // Get sessions of the user from remote. + } else { + navController.navigate(R.id.loginFragment) + } + } + + // Display the list of sessions. val adapter = MenuAdapter() binding.menuList.adapter = adapter menuViewModel.sessions.observe(viewLifecycleOwner) { 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 deleted file mode 100644 index b085b2c..0000000 --- a/app/src/main/java/com/example/palto/ui/sessionList/placeholder/PlaceholderContent.kt +++ /dev/null @@ -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 = 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/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml index 96f7159..7e7c0c7 100644 --- a/app/src/main/res/layout/fragment_login.xml +++ b/app/src/main/res/layout/fragment_login.xml @@ -13,11 +13,8 @@ @@ -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" /> + + + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.5" /> - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/login" /> \ 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 70d74d8..5cb3d0d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ Nom d’utilisateur Mot de passe Connexion + Connexion anonyme Sign in "Bienvenue !" Serveur inaccessible diff --git a/build.gradle.kts b/build.gradle.kts index 8e8f4ab..55ea678 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.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 } \ No newline at end of file