Files
michas-droid/src/main/kotlin/com/michatec/store/screen/ScreenActivity.kt
T

261 lines
9.1 KiB
Kotlin

package com.michatec.store.screen
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcel
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout
import android.widget.Toolbar
import androidx.activity.OnBackPressedCallback
import androidx.core.os.BundleCompat
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.michatec.store.BuildConfig
import com.michatec.store.R
import com.michatec.store.content.Cache
import com.michatec.store.content.Preferences
import com.michatec.store.database.CursorOwner
import com.michatec.store.utility.KParcelable
import com.michatec.store.utility.Utils
import com.michatec.store.utility.extension.android.*
import com.michatec.store.utility.extension.resources.*
import com.michatec.store.utility.extension.text.*
import org.woheller69.freeDroidWarn.FreeDroidWarn
abstract class ScreenActivity: FragmentActivity() {
companion object {
private const val STATE_FRAGMENT_STACK = "fragmentStack"
}
sealed class SpecialIntent {
object Updates: SpecialIntent()
class Install(val packageName: String?, val cacheFileName: String?): SpecialIntent()
}
private class FragmentStackItem(val className: String, val arguments: Bundle?,
val savedState: Fragment.SavedState?): KParcelable {
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(className)
dest.writeByte(if (arguments != null) 1 else 0)
arguments?.writeToParcel(dest, flags)
dest.writeByte(if (savedState != null) 1 else 0)
savedState?.writeToParcel(dest, flags)
}
companion object {
@Suppress("unused") @JvmField val CREATOR = KParcelable.creator {
val className = it.readString()!!
val arguments = if (it.readByte().toInt() == 0) null else Bundle.CREATOR.createFromParcel(it)
arguments?.classLoader = ScreenActivity::class.java.classLoader
val savedState = if (it.readByte().toInt() == 0) null else Fragment.SavedState.CREATOR.createFromParcel(it)
FragmentStackItem(className, arguments, savedState)
}
}
}
lateinit var cursorOwner: CursorOwner
private set
private val fragmentStack = mutableListOf<FragmentStackItem>()
private val currentFragment: Fragment?
get() {
supportFragmentManager.executePendingTransactions()
return supportFragmentManager.findFragmentById(R.id.main_content)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(Utils.configureLocale(base))
}
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(Preferences[Preferences.Key.Theme].getResId(resources.configuration))
super.onCreate(savedInstanceState)
FreeDroidWarn.showWarningOnUpgrade(this, BuildConfig.VERSION_CODE)
WindowCompat.setDecorFitsSystemWindows(window, false)
addContentView(FrameLayout(this).apply { id = R.id.main_content },
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
if (savedInstanceState == null) {
cursorOwner = CursorOwner()
supportFragmentManager.beginTransaction()
.add(cursorOwner, CursorOwner::class.java.name)
.commit()
} else {
cursorOwner = supportFragmentManager
.findFragmentByTag(CursorOwner::class.java.name) as CursorOwner
}
savedInstanceState?.let {
BundleCompat.getParcelableArrayList(it, STATE_FRAGMENT_STACK, FragmentStackItem::class.java)
?.let { list -> fragmentStack += list }
}
if (savedInstanceState == null) {
replaceFragment(TabsFragment(), null)
if ((intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
handleIntent(intent)
}
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val currentFragment = currentFragment
if (!(currentFragment is ScreenFragment && currentFragment.onBackPressed())) {
hideKeyboard()
if (!popFragment()) {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
}
})
supportFragmentManager.addFragmentOnAttachListener { _, _ ->
hideKeyboard()
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelableArrayList(STATE_FRAGMENT_STACK, ArrayList(fragmentStack))
}
private fun replaceFragment(fragment: Fragment, open: Boolean?) {
if (open != null) {
currentFragment?.view?.translationZ = (if (open) -1f else 1f)
}
supportFragmentManager
.beginTransaction()
.apply {
if (open != null) {
setCustomAnimations(if (open) R.animator.slide_in else 0,
if (open) R.animator.slide_in_keep else R.animator.slide_out)
}
}
.replace(R.id.main_content, fragment)
.commit()
}
private fun pushFragment(fragment: Fragment) {
currentFragment?.let { fragmentStack.add(FragmentStackItem(it::class.java.name, it.arguments,
supportFragmentManager.saveFragmentInstanceState(it))) }
replaceFragment(fragment, true)
}
private fun popFragment(): Boolean {
return fragmentStack.isNotEmpty() && run {
val stackItem = fragmentStack.removeAt(fragmentStack.size - 1)
val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, stackItem.className)
stackItem.arguments?.let(fragment::setArguments)
stackItem.savedState?.let(fragment::setInitialSavedState)
replaceFragment(fragment, false)
true
}
}
private fun hideKeyboard() {
(getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager)
?.hideSoftInputFromWindow((currentFocus ?: window.decorView).windowToken, 0)
}
internal fun onToolbarCreated(toolbar: Toolbar) {
if (fragmentStack.isNotEmpty()) {
toolbar.navigationIcon = toolbar.context.getDrawableFromAttr(android.R.attr.homeAsUpIndicator)
toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
handleIntent(intent)
}
protected val Intent.packageName: String?
get() {
val uri = data
return when {
uri?.scheme == "package" || uri?.scheme == "fdroid.app" -> {
uri.schemeSpecificPart?.nullIfEmpty()
}
uri?.scheme == "market" && uri.host == "details" -> {
uri.getQueryParameter("id")?.nullIfEmpty()
}
uri != null && uri.scheme in setOf("http", "https") -> {
val host = uri.host.orEmpty()
if (host == "f-droid.org" || host.endsWith(".f-droid.org")) {
uri.lastPathSegment?.nullIfEmpty()
} else {
null
}
}
else -> {
null
}
}
}
protected fun handleSpecialIntent(specialIntent: SpecialIntent) {
when (specialIntent) {
is SpecialIntent.Updates -> {
if (currentFragment !is TabsFragment) {
fragmentStack.clear()
replaceFragment(TabsFragment(), true)
}
val tabsFragment = currentFragment as TabsFragment
tabsFragment.selectUpdates()
}
is SpecialIntent.Install -> {
val packageName = specialIntent.packageName
if (!packageName.isNullOrEmpty()) {
val fragment = currentFragment
if (fragment !is ProductFragment || fragment.packageName != packageName) {
pushFragment(ProductFragment(packageName))
}
specialIntent.cacheFileName?.let(::startPackageInstaller)
}
Unit
}
}::class
}
open fun handleIntent(intent: Intent?) {
when (intent?.action) {
Intent.ACTION_VIEW -> {
val packageName = intent.packageName
if (!packageName.isNullOrEmpty()) {
val fragment = currentFragment
if (fragment !is ProductFragment || fragment.packageName != packageName) {
pushFragment(ProductFragment(packageName))
}
}
}
}
}
internal fun startPackageInstaller(cacheFileName: String) {
val (uri, flags) = if (Android.sdk(24)) {
Pair(Cache.getReleaseUri(this, cacheFileName), Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else {
Pair(Uri.fromFile(Cache.getReleaseFile(this, cacheFileName)), 0)
}
startActivity(Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, "application/vnd.android.package-archive")
.addFlags(flags or Intent.FLAG_ACTIVITY_NEW_TASK))
}
internal fun navigateProduct(packageName: String) = pushFragment(ProductFragment(packageName))
internal fun navigateRepositories() = pushFragment(RepositoriesFragment())
internal fun navigatePreferences() = pushFragment(PreferencesFragment())
internal fun navigateAddRepository() = pushFragment(EditRepositoryFragment(null))
internal fun navigateRepository(repositoryId: Long) = pushFragment(RepositoryFragment(repositoryId))
internal fun navigateEditRepository(repositoryId: Long) = pushFragment(EditRepositoryFragment(repositoryId))
}