Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/52 entry fragment viewmodel #74

Merged
merged 30 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
743dec9
adds textview underlayer for entry screen
artwist-polyakov Feb 14, 2024
5807e48
adds sizes and margins for register cardview
artwist-polyakov Feb 15, 2024
b219b63
adds register string
artwist-polyakov Feb 15, 2024
8dde232
adds style for register button in styles
artwist-polyakov Feb 15, 2024
5c583bb
adds two background files for buttons
artwist-polyakov Feb 15, 2024
9cfa959
adds cardview on entry screen
artwist-polyakov Feb 15, 2024
feb249c
fixes id names on entry screen
artwist-polyakov Feb 16, 2024
3c6e4e2
Merge branch 'dev' into feature/52-entry-fragment-viewmodel
artwist-polyakov Feb 16, 2024
401cd5e
deletes unusual blocks on entry screen
artwist-polyakov Feb 16, 2024
6b83152
adds buttons and text on entry screen
artwist-polyakov Feb 16, 2024
dd51c92
removes vk login from entryfragment
artwist-polyakov Feb 16, 2024
8b3970f
adds status bar tint
artwist-polyakov Feb 16, 2024
e80a57a
moves styles from entry screen layout to styles_btn.xml
artwist-polyakov Feb 16, 2024
049560c
adds ellipsize to button
artwist-polyakov Feb 16, 2024
5925b6d
adds debounce util
artwist-polyakov Feb 16, 2024
3d5b46c
adds interaction objects
artwist-polyakov Feb 16, 2024
1afbb3d
adds entry viewmodel
artwist-polyakov Feb 16, 2024
10bc964
changes EntryFragbent to BaseFragment
artwist-polyakov Feb 16, 2024
d9d0ae8
adds default value for debounce
artwist-polyakov Feb 16, 2024
1bf4ea9
clears timber logs
artwist-polyakov Feb 16, 2024
3a4ba60
little fixes
artwist-polyakov Feb 16, 2024
f049053
renames variable
artwist-polyakov Feb 16, 2024
06293a0
changes state and interaction to sealed interface
artwist-polyakov Feb 16, 2024
7ce7bb4
changes lines and ellipsize for signup button
artwist-polyakov Feb 18, 2024
1a14026
adds lines and ellipsize content for enter button
artwist-polyakov Feb 18, 2024
664326a
fixes right border enter button bug
artwist-polyakov Feb 18, 2024
747fd52
fixes ellipsize on small screens
artwist-polyakov Feb 18, 2024
3cfd2fe
adds margin hack to top text
artwist-polyakov Feb 18, 2024
2efb34f
disables night mode
artwist-polyakov Feb 18, 2024
d8ae2d6
adds directly textsize to buttons
artwist-polyakov Feb 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,148 +1,73 @@
package app.cashadvisor.authorization.presentation.ui
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import android.widget.Toast
import androidx.fragment.app.Fragment

import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import app.cashadvisor.R
import app.cashadvisor.authorization.presentation.viewmodel.EntryViewModel
import app.cashadvisor.authorization.presentation.viewmodel.models.EntryInteraction
import app.cashadvisor.authorization.presentation.viewmodel.models.EntryScreenState
import app.cashadvisor.common.ui.BaseFragment
import app.cashadvisor.common.utils.debounce
import app.cashadvisor.databinding.FragmentEntryBinding
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
import com.vk.id.AccessToken
import com.vk.id.VKID
import com.vk.id.VKIDAuthFail

class EntryFragment : Fragment() {
private var _binding: FragmentEntryBinding? = null
private val binding get() = _binding!!
private lateinit var mGoogleSignInClient: GoogleSignInClient
private lateinit var startResultSignIn: ActivityResultLauncher<Intent>

private val vkAuthCallback = object : VKID.AuthCallback {
override fun onSuccess(accessToken: AccessToken) {
val token = accessToken.token
Toast.makeText(requireContext(), "token: ${token}", Toast.LENGTH_SHORT).show()
Toast.makeText(
requireContext(),
"${accessToken.userData.firstName} ${accessToken.userData.lastName}",
Toast.LENGTH_LONG
).show()
}

override fun onFail(fail: VKIDAuthFail) {
Toast.makeText(requireContext(), "error: ${fail.description}", Toast.LENGTH_LONG).show()
when (fail) {
is VKIDAuthFail.Canceled -> {
//...
}
else -> {
//...
}
}
}
}
import kotlinx.coroutines.launch

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {

}
setupGoogleRegistration()
}
class EntryFragment :
BaseFragment<FragmentEntryBinding, EntryViewModel>(FragmentEntryBinding::inflate) {
override val viewModel: EntryViewModel by viewModels()
private var onButtonClickDebounce: ((EntryInteraction) -> Unit)? = null

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentEntryBinding.inflate(layoutInflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onConfigureViews() {
configureDebounce()
binding.btnLogin.setOnClickListener {
findNavController().navigate(R.id.action_entryFragment_to_loginFragment)
onButtonClickDebounce?.invoke(EntryInteraction.SignInTapped)
}

binding.btnSignup.setOnClickListener {
findNavController().navigate(R.id.action_entryFragment_to_signupFragment)
}

binding.signInButton.setOnClickListener{
val signIntent = mGoogleSignInClient.signInIntent
startResultSignIn.launch(signIntent)
onButtonClickDebounce?.invoke(EntryInteraction.SignUpTapped)
}
binding.btnAuthVk.setOnClickListener {
val vkid = VKID(requireContext())
vkid.authorize(this, vkAuthCallback)
}

}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
override fun onSubscribe() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.state.collect { state ->
render(state)
}
}
}

override fun onStart() {
super.onStart()

//Проверка вошел ли пользователь с помощью google в приложение

val account = GoogleSignIn.getLastSignedInAccount(requireActivity())
}
private fun render(state: EntryScreenState) {
when (state) {
is EntryScreenState.SignUp -> {
findNavController().navigate(R.id.action_entryFragment_to_signupFragment)

//Настроить регистрацию через google
private fun setupGoogleRegistration() {
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestIdToken(getString(R.string.client_id))
.build()
}

mGoogleSignInClient = GoogleSignIn.getClient(requireActivity(), gso)
is EntryScreenState.SignIn -> {
findNavController().navigate(R.id.action_entryFragment_to_loginFragment)

setupResultSignIn()
}
}

private fun setupResultSignIn(){
startResultSignIn = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == RESULT_OK) {
val task =
GoogleSignIn.getSignedInAccountFromIntent(result.data)
handleSignInResult(task)
else -> {
// no-op
}
}
}

private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
try {
val account: GoogleSignInAccount? = completedTask.getResult(ApiException::class.java)
val googleFirstName = account?.givenName ?: ""
Log.i("Google Account", "Google First Name: $googleFirstName")
val googleLastName = account?.familyName ?: ""
Log.i("Google Account", "Google Last Name: $googleLastName")
val googleEmail = account?.email ?: ""
Log.i("Google Account", "Google Email: $googleEmail")
val googleIdToken = account?.idToken
Log.i("Google Account", "Google idToken: $googleIdToken")
Log.i("Google Account", "Token Info: https://oauth2.googleapis.com/tokeninfo?id_token=$googleIdToken")

} catch (e: ApiException) {
Log.e(
"Google Account", "failed code= ${e.statusCode}"
)
fun configureDebounce() {
onButtonClickDebounce = debounce(
CLICK_DEBOUNCE_DELAY,
viewLifecycleOwner.lifecycleScope,
useLastParam = false,
actionWithDelay = false
) { action ->
viewModel.handleInteraction(action)
alexxk2 marked this conversation as resolved.
Show resolved Hide resolved
}
}

companion object {
private const val CLICK_DEBOUNCE_DELAY = 500L
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package app.cashadvisor.authorization.presentation.viewmodel

import androidx.lifecycle.viewModelScope
import app.cashadvisor.authorization.presentation.viewmodel.models.EntryInteraction
import app.cashadvisor.authorization.presentation.viewmodel.models.EntryScreenState
import app.cashadvisor.common.ui.BaseViewModel
import app.cashadvisor.common.utils.debounce
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject

@HiltViewModel
class EntryViewModel @Inject constructor() : BaseViewModel() {
private var _state = MutableStateFlow<EntryScreenState>(EntryScreenState.Default)
val state: StateFlow<EntryScreenState>
get() = _state
private var coolDownDebounce: ((Unit) -> Unit) = debounce(
COOL_DOWN_DELAY,
viewModelScope
) {
_state.value = EntryScreenState.Default
}

fun handleInteraction(action: EntryInteraction) {
when (action) {
is EntryInteraction.SignUpTapped -> {
_state.value = EntryScreenState.SignUp
}

is EntryInteraction.SignInTapped -> {
_state.value = EntryScreenState.SignIn
}
}
coolDownDebounce.invoke(Unit)
}

companion object {
private const val COOL_DOWN_DELAY = 100L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package app.cashadvisor.authorization.presentation.viewmodel.models

sealed interface EntryInteraction {
object SignUpTapped : EntryInteraction
object SignInTapped : EntryInteraction
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package app.cashadvisor.authorization.presentation.viewmodel.models

sealed interface EntryScreenState {

object Default : EntryScreenState
object SignIn : EntryScreenState
object SignUp : EntryScreenState

}
40 changes: 40 additions & 0 deletions app/src/main/java/app/cashadvisor/common/utils/Debounce.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package app.cashadvisor.common.utils

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
*Этот Kotlin класс определяет функцию debounce, которая создает и возвращает замыкание с дебаунс-логикой для переданной функции action. Дебаунсинг используется для ограничения скорости вызова функции, то есть функция action будет вызвана не чаще, чем указано в параметре delayMillis.
*
* ### Параметры функции debounce:
*
* - delayMillis: Long - время задержки в миллисекундах между вызовами функции action.
* - coroutineScope: CoroutineScope - область видимости корутины, в которой будет выполняться задержка и вызов функции action.
* - useLastParam: Boolean - если true, то после задержки будет использоваться последний переданный аргумент, если false, то будет использоваться аргумент, который был передан при первом вызове.
* - actionWithDelay: Boolean (по умолчанию true) - если true, функция action будет вызвана после задержки, если false, функция action будет вызвана сразу без задержки.
* - action: (T) -> Unit - функция, вызов которой нужно задебаунсить. T - тип входного параметра функции.
*
*/
fun <T> debounce(
delayMillis: Long,
coroutineScope: CoroutineScope,
useLastParam: Boolean = false,
actionWithDelay: Boolean = true,
action: (T) -> Unit
): (T) -> Unit {
var debounceJob: Job? = null
return { param: T ->
if (useLastParam) {
debounceJob?.cancel()
}
if (debounceJob?.isCompleted != false || useLastParam) {
if (!actionWithDelay) action(param)
debounceJob = coroutineScope.launch {
delay(delayMillis)
if (actionWithDelay) action(param)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package app.cashadvisor.main.presentation.ui

import android.content.res.Configuration
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.WindowInsetsController
import android.view.WindowManager
import android.widget.Button
import androidx.activity.viewModels
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
Expand Down Expand Up @@ -38,7 +43,7 @@ class MainActivity : AppCompatActivity() {
Timber.tag("MainActivity").e("Error log")

binding = ActivityMainBinding.inflate(layoutInflater).also { setContentView(it.root) }

setStatusBarColor()
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
Expand Down Expand Up @@ -138,4 +143,30 @@ class MainActivity : AppCompatActivity() {
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}

/**
* Метод определяет теккущую тему устройства (тёмная/светлая) и относительно этого устанавливает цвета в статус баре
*/
private fun setStatusBarColor() {
val uiMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK

window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)

if (uiMode == Configuration.UI_MODE_NIGHT_NO) {
window.statusBarColor = ContextCompat.getColor(this, R.color.white)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
} else {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
} else {
window.statusBarColor = ContextCompat.getColor(this, R.color.black)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
window.decorView.systemUiVisibility = 0
}
}
}
}
Loading
Loading