Deux nouveaux fragments.
This commit is contained in:
parent
fdee234200
commit
9f03838fba
31 changed files with 684 additions and 79 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -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
|
||||
|
|
3
.idea/.gitignore
vendored
3
.idea/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -1 +0,0 @@
|
|||
Palto
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="jbr-17 (2)" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.0" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -30,6 +30,9 @@ android {
|
|||
jvmTarget = "1.8"
|
||||
}
|
||||
buildToolsVersion = "33.0.1"
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -39,6 +42,11 @@ dependencies {
|
|||
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")
|
||||
|
|
61
app/src/main/java/com/example/palto/ItemFragment.kt
Normal file
61
app/src/main/java/com/example/palto/ItemFragment.kt
Normal file
|
@ -0,0 +1,61 @@
|
|||
package com.example.palto
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
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.placeholder.PlaceholderContent
|
||||
|
||||
/**
|
||||
* A fragment representing a list of Items.
|
||||
*/
|
||||
class ItemFragment : Fragment() {
|
||||
|
||||
private var columnCount = 1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
arguments?.let {
|
||||
columnCount = it.getInt(ARG_COLUMN_COUNT)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_item_list, container, false)
|
||||
|
||||
// Set the adapter
|
||||
if (view is RecyclerView) {
|
||||
with(view) {
|
||||
layoutManager = when {
|
||||
columnCount <= 1 -> LinearLayoutManager(context)
|
||||
else -> GridLayoutManager(context, columnCount)
|
||||
}
|
||||
adapter = MyItemRecyclerViewAdapter(PlaceholderContent.ITEMS)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// TODO: Customize parameter argument names
|
||||
const val ARG_COLUMN_COUNT = "column-count"
|
||||
|
||||
// TODO: Customize parameter initialization
|
||||
@JvmStatic
|
||||
fun newInstance(columnCount: Int) =
|
||||
ItemFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putInt(ARG_COLUMN_COUNT, columnCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.example.palto
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
|
||||
import com.example.palto.placeholder.PlaceholderContent.PlaceholderItem
|
||||
import com.example.palto.databinding.FragmentItemBinding
|
||||
|
||||
/**
|
||||
* [RecyclerView.Adapter] that can display a [PlaceholderItem].
|
||||
* TODO: Replace the implementation with code for your data type.
|
||||
*/
|
||||
class MyItemRecyclerViewAdapter(
|
||||
private val values: List<PlaceholderItem>
|
||||
) : RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
|
||||
return ViewHolder(
|
||||
FragmentItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
inner class ViewHolder(binding: FragmentItemBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
val idView: TextView = binding.itemNumber
|
||||
val contentView: TextView = binding.content
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString() + " '" + contentView.text + "'"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +1,21 @@
|
|||
package com.example.palto
|
||||
|
||||
import android.app.Activity
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
import java.net.URL
|
||||
|
||||
|
||||
class PaltoActivity : AppCompatActivity() {
|
||||
private var nfcAdapter: NfcAdapter? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_login)
|
||||
setContentView(R.layout.activity_palto)
|
||||
|
||||
// get the NFC Adapter
|
||||
this.nfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
|
@ -27,6 +30,12 @@ class PaltoActivity : AppCompatActivity() {
|
|||
if (!(this.nfcAdapter!!.isEnabled)) {
|
||||
Log.w("NFC", "NFC is not enabled")
|
||||
}
|
||||
|
||||
// TEST
|
||||
/*
|
||||
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() {
|
||||
|
|
24
app/src/main/java/com/example/palto/data/LoginDataSource.kt
Normal file
24
app/src/main/java/com/example/palto/data/LoginDataSource.kt
Normal file
|
@ -0,0 +1,24 @@
|
|||
package com.example.palto.data
|
||||
|
||||
import com.example.palto.data.model.LoggedInUser
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Class that handles authentication w/ login credentials and retrieves user information.
|
||||
*/
|
||||
class LoginDataSource {
|
||||
|
||||
fun login(username: String, password: String): Result<LoggedInUser> {
|
||||
try {
|
||||
// TODO: handle loggedInUser authentication
|
||||
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
|
||||
}
|
||||
}
|
46
app/src/main/java/com/example/palto/data/LoginRepository.kt
Normal file
46
app/src/main/java/com/example/palto/data/LoginRepository.kt
Normal file
|
@ -0,0 +1,46 @@
|
|||
package com.example.palto.data
|
||||
|
||||
import com.example.palto.data.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: LoginDataSource) {
|
||||
|
||||
// in-memory cache of the loggedInUser object
|
||||
var user: LoggedInUser? = 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
|
||||
user = null
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
user = null
|
||||
dataSource.logout()
|
||||
}
|
||||
|
||||
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 setLoggedInUser(loggedInUser: LoggedInUser) {
|
||||
this.user = loggedInUser
|
||||
// If user credentials will be cached in local storage, it is recommended it be encrypted
|
||||
// @see https://developer.android.com/training/articles/keystore
|
||||
}
|
||||
}
|
18
app/src/main/java/com/example/palto/data/Result.kt
Normal file
18
app/src/main/java/com/example/palto/data/Result.kt
Normal file
|
@ -0,0 +1,18 @@
|
|||
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]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.example.palto.data.model
|
||||
|
||||
/**
|
||||
* Data class that captures user information for logged in users retrieved from LoginRepository
|
||||
*/
|
||||
data class LoggedInUser(
|
||||
val userId: String,
|
||||
val displayName: String
|
||||
)
|
|
@ -0,0 +1,57 @@
|
|||
package com.example.palto.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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.example.palto.ui.login
|
||||
|
||||
/**
|
||||
* 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
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package com.example.palto.ui.login
|
||||
|
||||
/**
|
||||
* Data validation state of the login form.
|
||||
*/
|
||||
data class LoginFormState(
|
||||
val usernameError: Int? = null,
|
||||
val passwordError: Int? = null,
|
||||
val isDataValid: Boolean = false
|
||||
)
|
131
app/src/main/java/com/example/palto/ui/login/LoginFragment.kt
Normal file
131
app/src/main/java/com/example/palto/ui/login/LoginFragment.kt
Normal file
|
@ -0,0 +1,131 @@
|
|||
package com.example.palto.ui.login
|
||||
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
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.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Toast
|
||||
import com.example.palto.databinding.FragmentLoginBinding
|
||||
|
||||
import com.example.palto.R
|
||||
|
||||
class LoginFragment : Fragment() {
|
||||
|
||||
private lateinit var loginViewModel: LoginViewModel
|
||||
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)
|
||||
loginViewModel = ViewModelProvider(this, LoginViewModelFactory())
|
||||
.get(LoginViewModel::class.java)
|
||||
|
||||
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.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 {
|
||||
updateUiWithUser(it)
|
||||
}
|
||||
})
|
||||
|
||||
val afterTextChangedListener = object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
loginViewModel.loginDataChanged(
|
||||
usernameEditText.text.toString(),
|
||||
passwordEditText.text.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
usernameEditText.addTextChangedListener(afterTextChangedListener)
|
||||
passwordEditText.addTextChangedListener(afterTextChangedListener)
|
||||
passwordEditText.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
loginViewModel.login(
|
||||
usernameEditText.text.toString(),
|
||||
passwordEditText.text.toString()
|
||||
)
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
loginButton.setOnClickListener {
|
||||
loadingProgressBar.visibility = View.VISIBLE
|
||||
loginViewModel.login(
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -0,0 +1,55 @@
|
|||
package com.example.palto.ui.login
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import android.util.Patterns
|
||||
import com.example.palto.data.LoginRepository
|
||||
import com.example.palto.data.Result
|
||||
|
||||
import com.example.palto.R
|
||||
|
||||
class LoginViewModel(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
|
||||
|
||||
fun login(username: String, password: String) {
|
||||
// can be launched in a separate asynchronous job
|
||||
val result = loginRepository.login(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(username: String, password: String) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.example.palto.ui.login
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.example.palto.data.LoginDataSource
|
||||
import com.example.palto.data.LoginRepository
|
||||
|
||||
/**
|
||||
* ViewModel provider factory to instantiate LoginViewModel.
|
||||
* Required given LoginViewModel has a non-empty constructor
|
||||
*/
|
||||
class LoginViewModelFactory : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
|
||||
return LoginViewModel(
|
||||
loginRepository = LoginRepository(
|
||||
dataSource = LoginDataSource()
|
||||
)
|
||||
) as T
|
||||
}
|
||||
throw IllegalArgumentException("Unknown ViewModel class")
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?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"
|
||||
tools:context=".PaltoActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/test_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,6 +1,19 @@
|
|||
<?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.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainerView"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
20
app/src/main/res/layout/fragment_item.xml
Normal file
20
app/src/main/res/layout/fragment_item.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_number"
|
||||
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:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/text_margin"
|
||||
android:textAppearance="?attr/textAppearanceListItem" />
|
||||
</LinearLayout>
|
13
app/src/main/res/layout/fragment_item_list.xml
Normal file
13
app/src/main/res/layout/fragment_item_list.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?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.ItemFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:context=".ItemFragment"
|
||||
tools:listitem="@layout/fragment_item" />
|
78
app/src/main/res/layout/fragment_login.xml
Normal file
78
app/src/main/res/layout/fragment_login.xml
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?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:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/fragment_horizontal_margin"
|
||||
android:paddingTop="@dimen/fragment_vertical_margin"
|
||||
android:paddingRight="@dimen/fragment_horizontal_margin"
|
||||
android:paddingBottom="@dimen/fragment_vertical_margin"
|
||||
tools:context=".ui.login.LoginFragment">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="96dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:autofillHints="@string/prompt_email"
|
||||
android:hint="@string/prompt_email"
|
||||
android:inputType="textEmailAddress"
|
||||
android:selectAllOnFocus="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
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_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/username" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/login"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:layout_marginBottom="64dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/action_sign_in"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/password"
|
||||
app:layout_constraintVertical_bias="0.2" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading"
|
||||
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"
|
||||
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" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -2,4 +2,24 @@
|
|||
<navigation 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/nav_graph" />
|
||||
android:id="@+id/nav_graph"
|
||||
app:startDestination="@id/loginFragment">
|
||||
<fragment
|
||||
android:id="@+id/loginFragment"
|
||||
android:name="com.example.palto.ui.login.LoginFragment"
|
||||
android:label="fragment_login"
|
||||
tools:layout="@layout/fragment_login" >
|
||||
<action
|
||||
android:id="@+id/action_loginFragment_to_itemFragment"
|
||||
app:destination="@id/itemFragment"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_in_left"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_in_left" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/itemFragment"
|
||||
android:name="com.example.palto.ItemFragment"
|
||||
android:label="fragment_item_list"
|
||||
tools:layout="@layout/fragment_item_list" />
|
||||
</navigation>
|
6
app/src/main/res/values/dimens.xml
Normal file
6
app/src/main/res/values/dimens.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="fragment_horizontal_margin">16dp</dimen>
|
||||
<dimen name="fragment_vertical_margin">16dp</dimen>
|
||||
<dimen name="text_margin">16dp</dimen>
|
||||
</resources>
|
|
@ -1,3 +1,12 @@
|
|||
<resources>
|
||||
<string name="app_name">Palto</string>
|
||||
<!-- Strings related to login -->
|
||||
<string name="prompt_email">Email</string>
|
||||
<string name="prompt_password">Password</string>
|
||||
<string name="action_sign_in">Sign in or register</string>
|
||||
<string name="action_sign_in_short">Sign in</string>
|
||||
<string name="welcome">"Welcome!"</string>
|
||||
<string name="invalid_username">Not a valid username</string>
|
||||
<string name="invalid_password">Password must be >5 characters</string>
|
||||
<string name="login_failed">"Login failed"</string>
|
||||
</resources>
|
Loading…
Reference in a new issue