Simple screens and logic with Navigation Component

This commit is contained in:
2021-07-12 00:17:18 +03:00
parent 1c773df3e1
commit 026c0c74af
25 changed files with 650 additions and 142 deletions
Binary file not shown.
-18
View File
@@ -1,18 +0,0 @@
{
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.meloda.fast",
"variantName": "processReleaseResources",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-release.apk"
}
]
}
@@ -0,0 +1,251 @@
package com.meloda.fast.base
import android.content.Intent
import android.util.SparseArray
import androidx.core.util.forEach
import androidx.core.util.set
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.meloda.fast.R
/*
fun BottomNavigationView.setupWithNavController(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
dataManager: DataManager,
containerId: Int,
intent: Intent
): LiveData<NavController> {
// Map of tags
val graphIdToTagMap = SparseArray<String>()
// Result. Mutable live data with the selected controlled
val selectedNavController = MutableLiveData<NavController>()
var firstFragmentGraphId = 0
// First create a NavHostFragment for each NavGraph ID
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Obtain its id
val graphId = navHostFragment.navController.graph.id
if (index == 0) {
firstFragmentGraphId = graphId
}
// Save to the map
graphIdToTagMap[graphId] = fragmentTag
// Attach or detach nav host fragment depending on whether it's the selected item.
if (this.selectedItemId == graphId) {
// Update livedata with the selected graph
selectedNavController.value = navHostFragment.navController
attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
} else {
detachNavHostFragment(fragmentManager, navHostFragment)
}
}
// Now connect selecting an item with swapping Fragments
var selectedItemTag = graphIdToTagMap[this.selectedItemId]
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
var isOnFirstFragment = selectedItemTag == firstFragmentTag
// When a navigation item is selected
setOnNavigationItemSelectedListener { item ->
// Don't do anything if the state is state has already been saved.
if (fragmentManager.isStateSaved) {
false
} else {
val navController = (fragmentManager.findFragmentByTag(selectedItemTag) as NavHostFragment).navController
navController.popBackStack(navController.graph.startDestination, false)
if (selectedItemTag != graphIdToTagMap[item.itemId]) {
val listCheck = listOf(R.id.contacts, R.id.chats)
val newlySelectedItemTag = if (listCheck.contains(item.itemId) && dataManager.token.isBlank()) graphIdToTagMap[R.id.signIn] else graphIdToTagMap[item.itemId]
// Pop everything above the first fragment (the "fixed start destination")
fragmentManager.popBackStack(
firstFragmentTag,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment
// Exclude the first fragment tag because it's always in the back stack.
if (firstFragmentTag != newlySelectedItemTag) {
// Commit a transaction that cleans the back stack and adds the first fragment
// to it, creating the fixed started destination.
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.attach(selectedFragment)
.setPrimaryNavigationFragment(selectedFragment)
.apply {
// Detach all other Fragments
graphIdToTagMap.forEach { _, fragmentTagIter ->
if (fragmentTagIter != newlySelectedItemTag) {
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
}
}
}
.addToBackStack(firstFragmentTag)
.setReorderingAllowed(true)
.commit()
}
selectedItemTag = newlySelectedItemTag
isOnFirstFragment = selectedItemTag == firstFragmentTag
selectedNavController.value = selectedFragment.navController
true
} else {
false
}
}
}
setOnNavigationItemReselectedListener { item ->
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment
val navController = selectedFragment.navController
// Pop the back stack to the start destination of the current navController graph
if (selectedItemTag != graphIdToTagMap[item.itemId] && dataManager.token.isNotBlank()) {
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.attach(selectedFragment)
.setPrimaryNavigationFragment(selectedFragment)
.apply {
// Detach all other Fragments
graphIdToTagMap.forEach { _, fragmentTagIter ->
if (fragmentTagIter != newlySelectedItemTag) {
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
}
}
}
.addToBackStack(firstFragmentTag)
.setReorderingAllowed(true)
.commit()
selectedItemTag = newlySelectedItemTag
isOnFirstFragment = selectedItemTag == firstFragmentTag
selectedNavController.value = selectedFragment.navController
} else navController.popBackStack(navController.graph.startDestination, false)
}
// Optional: on item reselected, pop back stack to the destination of the graph
setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
// Finally, ensure that we update our BottomNavigationView when the back stack changes
fragmentManager.addOnBackStackChangedListener {
if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
this.selectedItemId = firstFragmentGraphId
}
// Reset the graph if the currentDestination is not valid (happens when the back
// stack is popped after using the back button).
selectedNavController.value?.let { controller ->
if (controller.currentDestination == null) {
controller.navigate(controller.graph.id)
}
}
}
return selectedNavController
}
private fun BottomNavigationView.setupDeepLinks(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int,
intent: Intent
) {
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Handle Intent
if (navHostFragment.navController.handleDeepLink(intent) &&
selectedItemId != navHostFragment.navController.graph.id
) {
this.selectedItemId = navHostFragment.navController.graph.id
}
}
}
private fun detachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment
) {
fragmentManager.beginTransaction()
.detach(navHostFragment)
.commitNow()
}
private fun attachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment,
isPrimaryNavFragment: Boolean
) {
fragmentManager.beginTransaction()
.attach(navHostFragment)
.apply {
if (isPrimaryNavFragment) {
setPrimaryNavigationFragment(navHostFragment)
}
}
.commitNow()
}
private fun obtainNavHostFragment(
fragmentManager: FragmentManager,
fragmentTag: String,
navGraphId: Int,
containerId: Int,
): NavHostFragment {
// If the Nav Host fragment exists, return it
val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
existingFragment?.let { return it }
// Otherwise, create it and return it.
val navHostFragment = NavHostFragment.create(navGraphId)
fragmentManager.beginTransaction()
.add(containerId, navHostFragment, fragmentTag)
.commitNow()
return navHostFragment
}
private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
val backStackCount = backStackEntryCount
for (index in 0 until backStackCount) {
if (getBackStackEntryAt(index).name == backStackName) {
return true
}
}
return false
}
private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
*/
@@ -27,4 +27,7 @@ abstract class BaseVM : ViewModel() {
)
}
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
protected suspend fun <T : VKEvent> sendEvent(event: T) = tasksEventChannel.send(event)
}
@@ -5,4 +5,7 @@ data class ShowDialogInfoEvent(
val message: String,
val positiveBtn: String? = null,
val negativeBtn: String? = null
) : VKEvent()
) : VKEvent()
object StartProgressEvent : VKEvent()
object StopProgressEvent : VKEvent()
@@ -4,6 +4,7 @@ import android.content.Intent
import android.util.SparseArray
import androidx.core.util.forEach
import androidx.core.util.set
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@@ -11,6 +12,7 @@ import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.meloda.fast.R
import com.meloda.fast.UserConfig
/**
* Manages the various graphs needed for a [BottomNavigationView].
@@ -22,7 +24,6 @@ object NavigationExtensions {
fun BottomNavigationView.setupWithNavController(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
// dataManager: DataManager,
containerId: Int,
intent: Intent
): LiveData<NavController> {
@@ -71,8 +72,7 @@ object NavigationExtensions {
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
var isOnFirstFragment = selectedItemTag == firstFragmentTag
// When a navigation item is selected
setOnNavigationItemSelectedListener { item ->
setOnItemSelectedListener { item ->
// Don't do anything if the state is state has already been saved.
if (fragmentManager.isStateSaved) {
false
@@ -81,12 +81,9 @@ object NavigationExtensions {
(fragmentManager.findFragmentByTag(selectedItemTag) as NavHostFragment).navController
navController.popBackStack(navController.graph.startDestination, false)
if (selectedItemTag != graphIdToTagMap[item.itemId]) {
// val listCheck = listOf(R.id.contacts, R.id.chats)
val newlySelectedItemTag =
//if (listCheck.contains(item.itemId) && dataManager.token.isBlank()) graphIdToTagMap[R.id.signIn] else
graphIdToTagMap[item.itemId]
val newlySelectedItemTag = //graphIdToTagMap[item.itemId]
if (!UserConfig.isLoggedIn()) graphIdToTagMap[R.id.login] else graphIdToTagMap[item.itemId]
// Pop everything above the first fragment (the "fixed start destination")
fragmentManager.popBackStack(
firstFragmentTag,
FragmentManager.POP_BACK_STACK_INCLUSIVE
@@ -128,7 +125,8 @@ object NavigationExtensions {
}
}
}
setOnNavigationItemReselectedListener { item ->
setOnItemReselectedListener { item ->
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
val selectedFragment =
fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment
@@ -257,5 +255,12 @@ object NavigationExtensions {
return false
}
val FragmentManager.visibleFragments
get(): List<Fragment> {
val visibleFragments = arrayListOf<Fragment>()
fragments.forEach { if (it.isVisible) visibleFragments.add(it) }
return visibleFragments
}
private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
}
@@ -0,0 +1,21 @@
package com.meloda.fast.fragment.friends
import android.os.Bundle
import android.view.View
import android.viewbinding.library.fragment.viewBinding
import com.meloda.fast.R
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.databinding.FragmentFriendsBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class FriendsFragment : BaseFragment(R.layout.fragment_friends) {
private val binding: FragmentFriendsBinding by viewBinding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
@@ -0,0 +1,21 @@
package com.meloda.fast.fragment.important
import android.os.Bundle
import android.view.View
import android.viewbinding.library.fragment.viewBinding
import com.meloda.fast.R
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.databinding.FragmentImportantBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ImportantFragment : BaseFragment(R.layout.fragment_important) {
private val binding: FragmentImportantBinding by viewBinding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
@@ -1,5 +1,6 @@
package com.meloda.fast.fragment.login
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.Gravity
import android.view.KeyEvent
@@ -7,9 +8,11 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.viewbinding.library.fragment.viewBinding
import android.webkit.CookieManager
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
@@ -21,11 +24,15 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.meloda.fast.R
import com.meloda.fast.base.BaseVMFragment
import com.meloda.fast.base.viewmodel.StartProgressEvent
import com.meloda.fast.base.viewmodel.StopProgressEvent
import com.meloda.fast.base.viewmodel.VKEvent
import com.meloda.fast.databinding.FragmentLoginBinding
import com.meloda.fast.fragment.main.MainFragment
import com.meloda.fast.util.KeyboardUtils
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import kotlin.concurrent.schedule
@@ -45,7 +52,7 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.checkUserSession()
(parentFragment?.parentFragment as? MainFragment)?.bottomBar?.isVisible = false
prepareViews()
@@ -63,16 +70,49 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
when (event) {
is ShowCaptchaDialog -> showCaptchaDialog(event.captchaImage, event.captchaSid)
is GoToValidationEvent -> goToValidation(event.redirectUrl)
GoToMainEvent -> goToMain()
is GoToMainEvent -> goToMain(event.haveAuthorized)
StartProgressEvent -> onProgressStarted()
StopProgressEvent -> onProgressEnded()
}
}
private fun onProgressStarted() {
binding.loginContainer.isVisible = false
binding.passwordContainer.isVisible = false
binding.auth.isVisible = false
binding.progress.isVisible = true
}
private fun onProgressEnded() {
binding.loginContainer.isVisible = true
binding.passwordContainer.isVisible = true
binding.auth.isVisible = true
binding.progress.isVisible = false
}
private fun prepareViews() {
prepareWebView()
prepareEmailEditText()
preparePasswordEditText()
prepareAuthButton()
}
@SuppressLint("SetJavaScriptEnabled")
private fun prepareWebView() {
with(binding.webView) {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.loadsImagesAutomatically = false
settings.userAgentString = "Chrome/41.0.2228.0 Safari/537.36"
clearCache(true)
}
val cookieManager = CookieManager.getInstance()
cookieManager.removeAllCookies(null)
cookieManager.flush()
cookieManager.setAcceptCookie(false)
}
private fun prepareEmailEditText() {
binding.loginInput.addTextChangedListener {
if (!binding.loginLayout.error.isNullOrBlank()) binding.loginLayout.error = ""
@@ -98,21 +138,30 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
(event.action == KeyEvent.ACTION_DOWN && (event.keyCode == KeyEvent.KEYCODE_ENTER || event.keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER))
) {
KeyboardUtils.hideKeyboardFrom(binding.passwordInput)
binding.authorize.performClick()
binding.auth.performClick()
true
} else false
}
}
private fun prepareAuthButton() {
binding.authorize.setOnClickListener {
binding.auth.setOnClickListener {
if (binding.progress.isVisible) return@setOnClickListener
val loginString = binding.loginInput.text.toString().trim()
val passwordString = binding.passwordInput.text.toString().trim()
if (!validateInputData(loginString, passwordString)) return@setOnClickListener
viewModel.login(requireContext(), loginString, passwordString)
KeyboardUtils.hideKeyboardFrom(it)
lifecycleScope.launch {
viewModel.login(
binding.webView,
loginString,
passwordString
)
}
}
}
@@ -193,17 +242,18 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
builder.setPositiveButton(android.R.string.ok) { _, _ ->
val captchaCode = captchaCodeEditText.text.toString().trim()
viewModel.login(
requireContext(),
lastEmail,
lastPassword,
"&captcha_sid=$captchaSid&captcha_key=$captchaCode"
)
lifecycleScope.launch {
viewModel.login(
binding.webView,
lastEmail,
lastPassword,
"&captcha_sid=$captchaSid&captcha_key=$captchaCode"
)
}
}
builder.setTitle(R.string.input_captcha)
builder.show()
}
private fun goToValidation(redirectUrl: String) {
@@ -213,8 +263,12 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
)
}
private fun goToMain() {
findNavController().navigate(R.id.toMain)
private fun goToMain(haveAuthorized: Boolean) {
lifecycleScope.launch {
if (haveAuthorized) delay(500)
findNavController().navigate(R.id.toMain)
}
}
}
@@ -1,10 +1,6 @@
package com.meloda.fast.fragment.login
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
@@ -12,68 +8,53 @@ import androidx.lifecycle.viewModelScope
import com.meloda.fast.UserConfig
import com.meloda.fast.api.VKAuth
import com.meloda.fast.base.viewmodel.BaseVM
import com.meloda.fast.base.viewmodel.StartProgressEvent
import com.meloda.fast.base.viewmodel.StopProgressEvent
import com.meloda.fast.base.viewmodel.VKEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.json.JSONObject
import org.jsoup.Jsoup
class LoginVM : BaseVM() {
fun login(
context: Context,
private var isWebViewPrepared = true
suspend fun login(
webView: WebView,
email: String,
password: String,
captcha: String = ""
) {
sendEvent(StartProgressEvent)
val urlToGo = VKAuth.getDirectAuthUrl(email, password, captcha)
// val builder = AlertDialog.Builder(context)
if (isWebViewPrepared) {
isWebViewPrepared = false
val webView = createWebView(context)
webView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
webView.addJavascriptInterface(WebViewHandlerInterface(), "HtmlHandler")
// builder.setTitle("Auth")
// builder.setView(webView)
// builder.show()
webView.addJavascriptInterface(WebViewHandlerInterface(), "HtmlHandler")
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
webView.loadUrl(
"javascript:window.HtmlHandler.handleHtml" +
"('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');"
)
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
webView.loadUrl(
"javascript:window.HtmlHandler.handleHtml" +
"('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');"
)
}
}
}
webView.loadUrl(urlToGo)
}
@SuppressLint("SetJavaScriptEnabled")
private fun createWebView(context: Context): WebView {
val loginWebView = WebView(context)
loginWebView.settings.javaScriptEnabled = true
loginWebView.settings.domStorageEnabled = true
loginWebView.settings.loadsImagesAutomatically = false
loginWebView.settings.userAgentString = "Chrome/41.0.2228.0 Safari/537.36"
loginWebView.clearCache(true)
val cookieManager = CookieManager.getInstance()
cookieManager.removeAllCookies(null)
cookieManager.flush()
cookieManager.setAcceptCookie(false)
return loginWebView
}
@Suppress("MoveVariableDeclarationIntoWhen")
private fun checkResponse(response: JSONObject) {
viewModelScope.launch {
viewModelScope.launch(Dispatchers.Default) {
delay(1500)
sendEvent(StopProgressEvent)
if (response.has("error")) {
val errorString = response.optString("error")
@@ -82,33 +63,12 @@ class LoginVM : BaseVM() {
val redirectUrl = response.optString("redirect_uri")
tasksEventChannel.send(GoToValidationEvent(redirectUrl))
// val bundle = Bundle()
// bundle.putString("url", redirectUrl)
/* fragment.setFragmentResultListener("validation") { _, bundle ->
val userId = bundle.getInt("userId")
val token = bundle.getString("token") ?: ""
saveUserData(userId, token)
openMainScreen()
}
fragment.parentFragmentManager.beginTransaction()
.replace(
R.id.fragmentContainer,
ValidationFragment().apply { arguments = bundle })
.addToBackStack("")
.commit()*/
}
"need_captcha" -> {
val captchaImage = response.optString("captcha_img")
val captchaSid = response.optString("captcha_sid")
tasksEventChannel.send(ShowCaptchaDialog(captchaImage, captchaSid))
// showCaptchaDialog(captchaImage, captchaSid)
}
}
} else {
@@ -118,11 +78,7 @@ class LoginVM : BaseVM() {
UserConfig.accessToken = accessToken
UserConfig.userId = userId
tasksEventChannel.send(GoToMainEvent)
// openMainScreen()
// onResponseListener?.onResponse(null)
tasksEventChannel.send(GoToMainEvent())
}
}
}
@@ -134,11 +90,7 @@ class LoginVM : BaseVM() {
UserConfig.accessToken = accessToken
UserConfig.userId = userId
tasksEventChannel.send(GoToMainEvent)
}
fun checkUserSession() = viewModelScope.launch {
if (UserConfig.isLoggedIn()) tasksEventChannel.send(GoToMainEvent)
tasksEventChannel.send(GoToMainEvent())
}
inner class WebViewHandlerInterface {
@@ -158,4 +110,4 @@ class LoginVM : BaseVM() {
data class ShowCaptchaDialog(val captchaImage: String, val captchaSid: String) : VKEvent()
data class GoToValidationEvent(val redirectUrl: String) : VKEvent()
object GoToMainEvent : VKEvent()
data class GoToMainEvent(val haveAuthorized: Boolean = true) : VKEvent()
@@ -4,7 +4,9 @@ import android.os.Bundle
import android.view.View
import android.viewbinding.library.fragment.viewBinding
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.base.BaseVMFragment
import com.meloda.fast.databinding.FragmentMainBinding
import com.meloda.fast.extensions.NavigationExtensions.setupWithNavController
@@ -21,18 +23,29 @@ class MainFragment : BaseVMFragment<MainVM>(R.layout.fragment_main) {
if (savedInstanceState == null) setupBottomBar()
if (!UserConfig.isLoggedIn()) findNavController().navigate(R.id.toLogin)
}
private fun setupBottomBar() {
val navGraphIds = listOf(R.id.messages, R.id.friends)
binding.bottomBar.setupWithNavController(
navGraphIds = listOf(),
fragmentManager = childFragmentManager,
containerId = R.id.fragmentContainer,
intent = requireActivity().intent
val navGraphIds = listOf(
R.navigation.messages,
R.navigation.friends,
R.navigation.important,
R.navigation.login
)
with(binding.bottomBar) {
selectedItemId = R.id.messages
setupWithNavController(
navGraphIds = navGraphIds,
fragmentManager = childFragmentManager,
containerId = R.id.fragmentContainer,
intent = requireActivity().intent
)
}
}
val bottomBar get() = binding.bottomBar
}
@@ -1,9 +1,21 @@
package com.meloda.fast.fragment.messages
import android.os.Bundle
import android.view.View
import android.viewbinding.library.fragment.viewBinding
import com.meloda.fast.R
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.databinding.FragmentConversationsBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ConversationsFragment : BaseFragment(R.layout.fragment_conversations) {
private val binding: FragmentConversationsBinding by viewBinding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
</vector>
@@ -4,6 +4,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="conversations" />
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+8 -3
View File
@@ -5,20 +5,25 @@
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="friends" />
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:background="@drawable/toolbar_background"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/toolbar_background"
app:layout_scrollFlags="scroll|enterAlways">
<include layout="@layout/toolbar" />
@@ -3,4 +3,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="important" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+18 -2
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/loginRoot"
@@ -8,6 +9,12 @@
android:layout_height="match_parent"
android:orientation="vertical">
<WebView
android:id="@+id/webView"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -16,6 +23,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
@@ -37,6 +45,14 @@
app:tint="?colorAccent" />
</FrameLayout>
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/loginContainer"
android:layout_width="match_parent"
@@ -110,7 +126,7 @@
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/authorize"
android:id="@+id/auth"
style="@style/Widget.MaterialButton"
android:layout_width="wrap_content"
android:layout_height="60dp"
@@ -2,17 +2,17 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigationFriends"
android:id="@id/friends"
android:icon="@drawable/ic_people_outline"
android:title="@string/navigation_friends" />
<item
android:id="@+id/navigationConversations"
android:id="@id/messages"
android:icon="@drawable/ic_message_outline"
android:title="@string/navigation_chats" />
<item
android:id="@+id/navigationImportant"
android:id="@id/important"
android:icon="@drawable/ic_star_border"
android:title="@string/navigation_important" />
+9 -1
View File
@@ -1,6 +1,14 @@
<?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"
android:id="@+id/friends">
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/friends"
app:startDestination="@+id/friendsFragment">
<fragment
android:id="@+id/friendsFragment"
android:name="com.meloda.fast.fragment.friends.FriendsFragment"
android:label="FriendsFragment"
tools:layout="@layout/fragment_friends" />
</navigation>
+14
View File
@@ -0,0 +1,14 @@
<?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/important"
app:startDestination="@id/importantFragment">
<fragment
android:id="@+id/importantFragment"
android:name="com.meloda.fast.fragment.important.ImportantFragment"
android:label="ImportantFragment"
tools:layout="@layout/fragment_important" />
</navigation>
+41
View File
@@ -0,0 +1,41 @@
<?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/login"
app:startDestination="@id/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.meloda.fast.fragment.login.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/toMain"
app:destination="@id/mainFragment"
app:popUpTo="@id/loginFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/toValidation"
app:destination="@id/validationFragment" />
</fragment>
<fragment
android:id="@+id/validationFragment"
android:name="com.meloda.fast.fragment.login.ValidationFragment"
android:label="ValidationFragment"
tools:layout="@layout/fragment_validation">
<action
android:id="@+id/toLogin"
app:destination="@id/loginFragment"
app:popUpTo="@id/validationFragment"
app:popUpToInclusive="true" />
</fragment>
</navigation>
+8 -2
View File
@@ -3,13 +3,19 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
app:startDestination="@id/loginFragment">
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.meloda.fast.fragment.main.MainFragment"
android:label="MainFragment"
tools:layout="@layout/fragment_main" />
tools:layout="@layout/fragment_main">
<action
android:id="@+id/toLogin"
app:destination="@id/loginFragment" />
</fragment>
<fragment
android:id="@+id/loginFragment"
+9 -1
View File
@@ -1,6 +1,14 @@
<?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"
android:id="@+id/messages">
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/messages"
app:startDestination="@+id/conversationsFragment">
<fragment
android:id="@+id/conversationsFragment"
android:name="com.meloda.fast.fragment.messages.ConversationsFragment"
android:label="ConversationsFragment"
tools:layout="@layout/fragment_conversations" />
</navigation>
+73
View File
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">
<item name="colorPrimary">@color/dark_primary</item>
<item name="colorPrimaryDark">@color/dark_primaryDark</item>
<item name="colorAccent">@color/dark_accent</item>
<item name="toolbarStyle">@style/AppTheme.Toolbar</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
<item name="dialogCornerRadius">12dp</item>
<item name="android:windowBackground">@color/dark_background</item>
<item name="itemTitleColor">?colorAccent</item>
<item name="dividerHorizontal">@color/dark_divider</item>
<item name="textColorSecondary">@color/dark_textSecondary</item>
<item name="editTextFilledBackgroundColor">@color/dark_edittext_filled_background</item>
<item name="messageInTextColor">@color/dark_message_in</item>
<item name="messageOutTextColor">@color/dark_message_out</item>
<item name="android:windowAnimationStyle">@style/AppTheme.ActivityAnimation</item>
<item name="android:navigationBarColor">@color/dark_navigationBar</item>
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">
@android:color/transparent
</item>
</style>
<style name="AppTheme.Toolbar" parent="Widget.MaterialComponents.Toolbar.PrimarySurface">
<item name="titleTextAppearance">@style/Toolbar.Title</item>
<item name="android:textSize">24sp</item>
<item name="android:elevation">3dp</item>
<item name="titleTextColor">@color/dark_accent</item>
</style>
<style name="AppTheme.Login.EditText" parent="">
<item name="android:layout_height">52dp</item>
<item name="android:background">@drawable/edittext_filled_background</item>
<item name="android:paddingStart">16dp</item>
<item name="android:paddingEnd">16dp</item>
<item name="android:layout_marginEnd">16dp</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:textColorPrimary</item>
<item name="android:textColorHint">?textColorSecondary</item>
<item name="fontFamily">@font/google_sans_regular</item>
<item name="android:singleLine">true</item>
<item name="android:maxLines">1</item>
</style>
<style name="AppTheme.FullScreenDialog" parent="Theme.MaterialComponents.DayNight.Dialog.Bridge">
<item name="colorPrimary">@color/dark_primary</item>
<item name="colorPrimaryDark">@color/dark_primaryDark</item>
<item name="colorAccent">@color/dark_accent</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowBackground">@color/dark_background</item>
<item name="actionMenuTextColor">?colorAccent</item>
<item name="android:navigationBarColor" tools:targetApi="o_mr1">@color/dark_navigationBar</item>
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">
@android:color/transparent
</item>
<item name="colorControlNormal">?colorAccent</item>
</style>
<style name="AppTheme.ProfileDialog" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
<item name="colorAccent">@color/dark_accent</item>
<item name="colorPrimary">@color/dark_primary</item>
<item name="colorPrimaryDark">@color/dark_primaryDark</item>
</style>
</resources>
+6 -6
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar.Bridge">
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
<item name="colorAccent">@color/accent</item>
@@ -27,8 +27,6 @@
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">
@android:color/transparent
</item>
</style>
<style name="AppTheme.Toolbar" parent="Widget.MaterialComponents.Toolbar.PrimarySurface">
@@ -59,15 +57,16 @@
<item name="android:maxLines">1</item>
</style>
<style name="AppTheme.FullScreenDialog" parent="Theme.MaterialComponents.Light.Dialog.Bridge">
<style name="AppTheme.FullScreenDialog" parent="Theme.MaterialComponents.DayNight.Dialog.Bridge">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
<item name="colorAccent">@color/accent</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowBackground">@color/background</item>
<item name="actionMenuTextColor">?colorAccent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor" tools:targetApi="o_mr1">?colorPrimaryDark</item>
<item name="android:navigationBarColor" tools:targetApi="o_mr1">@color/navigationBar</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">
@android:color/transparent
@@ -82,10 +81,11 @@
<item name="android:layout_marginEnd">12dp</item>
</style>
<style name="AppTheme.ProfileDialog" parent="Theme.MaterialComponents.Light.BottomSheetDialog">
<style name="AppTheme.ProfileDialog" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
<item name="colorAccent">@color/accent</item>
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
<item name="android:windowLightStatusBar">true</item>
</style>
<style name="Widget.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">