- Viewmodels and live data for the data involved

- migrating to kotlin and removal of spaghetti code
- android navigation added
- user authentication section finished
- ui updated for authentication section
This commit is contained in:
2020-07-17 13:36:28 +01:00
parent 66af16b999
commit c476ed556d
46 changed files with 2037 additions and 553 deletions

179
.idea/navEditor.xml generated Normal file
View File

@@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="navEditor-manualLayoutAlgorithm2">
<option name="myPositions">
<map>
<entry key="auth_navigation.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="forgotPassword">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="1090" />
<option name="y" value="-654" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="loginFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="707" />
<option name="y" value="-640" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_loginFragment_to_forgotPassword">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_loginFragment_to_mainActivity">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="loginHomeFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="443" />
<option name="y" value="-636" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_splashFragment_to_loginFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="to_registrationNicknameFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="mainActivity">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="1094" />
<option name="y" value="-236" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="registrationConfirmationFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="703" />
<option name="y" value="-234" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_registrationConfirmationFragment_to_mainActivity">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="registrationEmailFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="444" />
<option name="y" value="101" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_registrationEmailFragment_to_registrationPasswordFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="registrationNicknameFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="437" />
<option name="y" value="-236" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_registrationNicknameFragment_to_registrationEmailFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="registrationPasswordFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="705" />
<option name="y" value="103" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_registrationPasswordFragment_to_registrationConfirmationFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@@ -1,12 +1,13 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
// kotlin kapt
apply plugin: 'kotlin-kapt'
// google services
apply plugin: 'com.google.gms.google-services'
// Android navigation
apply plugin: 'androidx.navigation.safeargs'
android {
compileSdkVersion 29
@@ -54,6 +55,8 @@ dependencies {
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.exifinterface:exifinterface:1.2.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
// Testing
testImplementation 'junit:junit:4.13'

View File

@@ -16,18 +16,22 @@
android:supportsRtl="true"
android:name=".application.AppClass"
android:theme="@style/AppTheme.NoActionBar">
<activity android:name=".AddItemActivity"></activity>
<activity
android:name="com.appttude.h_mal.days_left.ui.login.FullscreenActivity"
<activity android:name=".ui.splashScreen.SplashFragment"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.appttude.h_mal.days_left.ui.login.FullscreenActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"/>
<activity android:name=".AddItemActivity"/>
<activity android:name=".ChangeUserDetailsActivity" />
<activity android:name=".AddShiftActivity" />
<activity

View File

@@ -0,0 +1,47 @@
package com.appttude.h_mal.days_left
import android.util.Log
import androidx.lifecycle.ViewModel
import com.appttude.h_mal.days_left.data.firebase.FirebaseFunctionsSource
import com.appttude.h_mal.days_left.models.AbnObject
import com.appttude.h_mal.days_left.utils.genericType
import com.google.gson.Gson
import java.io.IOException
import java.lang.Exception
class FunctionsViewmodel(
val functions: FirebaseFunctionsSource
) : ViewModel(){
fun getAbnList(input: String){
try {
functions.getAbnList(input).continueWith{ task ->
// This continuation runs on either success or failure, but if the task
// has failed then getResult() will throw an Exception which will be
// propagated down.
val result= task.result?.data
result
}.continueWith {
if (!it.isSuccessful){
throw it.exception ?: IOException("Unable to retrieve Abn details")
}
val json = Gson().toJson(it)
val type = genericType<List<AbnObject>>()
Gson().fromJson<List<AbnObject>>(json, type)
}.addOnCompleteListener {
if (!it.isSuccessful){
throw it.exception ?: IOException("Unable to retrieve Abn details")
}
val list = it.result
}
}catch (e: Exception){
e.cause?.printStackTrace()
e.message?.let {
}
}
}
}

View File

@@ -1,9 +1,10 @@
package com.appttude.h_mal.days_left.application
import android.app.Application
import com.appttude.h_mal.days_left.FirebaseDatabase
import com.appttude.h_mal.days_left.ui.login.FirebaseAuthSource
import com.appttude.h_mal.days_left.ui.login.UserRepository
import com.appttude.h_mal.days_left.ui.login.AuthViewModelFactory
import com.appttude.h_mal.days_left.data.firebase.FirebaseAuthSource
import com.appttude.h_mal.days_left.data.firebase.FirebaseDataSource
import com.appttude.h_mal.days_left.data.repository.UserRepository
import com.appttude.h_mal.days_left.ui.main.MainViewModelFactory
import com.appttude.h_mal.days_left.ui.main.ShiftsViewModelFactory
import org.kodein.di.Kodein
@@ -20,17 +21,18 @@ class AppClass : Application(), KodeinAware{
import(androidXModule(this@AppClass))
bind() from singleton { FirebaseAuthSource() }
bind() from singleton { FirebaseDatabase() }
bind() from singleton { UserRepository(instance()) }
bind() from provider {
MainViewModelFactory(
bind() from singleton { FirebaseDataSource() }
bind() from singleton {
UserRepository(
instance()
)
}
bind() from provider { MainViewModelFactory( instance() ) }
bind() from provider {
ShiftsViewModelFactory(
instance()
)
ShiftsViewModelFactory( instance() )
}
bind() from provider {
AuthViewModelFactory( instance() )
}
}
}

View File

@@ -1,7 +1,10 @@
package com.appttude.h_mal.days_left
package com.appttude.h_mal.days_left.data.firebase
import com.appttude.h_mal.days_left.FirebaseClass
import com.appttude.h_mal.days_left.FirebaseClass.Companion.auth
import com.appttude.h_mal.days_left.FirebaseClass.Companion.mDatabase
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
const val USER_FIREBASE = "users"
val EMPLOYER_FIREBASE = "employers"
@@ -12,10 +15,19 @@ val SHIFT_ID = "shift_id"
val PIECE = "Piece Rate"
val HOURLY = "Hourly"
class FirebaseDatabase{
class FirebaseDataSource{
fun allShifts() = mDatabase.child(USER_FIREBASE).child(auth.uid!!).child(SHIFT_FIREBASE)
fun child(shiftId: String) = mDatabase.child(USER_FIREBASE).child(auth.uid!!).child(SHIFT_FIREBASE).child(shiftId)
private val firebaseDatabase: FirebaseDatabase by lazy {
FirebaseDatabase.getInstance()
}
private val mDatabase = firebaseDatabase.reference
fun allShifts() = mDatabase.child(USER_FIREBASE).child(auth.uid!!).child(
SHIFT_FIREBASE
)
fun child(shiftId: String) = mDatabase.child(USER_FIREBASE).child(auth.uid!!).child(
SHIFT_FIREBASE
).child(shiftId)
fun taskObject(abn: String) = mDatabase.child(FirebaseClass.EMPLOYER_FIREBASE).child(abn).child(
FirebaseClass.TASK_FIREBASE

View File

@@ -0,0 +1,34 @@
package com.appttude.h_mal.days_left.data.firebase
import com.google.android.gms.tasks.Task
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.functions.FirebaseFunctions
import com.google.firebase.functions.HttpsCallableResult
import java.util.HashMap
class FirebaseFunctionsSource {
private val functions: FirebaseFunctions by lazy {
FirebaseFunctions.getInstance()
}
fun getAbnList(input: String): Task<HttpsCallableResult> {
val data = HashMap<String, Any>()
data["input"] = input
data["push"] = true
return functions
.getHttpsCallable("abnLooKUp")
.call(data)
}
fun getExcel(): Task<HttpsCallableResult> {
// Create the arguments to the callable function.
val data = HashMap<String, Any>()
data["push"] = true
return functions
.getHttpsCallable("writeFireToExcelVisa")
.call(data)
}
}

View File

@@ -0,0 +1,20 @@
package com.appttude.h_mal.days_left.firebaseUtils
import com.google.android.gms.tasks.Task
import java.io.IOException
abstract class FirebaseExtraction {
fun <T : Any> safeFirebaseResult(
call: () -> Task<T>
): T {
val task = call.invoke()
if (!task.isSuccessful) {
throw task.exception ?: IOException("Failed to complete firebase task")
} else {
return task.result!!
}
}
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.days_left.firebaseLiveData
package com.appttude.h_mal.days_left.firebaseUtils
import android.util.Log
import androidx.lifecycle.LiveData

View File

@@ -0,0 +1,10 @@
package com.appttude.h_mal.days_left.models
/**
* Data validation state of the login form.
*/
data class LoginFormState(
val usernameError: Int? = null,
val passwordError: Int? = null,
val isDataValid: Boolean = false
)

View File

@@ -0,0 +1,24 @@
package com.appttude.h_mal.days_left.models.liveData
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}

View File

@@ -0,0 +1,38 @@
package com.appttude.h_mal.days_left.models.registration
import android.os.Parcel
import android.os.Parcelable
data class RegistrationArgs(
var name: String? = null,
var email: String? = null,
var password: String? = null
): Parcelable{
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeString(email)
parcel.writeString(password)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<RegistrationArgs> {
override fun createFromParcel(parcel: Parcel): RegistrationArgs {
return RegistrationArgs(parcel)
}
override fun newArray(size: Int): Array<RegistrationArgs?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -2,7 +2,7 @@ package com.appttude.h_mal.days_left.ui.login
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.days_left.FirebaseDatabase
import com.appttude.h_mal.days_left.data.repository.UserRepository
/**
* ViewModel provider factory to instantiate LoginViewModel.

View File

@@ -5,13 +5,22 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import android.view.inputmethod.EditorInfo
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.ui.login.FullscreenActivity.Companion.fragmentManagerLogin
import kotlinx.android.synthetic.main.fragment_forgot_password.*
import com.appttude.h_mal.days_left.utils.afterTextChanged
import com.appttude.h_mal.days_left.utils.isEmailValid
import com.appttude.h_mal.days_left.utils.navigateTo
import com.appttude.h_mal.days_left.utils.showToast
import kotlinx.android.synthetic.main.fragment_forgot_password.submission_et
import kotlinx.android.synthetic.main.fragment_forgot_password.til_submission
class ForgotPassword : Fragment(), ForgotPasswordCallback {
class ForgotPassword : Fragment() {
val viewModel: AuthViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@@ -21,24 +30,39 @@ class ForgotPassword : Fragment(), ForgotPasswordCallback {
return inflater.inflate(R.layout.fragment_forgot_password, container, false)
}
override fun onStarted() {
pb.visibility = View.VISIBLE
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
submission_et.apply{
afterTextChanged {
til_submission.error = null
}
setOnEditorActionListener { _, id, _ ->
if (id == EditorInfo.IME_ACTION_DONE) {
resetPassword()
return@setOnEditorActionListener true
}
false
}
}
viewModel.operationResetPassword.observe(viewLifecycleOwner, Observer {
it.getContentIfNotHandled()?.let {
context?.let {ctx ->
ctx.showToast(ctx.resources.getString(R.string.password_reset_success))
activity?.onBackPressed()
}
}
})
}
override fun onEmailInvalid() {
pb.visibility = View.GONE
reset_pw.error = getString(R.string.error_invalid_password)
private fun resetPassword(){
val email = submission_et.text.toString()
if (!isEmailValid(email)){
til_submission.error = "Not a valid email address"
return
}
viewModel.resetPassword(email)
}
override fun onSuccess() {
pb.visibility = View.GONE
fragmentManagerLogin.popBackStack()
}
override fun onFailure(message: String) {
pb.visibility = View.GONE
Toast.makeText(context,message,Toast.LENGTH_LONG).show()
}
}

View File

@@ -2,38 +2,55 @@ package com.appttude.h_mal.days_left.ui.login
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.Observer
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.FirebaseClass.Companion.auth
import com.google.firebase.auth.FirebaseAuth
import com.appttude.h_mal.days_left.ui.splashScreen.SplashFragment
import com.appttude.h_mal.days_left.utils.hide
import com.appttude.h_mal.days_left.utils.show
import com.appttude.h_mal.days_left.utils.showToast
import kotlinx.android.synthetic.main.activity_fullscreen.*
import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein
import org.kodein.di.generic.instance
class FullscreenActivity : AppCompatActivity() {
companion object {
lateinit var fragmentManagerLogin : FragmentManager
}
class FullscreenActivity : AppCompatActivity(), KodeinAware {
override val kodein by kodein()
private val factory by instance<AuthViewModelFactory>()
private val viewModel: AuthViewModel by viewModels { factory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fullscreen)
auth = FirebaseAuth.getInstance()
viewModel.operationState.observe(this, Observer {
if (it){
progress_circular.show()
}else{
progress_circular.hide()
}
})
fragmentManagerLogin = supportFragmentManager
fragmentManagerLogin.beginTransaction().replace(
R.id.container,
SplashFragment()
).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit()
viewModel.operationResult.observe(this, Observer {
it.getContentIfNotHandled()?.let { message ->
showToast(message)
}
})
}
// If the there is more than 1 fragment in the backstack
// Go back to previous fragment
// Or exit
override fun onBackPressed() {
super.onBackPressed()
if (fragmentManagerLogin.fragments.size > 1) {
fragmentManagerLogin.popBackStack()
container.childFragmentManager.backStackEntryCount.let {
if (it > 0) {
super.onBackPressed()
} else {
finish()
}
}
}
}

View File

@@ -1,28 +1,26 @@
package com.appttude.h_mal.days_left.ui.login
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.ui.login.FullscreenActivity.Companion.fragmentManagerLogin
import com.appttude.h_mal.days_left.ui.main.MainActivity
import com.appttude.h_mal.days_left.utils.afterTextChanged
import com.appttude.h_mal.days_left.utils.navigateTo
import kotlinx.android.synthetic.main.fragment_login.*
import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein
import org.kodein.di.generic.instance
class LoginFragment : Fragment(), LoginCallback, KodeinAware {
class LoginFragment : Fragment(), KodeinAware {
override val kodein by kodein()
private val factory by instance<AuthViewModelFactory>()
private val viewModel: AuthViewModel by viewModels{ factory }
private val viewModel: AuthViewModel by viewModels { factory }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@@ -34,57 +32,46 @@ class LoginFragment : Fragment(), LoginCallback, KodeinAware {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
password.setOnEditorActionListener(TextView.OnEditorActionListener { textView, id, keyEvent ->
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
viewModel.onClickLogin(textView)
return@OnEditorActionListener true
password.apply {
setOnEditorActionListener { textView, id, _ ->
if (id == EditorInfo.IME_ACTION_DONE) {
viewModel.login(
submission_et.text.toString(),
textView.text.toString()
)
return@setOnEditorActionListener true
}
false
}
afterTextChanged {
til_password.error = null
}
}
submission_et.apply {
afterTextChanged {
til_submission.error = null
}
}
viewModel.loginFormState.observe(viewLifecycleOwner, Observer {
val loginState = it ?: return@Observer
if (loginState.usernameError != null) {
til_submission.error = getString(loginState.usernameError)
}
if (loginState.passwordError != null) {
til_password.error = getString(loginState.passwordError)
}
false
})
forgot.setOnClickListener{
fragmentManagerLogin.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_in)
.replace(R.id.container, ForgotPassword())
.addToBackStack("forgot_pw").commit()
}
register_button.setOnClickListener {
fragmentManagerLogin.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_in)
.replace(R.id.container, Register())
.addToBackStack("register")
.commit()
}
}
override fun onStarted() {
login_progress.visibility = View.VISIBLE
}
override fun onEmailInvalid() {
email.error = getString(R.string.error_invalid_email)
email.requestFocus()
login_progress.visibility = View.GONE
}
override fun onPasswordInvalid() {
password.error = getString(R.string.error_invalid_password)
password.requestFocus()
login_progress.visibility = View.GONE
}
override fun onSuccess() {
login_progress.visibility = View.GONE
Intent(context, MainActivity::class.java).apply {
startActivity(this)
activity?.finish()
}
}
override fun onFailure(message: String) {
Toast.makeText(context,message,Toast.LENGTH_LONG).show()
login_progress.visibility = View.GONE
viewModel.operationLogin.observe(viewLifecycleOwner, Observer {
it.getContentIfNotHandled()?.let {
view.navigateTo(R.id.LoginTo_mainActivity)
}
})
}
}

View File

@@ -0,0 +1,45 @@
package com.appttude.h_mal.days_left.ui.login
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.utils.navigateTo
import kotlinx.android.synthetic.main.fragment_login_home.*
/**
* A simple [Fragment] subclass.
* create an instance of this fragment.
*/
class LoginHomeFragment : Fragment(), View.OnClickListener {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_login_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sign_up.setOnClickListener(this)
submission_button.setOnClickListener(this)
}
override fun onClick(v: View) {
when(v.id){
R.id.sign_up -> R.id.to_registrationNicknameFragment
R.id.submission_button -> R.id.splash_to_loginFragment
else ->{
null
}
}?.let {
view?.navigateTo(it)
}
}
}

View File

@@ -1,73 +0,0 @@
package com.appttude.h_mal.days_left.ui.login
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProviders
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.ui.main.MainActivity
import kotlinx.android.synthetic.main.fragment_register.*
class Register : Fragment(), RegisterCallBack {
val viewModel = ViewModelProviders.of(this).get(RegisterViewModel::class.java)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_register, container, false)
}
override fun onStarted() {
pb.visibility = View.VISIBLE
}
override fun onNameInvalid() {
pb.visibility = View.GONE
name_register.error = getString(R.string.error_invalid_email)
name_register.requestFocus()
}
override fun onEmailInvalid() {
pb.visibility = View.GONE
email_register.error = getString(R.string.error_invalid_email)
email_register.requestFocus()
}
override fun onPasswordInvalid(id: Int) {
pb.visibility = View.GONE
when(id){
0 -> password_top.error = getString(R.string.error_invalid_password)
1 -> password_top.error = getString(R.string.no_match_password)
}
password_top.requestFocus()
}
override fun onPasswordTwoInvalid() {
pb.visibility = View.GONE
password_bottom.error = getString(R.string.error_invalid_password)
password_bottom.requestFocus()
}
override fun onSuccess() {
pb.visibility = View.GONE
Intent(context, MainActivity::class.java).apply {
this.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(this)
activity?.finish()
}
}
override fun onFailure(message: String) {
pb.visibility = View.GONE
Toast.makeText(context,message,Toast.LENGTH_LONG).show()
}
}

View File

@@ -0,0 +1,82 @@
package com.appttude.h_mal.days_left.ui.login.registration
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.ui.login.AuthViewModel
import com.appttude.h_mal.days_left.utils.afterTextChanged
import com.appttude.h_mal.days_left.utils.isEmailValid
import com.appttude.h_mal.days_left.utils.navigateTo
import kotlinx.android.synthetic.main.fragment_registration_four.*
import kotlinx.android.synthetic.main.fragment_registration_four.submission_button
import kotlinx.android.synthetic.main.fragment_registration_four.submission_et
import kotlinx.android.synthetic.main.fragment_registration_four.til_submission
import kotlinx.android.synthetic.main.fragment_registration_two.*
/**
* A simple [Fragment] subclass.
* Use the [RegistrationFourFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class RegistrationConfirmationFragment : Fragment() {
val viewmodel: AuthViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_registration_four, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
submission_et.apply {
afterTextChanged {
til_submission.error = null
}
setOnEditorActionListener { textView, id, _ ->
if (id == EditorInfo.IME_ACTION_DONE) {
next()
return@setOnEditorActionListener true
}
false
}
}
submission_button.setOnClickListener { next() }
viewmodel.operationRegister.observe(viewLifecycleOwner, Observer {
it.getContentIfNotHandled()?.let {
view.navigateTo(R.id.registration_to_mainActivity)
}
})
}
fun next(){
val password = submission_et.text.toString()
val regArgs =
RegistrationConfirmationFragmentArgs.fromBundle(requireArguments()).regThreeArgs
val previousPassword = regArgs.password!!
if (password != previousPassword){
til_submission.error = "Passwords do not match"
return
}
regArgs.run {
viewmodel.register(
email!!, previousPassword, name!!
)
}
}
}

View File

@@ -0,0 +1,72 @@
package com.appttude.h_mal.days_left.ui.login.registration
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.models.registration.RegistrationArgs
import com.appttude.h_mal.days_left.utils.afterTextChanged
import com.appttude.h_mal.days_left.utils.isEmailValid
import com.appttude.h_mal.days_left.utils.isNameValid
import com.appttude.h_mal.days_left.utils.navigateTo
import kotlinx.android.synthetic.main.fragment_registration_two.*
/**
* A simple [Fragment] subclass.
* Use the [RegistrationEmailFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class RegistrationEmailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_registration_two, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
submission_et.apply {
afterTextChanged {
til_submission.error = null
}
setOnEditorActionListener { textView, id, _ ->
if (id == EditorInfo.IME_ACTION_DONE) {
next()
return@setOnEditorActionListener true
}
false
}
}
submission_button.setOnClickListener { next() }
}
fun next(){
val email = submission_et.text.toString()
if (!isEmailValid(email)){
til_submission.error = "Enter a valid Email"
return
}
val regArgs =
RegistrationEmailFragmentArgs.fromBundle(requireArguments()).regOneArgs
regArgs.email = email
val action =
RegistrationEmailFragmentDirections.toRegistrationPasswordFragment(regArgs)
view?.navigateTo(action)
}
}

View File

@@ -0,0 +1,67 @@
package com.appttude.h_mal.days_left.ui.login.registration
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.models.registration.RegistrationArgs
import com.appttude.h_mal.days_left.utils.afterTextChanged
import com.appttude.h_mal.days_left.utils.isNameValid
import com.appttude.h_mal.days_left.utils.navigateTo
import kotlinx.android.synthetic.main.fragment_nickname.*
/**
* A simple [Fragment] subclass.
* Use the [RegistrationNicknameFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class RegistrationNicknameFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_nickname, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
submission_et.apply {
afterTextChanged {
til_submission.error = null
}
setOnEditorActionListener { textView, id, _ ->
if (id == EditorInfo.IME_ACTION_DONE) {
next()
return@setOnEditorActionListener true
}
false
}
}
submission_button.setOnClickListener { next() }
}
fun next(){
val name = submission_et.text.toString()
if (!isNameValid(name)){
til_submission.error = "Enter a valid Nickname"
return
}
val regArgs = RegistrationArgs(name)
val action =
RegistrationNicknameFragmentDirections.toRegistrationEmail(regArgs)
view?.navigateTo(action)
}
}

View File

@@ -0,0 +1,71 @@
package com.appttude.h_mal.days_left.ui.login.registration
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import com.appttude.h_mal.days_left.R
import com.appttude.h_mal.days_left.utils.afterTextChanged
import com.appttude.h_mal.days_left.utils.isEmailValid
import com.appttude.h_mal.days_left.utils.isPasswordValid
import com.appttude.h_mal.days_left.utils.navigateTo
import kotlinx.android.synthetic.main.fragment_registration_password.*
/**
* A simple [Fragment] subclass.
* Use the [RegistrationPasswordFragment] factory method to
* create an instance of this fragment.
*/
class RegistrationPasswordFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_registration_password, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
submission_et.apply {
afterTextChanged {
til_submission.error = null
}
setOnEditorActionListener { textView, id, _ ->
if (id == EditorInfo.IME_ACTION_DONE) {
next()
return@setOnEditorActionListener true
}
false
}
}
submission_button.setOnClickListener { next() }
}
fun next(){
val password = submission_et.text.toString()
if (!isPasswordValid(password)){
til_submission.error = "Enter a valid Email"
return
}
val regArgs =
RegistrationPasswordFragmentArgs.fromBundle(requireArguments()).regTwoArgs
regArgs.password = password
val action =
RegistrationPasswordFragmentDirections.toRegistrationConfirmationFragment(regArgs)
view?.navigateTo(action)
}
}

View File

@@ -9,6 +9,8 @@ import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.fragment.app.FragmentManager
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.days_left.*
import com.appttude.h_mal.days_left.ui.login.FullscreenActivity
import com.appttude.h_mal.days_left.FirebaseClass.Companion.SHIFT_FIREBASE
@@ -26,11 +28,13 @@ import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.nav_header_main.view.*
import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein
import org.kodein.di.android.x.kodein
import org.kodein.di.generic.instance
class MainActivity : AppCompatActivity(), KodeinAware {
override val kodein by kodein()
private val factory by instance<MainViewModelFactory>()
private val factory2 by instance<ShiftsViewModelFactory>()
val viewModel: MainViewModel by viewModels{ factory }
companion object{
@@ -42,6 +46,8 @@ class MainActivity : AppCompatActivity(), KodeinAware {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_drawer_main)
ViewModelProvider(this, factory2).get(ShiftsViewModel::class.java)
//setup backstack change listener
supportFragmentManager.addOnBackStackChangedListener(backStackChangedListener)
//set toolbar

View File

@@ -2,7 +2,7 @@ package com.appttude.h_mal.days_left.ui.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.days_left.ui.login.UserRepository
import com.appttude.h_mal.days_left.data.repository.UserRepository
/**
* ViewModel provider factory to instantiate LoginViewModel.

View File

@@ -1,14 +1,88 @@
package com.appttude.h_mal.days_left.ui.main
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import com.appttude.h_mal.days_left.FirebaseDatabase
import com.appttude.h_mal.days_left.firebaseLiveData.FirebaseQueryLiveData
import com.appttude.h_mal.days_left.data.firebase.FirebaseDataSource
import com.appttude.h_mal.days_left.firebaseUtils.FirebaseQueryLiveData
import com.appttude.h_mal.days_left.models.ShiftObject
import com.google.firebase.database.DataSnapshot
import java.util.HashSet
class ShiftsViewModel(
val database: FirebaseDatabase
val database: FirebaseDataSource
): ViewModel() {
private val shifts = database.allShifts()
val shifts = database.allShifts()
val shiftData = FirebaseQueryLiveData(shifts)
val liveData = MutableLiveData<Triple<List<ShiftObject?>, Int, IntArray>>()
init {
shiftData.observeForever {
val list = it.mapSnapToShiftList()
val uniqueEntries = countDistinct(list)
val shiftTypeCount = countShiftType(list)
liveData.value = Triple(
list,
uniqueEntries,
shiftTypeCount
)
}
}
private fun countDistinct(list: List<ShiftObject?>): Int {
return list.distinctBy { it?.shiftDate }.size
}
private fun countShiftType(list: List<ShiftObject?>): IntArray {
val i = list.filter { it?.taskObject?.workType.equals("Hourly") }.size
val j = list.size - i
return intArrayOf(i, j)
}
private fun DataSnapshot.mapSnapToShiftList(): List<ShiftObject?> = this.children.map {
it.getValue(ShiftObject::class.java)
}
// private fun calculateAccumulatedPay(type: Int): Float {
// var pay = 0f
//
// for (shiftObject in shiftList) {
// shiftObject?.let {
// when (type){
// 0 -> {
// if (it.taskObject?.workType == "Hourly") {
// pay += it.taskObject?.rate?.times((it.timeObject!!.hours - it.timeObject!!.breakEpoch))
// ?: pay
// }
// }
// 1 -> {
// if (it.taskObject?.workType == "Piece Rate") {
// pay += it.taskObject?.rate?.times(it.unitsCount!!) ?: pay
// }
// }
// else -> {
// if (it.taskObject?.workType == "Hourly") {
// pay += it.taskObject?.rate?.times((it.timeObject!!.hours - it.timeObject!!.breakEpoch))
// ?: pay
// } else {
// pay += it.taskObject?.rate?.times(it.unitsCount!!) ?: pay
// }
// }
// }
// }
// }
//
// return pay
// }
private fun totalPay(): Float{
return 0.0f
}
}

View File

@@ -2,14 +2,14 @@ package com.appttude.h_mal.days_left.ui.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.days_left.FirebaseDatabase
import com.appttude.h_mal.days_left.data.firebase.FirebaseDataSource
/**
* ViewModel provider factory to instantiate LoginViewModel.
* Required given LoginViewModel has a non-empty constructor
*/
class ShiftsViewModelFactory(
private val firebaseDatabase: FirebaseDatabase
private val firebaseDatabase: FirebaseDataSource
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")

View File

@@ -4,24 +4,31 @@ import android.app.AlertDialog
import android.content.DialogInterface
import android.content.DialogInterface.BUTTON_POSITIVE
import android.content.Intent
import android.database.DataSetObserver
import android.os.Bundle
import android.view.*
import android.widget.AdapterView
import androidx.fragment.app.activityViewModels
import com.appttude.h_mal.days_left.*
import com.appttude.h_mal.days_left.ui.main.MainActivity
import com.appttude.h_mal.days_left.CustomDialog.Companion.dateSelectionFrom
import com.appttude.h_mal.days_left.CustomDialog.Companion.dateSelectionTo
import com.appttude.h_mal.days_left.ui.main.MainActivity.Companion.ref
import com.appttude.h_mal.days_left.FirebaseClass.Companion.SHIFT_ID
import com.appttude.h_mal.days_left.models.AbnObject
import com.appttude.h_mal.days_left.models.ShiftObject
import com.appttude.h_mal.days_left.ui.main.MainActivity
import com.appttude.h_mal.days_left.ui.main.MainActivity.Companion.ref
import com.appttude.h_mal.days_left.ui.main.ShiftsViewModel
import com.firebase.ui.database.FirebaseRecyclerAdapter
import com.google.firebase.FirebaseOptions
import com.google.firebase.database.Query
import kotlinx.android.synthetic.main.dialog_previous_abns_used.view.*
import kotlinx.android.synthetic.main.fragment_list.*
import java.util.*
import kotlin.collections.ArrayList
class FragmentList : androidx.fragment.app.Fragment() {
val viewModel: ShiftsViewModel by activityViewModels()
lateinit var fireAdapter: FireAdapter
@@ -35,49 +42,49 @@ class FragmentList : androidx.fragment.app.Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//set custom firebase adapter on listview
fireAdapter = FireAdapter(
activity,
ShiftObject::class.java,
R.layout.list_item,
ref
viewModel.shifts
)
page_two_list.adapter = fireAdapter
page_two_list.setOnItemClickListener(object : AdapterView.OnItemClickListener{
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val refId = fireAdapter.getId(position)
val intent = Intent(activity, AddShiftActivity::class.java)
intent.putExtra(SHIFT_ID, refId)
startActivity(intent)
}
})
page_two_list.apply {
adapter = fireAdapter
page_two_list.setOnItemLongClickListener(object : AdapterView.OnItemLongClickListener{
override fun onItemLongClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long): Boolean {
val builder = AlertDialog.Builder(context)
builder.setTitle("Are you sure you want to delete?")
builder.setNegativeButton(android.R.string.no, null)
builder.setPositiveButton(
android.R.string.yes
) { dialog, which ->
fireAdapter.getRef(position).removeValue()
onItemClickListener =
AdapterView.OnItemClickListener { _, _, position, _ ->
val refId = fireAdapter.getId(position)
val intent = Intent(activity, AddShiftActivity::class.java)
intent.putExtra(SHIFT_ID, refId)
startActivity(intent)
}
builder.create().show()
return false
}
})
onItemLongClickListener =
AdapterView.OnItemLongClickListener { _, _, position, _ ->
AlertDialog.Builder(context).apply {
setTitle("Are you sure you want to delete?")
setNegativeButton(android.R.string.no, null)
setPositiveButton(android.R.string.yes) { _, _ ->
fireAdapter.getRef(position).removeValue()
}
create().show()
}
true
}
}
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_list_fragment, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.getItemId()) {
when (item.itemId) {
R.id.app_bar_filter -> {
filterData()
return false
@@ -91,122 +98,134 @@ class FragmentList : androidx.fragment.app.Fragment() {
}
private fun sortData() {
val grpname = arrayOf("Name", "Date Added", "Date of shift")
val groupName = arrayOf("Name", "Date Added", "Date of shift")
val checkedItem = -1
val alt_bld = AlertDialog.Builder(context)
alt_bld.setTitle("Sort by:")
alt_bld.setSingleChoiceItems(grpname, checkedItem) { dialog, item ->
when (item) {
0 -> {
val q1 = ref.orderByChild("abnObject/companyName").equalTo("GREEN CLOUD NURSERY")
fireAdapter = FireAdapter(
AlertDialog.Builder(context).apply {
setTitle("Sort by:")
setSingleChoiceItems(groupName, checkedItem) { dialog, item ->
when (item) {
0 -> {
val q1 =
viewModel.shifts.orderByChild("abnObject/companyName").equalTo("GREEN CLOUD NURSERY")
fireAdapter.notifyDataSetChanged()
// fireAdapter = FireAdapter(
// activity,
// ShiftObject::class.java,
// R.layout.list_item,
// q1
// )
}
1 -> fireAdapter = FireAdapter(
activity,
ShiftObject::class.java,
R.layout.list_item,
q1
ref.orderByChild("dateTimeAdded")
)
2 -> fireAdapter = FireAdapter(
activity,
ShiftObject::class.java,
R.layout.list_item,
ref.orderByChild("shiftDate")
)
}
1 -> fireAdapter = FireAdapter(
activity,
ShiftObject::class.java,
R.layout.list_item,
ref.orderByChild("dateTimeAdded")
)
2 -> fireAdapter = FireAdapter(
activity,
ShiftObject::class.java,
R.layout.list_item,
ref.orderByChild("shiftDate")
)
page_two_list.adapter = fireAdapter
dialog.dismiss()
}
page_two_list.adapter = fireAdapter
dialog.dismiss()
create().show()
}
alt_bld.create().show()
}
private fun filterData(){
private fun filterData() {
val groupName = arrayOf("Name", "Date Added", "Shift Type")
val checkedItem = -1
val builder = AlertDialog.Builder(context)
builder.setTitle("Filter by:")
builder.setSingleChoiceItems(groupName,checkedItem, DialogInterface.OnClickListener{dialog, item ->
dialog.dismiss()
builder.setSingleChoiceItems(
groupName,
checkedItem,
DialogInterface.OnClickListener { dialog, item ->
dialog.dismiss()
when(item) {
0 -> {
val dialogBuilder = AlertDialog.Builder(context)
dialogBuilder.setTitle("Select Employer:")
//get layout
val dialogView = View.inflate(context,
R.layout.dialog_previous_abns_used, null)
//hide button
dialogView.button_list_dialog.visibility = View.GONE
//get listview
val listView = dialogView.list_item_list_dialog
//get unique abn objects
val uniqueAbnObjects= turnToUniqueAbnObject(MainActivity.shiftList)
//populate list in view
listView.adapter =
AbnListAdapter(
context!!,
uniqueAbnObjects as MutableList<AbnObject>
when (item) {
0 -> {
val dialogBuilder = AlertDialog.Builder(context)
dialogBuilder.setTitle("Select Employer:")
//get layout
val dialogView = View.inflate(
context,
R.layout.dialog_previous_abns_used, null
)
//on item click listener
listView.setOnItemClickListener(AdapterView.OnItemClickListener{parent, view, position, id ->
applyFilter(uniqueAbnObjects[position].abn!!,null)
})
//set view on dialog
dialogBuilder.setView(dialogView)
dialogBuilder.create().show()
}
1 -> {
val customDialog =
CustomDialog(context!!)
customDialog.setButton(BUTTON_POSITIVE, getContext()?.getString(android.R.string.yes),
DialogInterface.OnClickListener{ dialogNew, which ->
//interface results back
if (dateSelectionFrom != dateSelectionTo) {
applyFilter(dateSelectionFrom, dateSelectionTo)
}
customDialog.dismiss()
//hide button
dialogView.button_list_dialog.visibility = View.GONE
//get listview
val listView = dialogView.list_item_list_dialog
//get unique abn objects
val uniqueAbnObjects = turnToUniqueAbnObject(MainActivity.shiftList)
//populate list in view
listView.adapter =
AbnListAdapter(
requireContext(),
uniqueAbnObjects as MutableList<AbnObject>
)
//on item click listener
listView.setOnItemClickListener(AdapterView.OnItemClickListener { parent, view, position, id ->
applyFilter(uniqueAbnObjects[position].abn!!, null)
})
//set view on dialog
dialogBuilder.setView(dialogView)
customDialog.create()
}
2 -> {
val typeDialog = AlertDialog.Builder(context)
val typeString = arrayOf("Hourly", "Piece Rate")
dialogBuilder.create().show()
typeDialog.setSingleChoiceItems(
arrayOf("Hourly", "Piece Rate"), -1
) { dialog, which ->
val q1 = ref.orderByChild("taskObject/workType").equalTo(typeString[which])
fireAdapter = FireAdapter(
activity,
ShiftObject::class.java,
R.layout.list_item,
q1
)
page_two_list.adapter = fireAdapter
}
typeDialog.create().show()
1 -> {
val customDialog =
CustomDialog(requireContext())
customDialog.setButton(BUTTON_POSITIVE,
getContext()?.getString(android.R.string.yes),
DialogInterface.OnClickListener { dialogNew, which ->
//interface results back
if (dateSelectionFrom != dateSelectionTo) {
applyFilter(dateSelectionFrom, dateSelectionTo)
}
customDialog.dismiss()
})
customDialog.create()
}
2 -> {
val typeDialog = AlertDialog.Builder(context)
val typeString = arrayOf("Hourly", "Piece Rate")
typeDialog.setSingleChoiceItems(
arrayOf("Hourly", "Piece Rate"), -1
) { dialog, which ->
val q1 =
ref.orderByChild("taskObject/workType").equalTo(typeString[which])
fireAdapter = FireAdapter(
activity,
ShiftObject::class.java,
R.layout.list_item,
q1
)
page_two_list.adapter = fireAdapter
}
typeDialog.create().show()
}
}
}
})
})
}
fun turnToUniqueAbnObject(shifts : ArrayList<ShiftObject>): List<AbnObject>{
fun turnToUniqueAbnObject(shifts: ArrayList<ShiftObject>): List<AbnObject> {
val abnList = mutableListOf<AbnObject>()
shifts.forEach{shiftObject ->
shifts.forEach { shiftObject ->
shiftObject.abnObject?.let { abnList.add(it) }
}

View File

@@ -0,0 +1,25 @@
package com.appttude.h_mal.days_left.utils
import android.text.TextUtils
import java.util.regex.Pattern
// Password must contain 8-16 characters,
// including at least 1 upper case letter and at least 1 numerical digit.
fun isPasswordValid(password: String?): Boolean {
return password?.let {
Pattern.compile("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}\$").matcher(it).find()
} ?: false
}
fun isNameValid(name: String?): Boolean {
return name?.let {
Pattern.compile("[a-zA-Z]{4,}(?: [a-zA-Z]+){0,2}\$").matcher(it).find()
} ?: false
}
fun isEmailValid(email: String?): Boolean {
return email?.let {
android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
} ?: false
}

View File

@@ -11,6 +11,8 @@ import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.navigation.NavDirections
import androidx.navigation.Navigation
import com.squareup.picasso.Picasso
import java.lang.Exception
@@ -35,18 +37,18 @@ fun Context.hideKeyboard(view: View?) {
inputMethodManager.hideSoftInputFromWindow(view?.windowToken, 0)
}
//fun View.navigateTo(navigationId: Int) {
// try {
// Navigation.findNavController(this).navigate(navigationId)
// }catch (e: IllegalArgumentException){
// e.printStackTrace()
// }
//
//}
//
//fun View.navigateTo(navDirections: NavDirections) {
// Navigation.findNavController(this).navigate(navDirections)
//}
fun View.navigateTo(navigationId: Int) {
try {
Navigation.findNavController(this).navigate(navigationId)
}catch (e: IllegalArgumentException){
e.printStackTrace()
}
}
fun View.navigateTo(navDirections: NavDirections) {
Navigation.findNavController(this).navigate(navDirections)
}
fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
this.addTextChangedListener(object : TextWatcher {

View File

@@ -0,0 +1,5 @@
package com.appttude.h_mal.days_left.utils
import com.google.gson.reflect.TypeToken
inline fun <reified T> genericType() = object: TypeToken<T>() {}.type

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -1,14 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.appttude.h_mal.days_left.Login.FullscreenActivity">
<FrameLayout
tools:context=".ui.login.FullscreenActivity">
<fragment
android:id="@+id/container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container">
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/auth_navigation"
tools:context=".ui.auth.AuthActivity" />
</FrameLayout>
<ProgressBar
android:id="@+id/progress_circular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
</FrameLayout>
android:visibility="gone"
android:elevation="0.2dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
@@ -9,21 +9,32 @@
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".AddItems.AddEmployerFragment">
tools:context=".AddItemActivity">
<ProgressBar
android:id="@+id/spinning_pb"
android:visibility="gone"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/empty_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<ImageView
android:layout_width="match_parent"
@@ -45,23 +56,16 @@
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/search_layout"
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/search_button"
android:orientation="vertical">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="16dp"
android:layout_weight="1">
</ListView>
</LinearLayout>
android:layout_weight="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,9 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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="com.appttude.h_mal.days_left.Login.BlankFragment"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:background="@drawable/splash">
</FrameLayout>
<ImageView
android:id="@+id/logo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="24dp"
android:adjustViewBounds="true"
android:src="@drawable/farmicon_round"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.2"
app:layout_constraintWidth_percent=".40"/>
<TextView
android:id="@+id/app_name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="@id/logo"
app:layout_constraintRight_toRightOf="@id/logo"
app:layout_constraintBottom_toTopOf="@id/logo"
android:gravity="center"
android:textStyle="bold"
android:textSize="36sp"
android:textColor="@android:color/white"
android:text="@string/app_name"
android:layout_marginBottom="12dp"
app:layout_constraintWidth_percent=".40"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,66 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:background="@drawable/spash_drawable"
tools:context="com.appttude.h_mal.days_left.Login.ForgotPassword">
android:background="@android:color/white"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.login.FullscreenActivity">
<RelativeLayout
<TextView
android:id="@+id/forgotPassword"
style="@style/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
android:text="@string/forgot_password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/forgotPassword_subtitle_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:text="@string/forgot_password_subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/forgotPassword" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_submission"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/forgotPassword_subtitle_tv">
<EditText
android:id="@+id/submission_et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.cardview.widget.CardView
android:id="@+id/submission_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".User.RegisterActivity">
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="64dp"
android:backgroundTint="@color/colorPrimary"
android:enabled="false"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/pb"
android:visibility="gone"/>
<LinearLayout
<TextView
android:id="@+id/submission_button_label"
style="@style/button_inner_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:background="@drawable/secondcardview"
android:orientation="vertical"
android:padding="12dp">
android:text="@string/action_submit"
android:textColor="@android:color/white" />
</androidx.cardview.widget.CardView>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="6dp">
<EditText
android:id="@+id/reset_pw"
style="@style/edittexttheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Enter Emil Address"
android:inputType="textEmailAddress"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<Button
android:id="@+id/reset_pw_sign_up"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="12dp"
android:background="@drawable/cardviewoutline"
android:text="Sign Up"
android:textColor="#91ddff"
android:textStyle="bold" />
</RelativeLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,149 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center_horizontal"
android:background="@android:color/white"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:background="@drawable/spash_drawable"
tools:context="com.appttude.h_mal.days_left.Login.FullscreenActivity">
tools:context=".ui.login.FullscreenActivity">
<!-- Login progress -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/email_login_form">
<LinearLayout
android:id="@+id/fields_holder"
<TextView
android:id="@+id/login_title_tv"
style="@style/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
android:text="@string/login_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/login_subtitle_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:text="@string/login_subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_title_tv" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_submission"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_subtitle_tv">
<EditText
android:id="@+id/submission_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:orientation="vertical">
android:layout_height="match_parent"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.cardview.widget.CardView
style="@style/cardview_theme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardBackgroundColor="@color/two">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/til_submission">
<AutoCompleteTextView
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:layout_marginRight="18dp"
android:autofilledHighlight="@android:color/transparent"
android:ems="10"
android:hint="@string/prompt_email"
android:importantForAutofill="yes"
android:inputType="textEmailAddress"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true"
android:textColorHighlight="@color/three" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/prompt_password"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
style="@style/cardview_theme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/two">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:layout_marginRight="18dp"
android:autofilledHighlight="@android:color/transparent"
android:ems="10"
android:hint="@string/prompt_password"
android:imeActionId="6"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:importantForAutofill="yes"
android:inputType="textPassword"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true"
android:textColorHighlight="@color/three" />
<!--</android.support.design.widget.TextInputLayout>-->
</androidx.cardview.widget.CardView>
<LinearLayout
android:id="@+id/register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginRight="18dp"
android:layout_marginLeft="18dp">
<TextView
android:id="@+id/forgot"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_weight="1"
android:shadowColor="@color/five"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="1"
android:text="Forgot Password"
android:textColor="@color/one"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/register_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:paddingRight="2dp"
android:shadowColor="@color/five"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="1"
android:text="Sign up"
android:textColor="@color/one"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/email_sign_in_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:background="@drawable/cardviewoutline"
android:text="@string/action_sign_in_short"
android:textColor="@color/three"
android:textStyle="bold" />
</RelativeLayout>
<ProgressBar
android:id="@+id/login_progress"
style="?android:attr/progressBarStyleLarge"
<!--todo: reactivate forgot password-->
<TextView
android:id="@+id/forgotPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginBottom="8dp"
android:visibility="gone" />
android:layout_marginTop="12dp"
android:text="@string/forgot_password"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/submission_button" />
<androidx.cardview.widget.CardView
android:id="@+id/submission_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="64dp"
android:backgroundTint="@color/colorPrimary"
android:enabled="false"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9">
</RelativeLayout>
<TextView
android:id="@+id/submission_button_label"
style="@style/button_inner_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/action_sign_in_short"
android:textColor="@android:color/white" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,108 @@
<?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/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:background="@android:color/white"
tools:context=".ui.login.FullscreenActivity">
<ImageView
android:id="@+id/prova_logo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="24dp"
android:adjustViewBounds="true"
android:src="@drawable/farmicon_round"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent=".60" />
<TextView
android:id="@+id/prova_title_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:selectAllOnFocus="true"
android:text="@string/welcome_title"
style="@style/title_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/prova_logo" />
<TextView
android:id="@+id/prova_subtitle_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:selectAllOnFocus="true"
android:text="@string/welcome_subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/prova_title_tv" />
<androidx.cardview.widget.CardView
android:id="@+id/sign_up"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:backgroundTint="#D5D5D5"
app:cardCornerRadius="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/prova_subtitle_tv">
<TextView
android:id="@+id/sign_up_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:gravity="center"
android:text="@string/register"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/submission_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:backgroundTint="@color/colorPrimary"
android:selectAllOnFocus="true"
app:cardCornerRadius="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sign_up">
<TextView
android:id="@+id/login_button_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:gravity="center"
android:text="@string/login"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,90 @@
<?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:background="@android:color/white"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.login.FullscreenActivity">
<TextView
android:id="@+id/submission_title_tv"
style="@style/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Enter Your Nickname" />
<TextView
android:id="@+id/submission_subtitle_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:lines="3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submission_title_tv"
android:text="Please enter your first name or the name you prefer to be called by." />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_submission"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submission_subtitle_tv">
<EditText
android:id="@+id/submission_et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/prompt_nickname"
android:inputType="textPersonName"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.cardview.widget.CardView
android:id="@+id/submission_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="64dp"
android:backgroundTint="@color/colorPrimary"
android:enabled="false"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9">
<TextView
android:id="@+id/submission_button_label"
style="@style/button_inner_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/next"
android:textColor="@android:color/white"
tools:text="Next" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,24 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
android:background="@drawable/spash_drawable"
tools:context="com.appttude.h_mal.days_left.Login.FullscreenActivity">
tools:context=".ui.login.FullscreenActivity">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/pb"
android:visibility="gone"/>
android:elevation="0.2dp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:layout_centerInParent="true">
android:layout_centerInParent="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent">
<androidx.cardview.widget.CardView
style="@style/cardview_theme"
@@ -136,4 +147,4 @@
android:textColor="#91ddff"
android:textStyle="bold" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,90 @@
<?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:background="@android:color/white"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.login.FullscreenActivity">
<TextView
android:id="@+id/submission_title_tv"
style="@style/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Enter Your Password" />
<TextView
android:id="@+id/submission_subtitle_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:lines="3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submission_title_tv"
android:text="Please enter your password again. This is to confirm your passwords match." />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_submission"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submission_subtitle_tv">
<EditText
android:id="@+id/submission_et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/prompt_password"
android:inputType="textPassword"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.cardview.widget.CardView
android:id="@+id/submission_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="64dp"
android:backgroundTint="@color/colorPrimary"
android:enabled="false"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9">
<TextView
android:id="@+id/submission_button_label"
style="@style/button_inner_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/next"
android:textColor="@android:color/white"
tools:text="Next" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,90 @@
<?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:background="@android:color/white"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.login.FullscreenActivity">
<TextView
android:id="@+id/submission_title_tv"
style="@style/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Enter Your Password" />
<TextView
android:id="@+id/submission_subtitle_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:lines="3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submission_title_tv"
android:text="Please enter your password/ This will be used for signing in" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_submission"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submission_subtitle_tv">
<EditText
android:id="@+id/submission_et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/prompt_password"
android:inputType="textPassword"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.cardview.widget.CardView
android:id="@+id/submission_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="64dp"
android:backgroundTint="@color/colorPrimary"
android:enabled="false"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9">
<TextView
android:id="@+id/submission_button_label"
style="@style/button_inner_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/next"
android:textColor="@android:color/white"
tools:text="Next" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,90 @@
<?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:background="@android:color/white"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.login.FullscreenActivity">
<TextView
android:id="@+id/submission_title_tv"
style="@style/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Enter Your Email address" />
<TextView
android:id="@+id/submission_subtitle_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:lines="3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submission_title_tv"
android:text="Please enter your email address. This will be used for signing in" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_submission"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/submission_subtitle_tv">
<EditText
android:id="@+id/submission_et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.cardview.widget.CardView
android:id="@+id/submission_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="64dp"
android:backgroundTint="@color/colorPrimary"
android:enabled="false"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9">
<TextView
android:id="@+id/submission_button_label"
style="@style/button_inner_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/next"
android:textColor="@android:color/white"
tools:text="Next" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<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/auth_navigation"
app:startDestination="@id/loginHomeFragment">
<fragment
android:id="@+id/loginHomeFragment"
android:name="com.appttude.h_mal.days_left.ui.login.LoginHomeFragment"
android:label="LoginHomeFragment"
tools:layout="@layout/fragment_login_home">
<action
android:id="@+id/to_registrationNicknameFragment"
app:destination="@id/registrationNicknameFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit"/>
<action
android:id="@+id/splash_to_loginFragment"
app:destination="@id/loginFragment"
app:launchSingleTop="true"
app:enterAnim="@anim/fragment_fade_enter"
app:exitAnim="@anim/fragment_fade_exit"
app:popEnterAnim="@anim/fragment_fade_enter"
app:popExitAnim="@anim/fragment_fade_exit"/>
</fragment>
<activity
android:id="@+id/mainActivity"
android:name="com.appttude.h_mal.days_left.ui.main.MainActivity"
android:label="activity_main"
tools:layout="@layout/activity_main" />
<fragment
android:id="@+id/loginFragment"
android:name="com.appttude.h_mal.days_left.ui.login.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/LoginTo_mainActivity"
app:destination="@id/mainActivity"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit"/>
<action
android:id="@+id/action_loginFragment_to_forgotPassword"
app:destination="@id/forgotPassword"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit"/>
</fragment>
<fragment
android:id="@+id/forgotPassword"
android:name="com.appttude.h_mal.days_left.ui.login.ForgotPassword"
android:label="ForgotPassword"
tools:layout="@layout/fragment_forgot_password"/>
<fragment
android:id="@+id/registrationNicknameFragment"
android:name="com.appttude.h_mal.days_left.ui.login.registration.RegistrationNicknameFragment"
android:label="RegistrationNicknameFragment"
tools:layout="@layout/fragment_nickname">
<action
android:id="@+id/to_registrationEmail"
app:destination="@id/registrationEmailFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit"/>
</fragment>
<fragment
android:id="@+id/registrationEmailFragment"
android:name="com.appttude.h_mal.days_left.ui.login.registration.RegistrationEmailFragment"
android:label="RegistrationEmailFragment"
tools:layout="@layout/fragment_registration_two">
<action
android:id="@+id/to_registrationPasswordFragment"
app:destination="@id/registrationPasswordFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit"/>
<argument
android:name="regOneArgs"
app:argType="com.appttude.h_mal.days_left.models.registration.RegistrationArgs" />
</fragment>
<fragment
android:id="@+id/registrationPasswordFragment"
android:name="com.appttude.h_mal.days_left.ui.login.registration.RegistrationPasswordFragment"
android:label="RegistrationPasswordFragment"
tools:layout="@layout/fragment_registration_password">
<action
android:id="@+id/to_registrationConfirmationFragment"
app:destination="@id/registrationConfirmationFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit"/>
<argument
android:name="regTwoArgs"
app:argType="com.appttude.h_mal.days_left.models.registration.RegistrationArgs" />
</fragment>
<fragment
android:id="@+id/registrationConfirmationFragment"
android:name="com.appttude.h_mal.days_left.ui.login.registration.RegistrationConfirmationFragment"
android:label="RegistrationConfirmationFragment"
tools:layout="@layout/fragment_registration_four">
<action
android:id="@+id/registration_to_mainActivity"
app:destination="@id/mainActivity"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit"/>
<argument
android:name="regThreeArgs"
app:argType="com.appttude.h_mal.days_left.models.registration.RegistrationArgs" />
</fragment>
</navigation>

View File

@@ -1,5 +1,5 @@
<resources>
<string name="app_name">Days_Left</string>
<string name="app_name">Days Left</string>
<string name="title_home">Home</string>
<string name="title_dashboard">List</string>
<string name="title_notifications">Tools</string>
@@ -16,7 +16,7 @@
<string name="title_activity_login">Sign in</string>
<!-- Strings related to login -->
<string name="prompt_email">Email</string>
<string name="prompt_password">Password (optional)</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="error_invalid_email">This email address is invalid</string>
@@ -39,6 +39,20 @@
<string name="empty_line_one">List is Empty</string>
<string name="empty_line_two">Search for an Employer</string>
<string name="submit">Submit</string>
<string name="welcome_title">Welcome to Days Left</string>
<string name="welcome_subtitle">Use days left to track your rural work progress, track your work done and compile data needed for visa applications.</string>
<string name="register">Regiseter</string>
<string name="login">Login</string>
<string name="login_title">Login</string>
<string name="login_subtitle">Login to your account with your email address and password.</string>
<string name="forgot_password">Forgot Password</string>
<string name="forgot_password_subtitle">Enter your email address and receive a reset password email.</string>
<string name="action_submit">Submit</string>
<string name="prompt_nickname">Enter a nickname</string>
<string name="next">Next</string>
<string name="invalid_username">Not a valid username</string>
<string name="invalid_password">Password must contain 816 characters, including at least 1 upper case letter and at least 1 numerical digit.</string>
<string name="password_reset_success">Password reset successful</string>
<string-array name="work_type">
<item>Select work type…</item>

View File

@@ -63,4 +63,17 @@
</style>
<style name="title_text" parent="TextAppearance.AppCompat">
<item name="android:textSize">32sp</item>
<item name="android:textStyle">bold</item>
</style>
<style name="button_inner_text" parent="TextAppearance.AppCompat">
<item name="android:gravity">center</item>
<item name="android:textSize">18sp</item>
<item name="android:textStyle">bold</item>
<item name="android:layout_margin">8dp</item>
</style>
</resources>

View File

@@ -12,7 +12,7 @@ buildscript {
classpath 'com.google.gms:google-services:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}