Move from java/ to kotlin/ directory
Android 12 dynamic color usage on login screen
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package com.meloda.fast.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
|
||||
object ContextExtensions {
|
||||
|
||||
fun Context.drawable(@DrawableRes resId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(this, resId)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun Context.color(@ColorRes resId: Int): Int {
|
||||
return ContextCompat.getColor(this, resId)
|
||||
}
|
||||
|
||||
fun Context.font(@FontRes resId: Int): Typeface? {
|
||||
return ResourcesCompat.getFont(this, resId)
|
||||
}
|
||||
|
||||
fun Context.string(@StringRes resId: Int): String {
|
||||
return getString(resId)
|
||||
}
|
||||
|
||||
fun Context.view(resId: Int, root: ViewGroup? = null, attachToRoot: Boolean = false): View {
|
||||
return LayoutInflater.from(this).inflate(resId, root, attachToRoot)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.meloda.fast.extensions
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
object DrawableExtensions {
|
||||
|
||||
fun Drawable?.tint(@ColorInt color: Int): Drawable? {
|
||||
this?.setTint(color)
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.meloda.fast.extensions
|
||||
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object FloatExtensions {
|
||||
|
||||
fun Float.int(): Int {
|
||||
return roundToInt()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.meloda.fast.extensions
|
||||
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
||||
object LiveDataExtensions {
|
||||
|
||||
operator fun <T> MutableLiveData<MutableList<T>>.set(position: Int, v: T) {
|
||||
val value = (this.value ?: arrayListOf()).apply { this[position] = v }
|
||||
this.value = value
|
||||
}
|
||||
|
||||
operator fun <T> MutableLiveData<MutableList<T>>.get(position: Int): T {
|
||||
return (value as MutableList<T>)[position]
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun <T> MutableLiveData<MutableList<T>>.add(v: T, position: Int = -1) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
if (position == -1) this.add(v) else this.add(position, v)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun <T> MutableLiveData<MutableList<T>>.addAll(values: List<T>, position: Int = -1) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
if (position == -1) this.addAll(values)
|
||||
else this.addAll(position, values)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
@Suppress("TYPE_INFERENCE_ONLY_INPUT_TYPES_WARNING")
|
||||
fun <T> MutableLiveData<MutableList<T>>.removeAll(values: List<T>) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
this.removeAll(values)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
fun <T> MutableLiveData<MutableList<T>>.removeAt(index: Int) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
this.removeAt(index)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
fun <T> MutableLiveData<MutableList<T>>.remove(item: T) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
this.remove(item)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
operator fun <T> MutableLiveData<MutableList<T>>.iterator(): Iterator<T> {
|
||||
return (value as MutableList<T>).iterator()
|
||||
}
|
||||
|
||||
fun <T> MutableLiveData<MutableList<T>>.clear() {
|
||||
value = arrayListOf()
|
||||
}
|
||||
|
||||
val <T> MutableLiveData<MutableList<T>>.indices get() = (value as MutableList<T>).indices
|
||||
|
||||
val <T> MutableLiveData<MutableList<T>>.size get() = (value as MutableList<T>).size
|
||||
|
||||
fun <T> MutableLiveData<MutableList<T>>.isEmpty(): Boolean {
|
||||
return (value as MutableList<T>).isEmpty()
|
||||
}
|
||||
|
||||
fun <T> MutableLiveData<MutableList<T>>.isNotEmpty(): Boolean {
|
||||
return !isEmpty()
|
||||
}
|
||||
|
||||
fun <T> MutableLiveData<MutableList<T>>.requireValue() = value!!
|
||||
|
||||
@UiThread
|
||||
operator fun <T> MutableLiveData<MutableList<T>>.plusAssign(values: List<T>) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
this.addAll(values)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
operator fun <T> MutableLiveData<MutableList<T>>.plusAssign(v: T) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
this.add(v)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
operator fun <T> MutableLiveData<MutableList<T>>.minusAssign(values: List<T>) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
this.removeAll(values)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
operator fun <T> MutableLiveData<MutableList<T>>.minusAssign(v: T) {
|
||||
val value = (this.value ?: arrayListOf()).apply {
|
||||
this.remove(v)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package com.meloda.fast.extensions
|
||||
|
||||
import android.content.Intent
|
||||
import android.util.SparseArray
|
||||
import androidx.core.util.forEach
|
||||
import androidx.fragment.app.Fragment
|
||||
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
|
||||
import com.meloda.fast.UserConfig
|
||||
|
||||
/**
|
||||
* Manages the various graphs needed for a [BottomNavigationView].
|
||||
*
|
||||
* This sample is a workaround until the Navigation Component supports multiple back stacks.
|
||||
*/
|
||||
object NavigationExtensions {
|
||||
|
||||
fun BottomNavigationView.setupWithNavController(
|
||||
navGraphIds: List<Int>,
|
||||
fragmentManager: FragmentManager,
|
||||
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
|
||||
|
||||
setOnItemSelectedListener { 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 newlySelectedItemTag = //graphIdToTagMap[item.itemId]
|
||||
if (!UserConfig.isLoggedIn()) graphIdToTagMap[R.id.login] else graphIdToTagMap[item.itemId]
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setOnItemReselectedListener { 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]) {
|
||||
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
|
||||
}
|
||||
|
||||
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,11 @@
|
||||
package com.meloda.fast.extensions
|
||||
|
||||
import java.util.*
|
||||
|
||||
object StringExtensions {
|
||||
|
||||
fun String.lowerCase(): String {
|
||||
return toLowerCase(Locale.getDefault())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.meloda.fast.extensions
|
||||
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
||||
object TextViewExtensions {
|
||||
|
||||
fun TextView.clear() {
|
||||
text = ""
|
||||
}
|
||||
|
||||
fun TextInputLayout.clear() {
|
||||
editText?.setText("")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user