Explorar el Código

add reactive UI logic for LoginFragment

MrOzOn hace 5 años
padre
commit
b9cf2f71f2

+ 1 - 1
app/src/main/res/values/colors.xml

@@ -2,5 +2,5 @@
 <resources>
     <color name="colorPrimary">#6200EE</color>
     <color name="colorPrimaryDark">#3700B3</color>
-    <color name="colorAccent">#03DAC5</color>
+    <color name="colorAccent">#0091EA</color>
 </resources>

+ 43 - 0
feature_auth/src/main/java/com/mrozon/feature_auth/presentation/LoginFragment.kt

@@ -1,13 +1,29 @@
 package com.mrozon.feature_auth.presentation
 
 import android.content.Context
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import androidx.core.widget.addTextChangedListener
+import androidx.core.widget.doOnTextChanged
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.viewModels
+import androidx.lifecycle.Observer
 import androidx.lifecycle.ViewModelProvider
 import com.mrozon.feature_auth.R
 import com.mrozon.feature_auth.databinding.FragmentLoginBinding
 import com.mrozon.feature_auth.di.LoginFragmentComponent
 import com.mrozon.utils.base.BaseFragment
+import com.mrozon.utils.extension.hideKeyboard
+import com.mrozon.utils.extension.offer
+import com.mrozon.utils.extension.visible
+import kotlinx.android.synthetic.main.fragment_login.*
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
+import timber.log.Timber
 import javax.inject.Inject
 
 class LoginFragment : BaseFragment<FragmentLoginBinding>() {
@@ -24,7 +40,34 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
         LoginFragmentComponent.injectFragment(this)
     }
 
+    @ExperimentalCoroutinesApi
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        binding?.etUserName?.offer(viewModel.userNameChannel)
+        binding?.etUserPassword?.offer(viewModel.userPasswordChannel)
+
+        btnLogin.setOnClickListener {
+            hideKeyboard()
+            viewModel.loginUser(etUserName.text.toString().trim(),etUserPassword.text.toString().trim())
+        }
+    }
+
+
+    @ExperimentalCoroutinesApi
+    @FlowPreview
     override fun subscribeUi() {
+        viewModel.enableLogin.observe(viewLifecycleOwner, Observer { validate ->
+            if(validate!=null) {
+                binding?.btnLogin?.isEnabled = validate
+            }
+        })
+
+        viewModel.progress.observe(viewLifecycleOwner, Observer { progress ->
+            binding?.progressBar?.visible(progress)
+            binding?.btnLogin?.isEnabled = !progress && viewModel.enableLogin.value?:false
+            binding?.btnRegistration?.isEnabled = !progress
+        })
     }
 
 }

+ 49 - 0
feature_auth/src/main/java/com/mrozon/feature_auth/presentation/LoginFragmentViewModel.kt

@@ -1,9 +1,58 @@
 package com.mrozon.feature_auth.presentation
 
+import android.util.Patterns.EMAIL_ADDRESS
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
 import com.mrozon.utils.base.BaseViewModel
+//import com.mrozon.utils.extension.asFlow
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.ConflatedBroadcastChannel
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineLatest
 import javax.inject.Inject
 
 class LoginFragmentViewModel @Inject constructor(
 ): BaseViewModel() {
 
+    private val _progress = MutableLiveData<Boolean>(false)
+    val progress: LiveData<Boolean>
+        get() = _progress
+
+    @ExperimentalCoroutinesApi
+    val userNameChannel = ConflatedBroadcastChannel<String>()
+
+    @ExperimentalCoroutinesApi
+    val userPasswordChannel = ConflatedBroadcastChannel<String>()
+
+    @FlowPreview
+    @ExperimentalCoroutinesApi
+    val enableLogin = object: MutableLiveData<Boolean>() {
+
+        override fun onActive() {
+            value?.let { return }
+            viewModelScope.launch {
+                var job: Deferred<Unit>? = null
+                userNameChannel.asFlow()
+                    .combine(userPasswordChannel.asFlow()) { name, psw ->
+                        Pair(name, psw)
+                    }
+                    .collect {
+                        job?.cancel()
+                        job = async(Dispatchers.Main) {
+                            value = validateInputData(it)
+                        }
+                    }
+            }
+        }
+    }
+
+    private fun validateInputData(pair: Pair<String,String>) =
+        (EMAIL_ADDRESS.matcher(pair.first).matches()) and (pair.second.isNotEmpty())
+
+    fun loginUser(userName: String, userPsw: String) {
+        _progress.value = true
+    }
 }

+ 89 - 16
feature_auth/src/main/res/layout/fragment_login.xml

@@ -4,25 +4,98 @@
     xmlns:tools="http://schemas.android.com/tools">
 
     <data>
-
+        <variable
+            name="viewModel"
+            type="com.mrozon.feature_auth.presentation.LoginFragmentViewModel" />
     </data>
 
-    <androidx.constraintlayout.widget.ConstraintLayout
+    <ScrollView
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
-        <TextView
-            android:id="@+id/textView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="32dp"
-            android:layout_marginTop="32dp"
-            android:layout_marginEnd="32dp"
-            android:layout_marginBottom="32dp"
-            android:text="TextView"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-    </androidx.constraintlayout.widget.ConstraintLayout>
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/layoutLogin"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <androidx.constraintlayout.widget.Guideline
+                android:id="@+id/guideline"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintGuide_percent="0.4" />
+
+            <EditText
+                android:id="@+id/etUserName"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="32dp"
+                android:layout_marginEnd="32dp"
+                android:ems="10"
+                android:gravity="center"
+                android:hint="@string/etUsername"
+                android:inputType="textEmailAddress"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="@+id/guideline" />
+
+            <EditText
+                android:id="@+id/etUserPassword"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="32dp"
+                android:layout_marginTop="24dp"
+                android:layout_marginEnd="32dp"
+                android:ems="10"
+                android:gravity="center"
+                android:hint="@string/etUserPassword"
+                android:inputType="textPassword"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/etUserName" />
+
+            <Button
+                android:id="@+id/btnLogin"
+                style="@style/Widget.AppCompat.Button.Colored"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="32dp"
+                android:layout_marginTop="64dp"
+                android:layout_marginEnd="32dp"
+                android:text="@string/btnLogin"
+                android:enabled="false"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/etUserPassword" />
+
+            <Button
+                android:id="@+id/btnRegistration"
+                style="@style/Widget.AppCompat.Button.Borderless.Colored"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="32dp"
+                android:layout_marginTop="16dp"
+                android:layout_marginEnd="32dp"
+                android:layout_marginBottom="16dp"
+                android:text="@string/btnRegistration"
+                android:textSize="12sp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/btnLogin" />
+
+            <ProgressBar
+                android:id="@+id/progressBar"
+                style="?android:attr/progressBarStyle"
+                android:layout_width="128dp"
+                android:layout_height="128dp"
+                android:indeterminate="true"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </ScrollView>
 </layout>

+ 4 - 0
feature_auth/src/main/res/values/strings.xml

@@ -1,4 +1,8 @@
 <resources>
     <!-- TODO: Remove or change this placeholder text -->
     <string name="hello_blank_fragment">Hello blank fragment</string>
+    <string name="etUsername">Type Your e-mail</string>
+    <string name="etUserPassword">Type Your password</string>
+    <string name="btnLogin">Login</string>
+    <string name="btnRegistration">Registration</string>
 </resources>

+ 23 - 0
utils/src/main/java/com/mrozon/utils/extension/FragmentExt.kt

@@ -0,0 +1,23 @@
+package com.mrozon.utils.extension
+
+import android.content.Context
+import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+
+fun AppCompatActivity.hideKeyboard() {
+    val view = this.currentFocus
+    if (view != null) {
+        val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        imm.hideSoftInputFromWindow(view.windowToken, 0)
+    }
+    window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
+}
+
+fun Fragment.hideKeyboard() {
+    val activity = this.activity
+    if (activity is AppCompatActivity) {
+        activity.hideKeyboard()
+    }
+}

+ 26 - 0
utils/src/main/java/com/mrozon/utils/extension/ViewExt.kt

@@ -0,0 +1,26 @@
+package com.mrozon.utils.extension
+
+import android.view.View
+import android.widget.EditText
+import androidx.core.widget.doOnTextChanged
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.ConflatedBroadcastChannel
+
+@ExperimentalCoroutinesApi
+fun EditText.offer(channel: ConflatedBroadcastChannel<String>) {
+    doOnTextChanged { text, _, _, _ ->
+        channel.offer(text.toString().trim())
+    }
+}
+
+fun View.visible(show: Boolean, isGone: Boolean = false) {
+    if(show) {
+        visibility = View.VISIBLE
+    }
+    else {
+        if (isGone)
+            visibility = View.GONE
+        else
+            visibility = View.INVISIBLE
+    }
+}