anytype-kotlin-wild/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseFragment.kt

132 lines
4.2 KiB
Kotlin

package com.anytypeio.anytype.core_utils.ui
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.anytypeio.anytype.core_utils.BuildConfig
import com.anytypeio.anytype.core_utils.ext.throttleFirst
import com.anytypeio.anytype.core_utils.insets.RootViewDeferringInsetsCallback
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
abstract class BaseFragment<T : ViewBinding>(
@LayoutRes private val layout: Int,
private val fragmentScope: Boolean = true
) : Fragment(layout) {
private var applyWindowRootInsets: Boolean = true
protected val handler = Handler(Looper.getMainLooper())
private var _binding: T? = null
val binding: T get() = _binding!!
val hasBinding get() = _binding != null
val jobs = mutableListOf<Job>()
private val throttleFlow = MutableSharedFlow<() -> Unit>(0)
abstract fun injectDependencies()
abstract fun releaseDependencies()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
injectDependencies()
}
override fun onStart() {
super.onStart()
proceed(throttleFlow.throttleFirst(THROTTLE_DURATION)) { it() }
}
protected fun throttle(task: () -> Unit) {
jobs += this.lifecycleScope.launch { throttleFlow.emit { task() } }
}
override fun onStop() {
super.onStop()
handler.removeCallbacksAndMessages(null)
jobs.apply {
forEach { it.cancel() }
clear()
}
}
override fun onDestroy() {
super.onDestroy()
if (fragmentScope) releaseDependencies()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = inflateBinding(inflater, container)
return _binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (applyWindowRootInsets) {
onApplyWindowRootInsets()
}
}
open fun onApplyWindowRootInsets() {
if (BuildConfig.USE_NEW_WINDOW_INSET_API && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val deferringInsetsListener = RootViewDeferringInsetsCallback(
persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
deferredInsetTypes = WindowInsetsCompat.Type.ime()
)
ViewCompat.setWindowInsetsAnimationCallback(binding.root, deferringInsetsListener)
ViewCompat.setOnApplyWindowInsetsListener(binding.root, deferringInsetsListener)
}
}
protected fun DialogFragment.showChildFragment(tag: String? = null) {
jobs += this@BaseFragment.lifecycleScope.launch {
throttleFlow.emit {
runCatching {
show(this@BaseFragment.childFragmentManager, tag)
}.fold(
onSuccess = {
// Do nothing.
},
onFailure = {
Timber.e(it, "Error while navifation")
}
)
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
protected abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?): T
}
fun <T> BaseFragment<*>.proceed(flow: Flow<T>, body: suspend (T) -> Unit) {
jobs += flow.cancellable().onEach { body(it) }.launchIn(lifecycleScope)
}
const val THROTTLE_DURATION = 300L