Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ dependencies {
implementation(libs.lifecycle.viewModel)
implementation(libs.recyclerview)
implementation(libs.sqlite.ktx)
implementation(libs.paging)

implementation(libs.image.viewer)
implementation(libs.bundles.coil)
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.SHOW_APP_INFO" />
</intent-filter>
Expand Down
15 changes: 7 additions & 8 deletions app/src/main/kotlin/com/looker/droidify/Droidify.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,23 @@ import com.looker.droidify.utility.common.log
import com.looker.droidify.utility.extension.toInstalledItem
import com.looker.droidify.work.CleanUpWorker
import dagger.hilt.android.HiltAndroidApp
import java.net.InetSocketAddress
import java.net.Proxy
import javax.inject.Inject
import kotlin.time.Duration.Companion.INFINITE
import kotlin.time.Duration.Companion.hours
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
import java.net.InetSocketAddress
import java.net.Proxy
import javax.inject.Inject
import kotlin.time.Duration.Companion.INFINITE
import kotlin.time.Duration.Companion.hours

@HiltAndroidApp
class Droidify : Application(), SingletonImageLoader.Factory, Configuration.Provider {

private val parentJob = SupervisorJob()
private val appScope = CoroutineScope(Dispatchers.Default + parentJob)
private val appScope: CoroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob())

@Inject
lateinit var settingsRepository: SettingsRepository
Expand Down Expand Up @@ -99,7 +98,7 @@ class Droidify : Application(), SingletonImageLoader.Factory, Configuration.Prov
}

private fun listenApplications() {
appScope.launch(Dispatchers.Default) {
appScope.launch {
registerReceiver(
InstalledAppReceiver(packageManager),
IntentFilter().apply {
Expand Down
126 changes: 53 additions & 73 deletions app/src/main/kotlin/com/looker/droidify/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.looker.droidify
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.OnBackPressedCallback
Expand All @@ -12,9 +11,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import com.looker.droidify.database.CursorOwner
import androidx.recyclerview.widget.RecyclerView
import com.looker.droidify.database.AppListRowViewType
import com.looker.droidify.datastore.SettingsRepository
import com.looker.droidify.datastore.extension.getThemeRes
import com.looker.droidify.datastore.get
Expand All @@ -39,17 +40,15 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import javax.inject.Inject
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.parcelize.Parcelize
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
companion object {
private const val STATE_FRAGMENT_STACK = "fragmentStack"
const val ACTION_UPDATES = "${BuildConfig.APPLICATION_ID}.intent.action.UPDATES"
const val ACTION_INSTALL = "${BuildConfig.APPLICATION_ID}.intent.action.INSTALL"
const val EXTRA_CACHE_FILE_NAME =
Expand All @@ -62,24 +61,17 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var installer: InstallManager

@Parcelize
private class FragmentStackItem(
val className: String, val arguments: Bundle?, val savedState: Fragment.SavedState?,
) : Parcelable

lateinit var cursorOwner: CursorOwner
private set

private var onBackPressedCallback: OnBackPressedCallback? = null

private val fragmentStack = mutableListOf<FragmentStackItem>()

private val currentFragment: Fragment?
get() {
val supportFragmentManager = supportFragmentManager
supportFragmentManager.executePendingTransactions()
return supportFragmentManager.findFragmentById(R.id.main_content)
}

val appListViewPool: RecyclerView.RecycledViewPool = RecyclerView.RecycledViewPool().apply {
setMaxRecycledViews(AppListRowViewType.PRODUCT, 30)
}

@EntryPoint
@InstallIn(SingletonComponent::class)
interface CustomUserRepositoryInjector {
Expand Down Expand Up @@ -130,63 +122,49 @@ class MainActivity : AppCompatActivity() {
hideKeyboard()
}

if (savedInstanceState == null) {
cursorOwner = CursorOwner()
supportFragmentManager.commit {
add(cursorOwner, CursorOwner::class.java.name)
}
} else {
cursorOwner =
supportFragmentManager.findFragmentByTag(CursorOwner::class.java.name) as CursorOwner
}

savedInstanceState?.getParcelableArrayList<FragmentStackItem>(STATE_FRAGMENT_STACK)
?.let { fragmentStack += it }
if (savedInstanceState == null) {
replaceFragment(TabsFragment(), null)
val intent = intent
if ((intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
handleIntent(intent)
}
}

if (SdkCheck.isR) {
window.statusBarColor = resources.getColor(android.R.color.transparent, theme)
window.navigationBarColor = resources.getColor(android.R.color.transparent, theme)
WindowCompat.setDecorFitsSystemWindows(window, false)
}
backHandler()
}

override fun onDestroy() {
super.onDestroy()
onBackPressedCallback = null
setupBackHandler()
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelableArrayList(STATE_FRAGMENT_STACK, ArrayList(fragmentStack))
}

private fun backHandler() {
if (onBackPressedCallback == null) {
onBackPressedCallback = object : OnBackPressedCallback(enabled = false) {
override fun handleOnBackPressed() {
hideKeyboard()
popFragment()
}
private fun setupBackHandler() {
val onBackPressedCallback = object : OnBackPressedCallback(enabled = false) {
override fun handleOnBackPressed() {
hideKeyboard()
popFragment()
}
onBackPressedDispatcher.addCallback(
this,
onBackPressedCallback!!,
)
}
onBackPressedCallback?.isEnabled = fragmentStack.isNotEmpty()

onBackPressedDispatcher.addCallback(
this,
onBackPressedCallback,
)

val supportFragmentManager = supportFragmentManager
onBackPressedCallback.isEnabled = supportFragmentManager.hasBackStackEntry()
supportFragmentManager.addOnBackStackChangedListener {
onBackPressedCallback.isEnabled = supportFragmentManager.hasBackStackEntry()
}
}

private fun replaceFragment(fragment: Fragment, open: Boolean?) {
if (open != null) {
currentFragment?.view?.translationZ =
(if (open) Int.MIN_VALUE else Int.MAX_VALUE).toFloat()
}

supportFragmentManager.commit {
if (open != null) {
setCustomAnimations(
Expand All @@ -196,41 +174,33 @@ class MainActivity : AppCompatActivity() {
}
setReorderingAllowed(true)
replace(R.id.main_content, fragment)

if (fragment !is TabsFragment) {
addToBackStack(null)
}
}
}

private fun pushFragment(fragment: Fragment) {
currentFragment?.let {
fragmentStack.add(
FragmentStackItem(
it::class.java.name,
it.arguments,
supportFragmentManager.saveFragmentInstanceState(it),
),
)
}
replaceFragment(fragment, true)
backHandler()
}

private fun popFragment(): Boolean {
return fragmentStack.isNotEmpty() && run {
val stackItem = fragmentStack.removeAt(fragmentStack.size - 1)
val fragment = Class.forName(stackItem.className).newInstance() as Fragment
stackItem.arguments?.let(fragment::setArguments)
stackItem.savedState?.let(fragment::setInitialSavedState)
replaceFragment(fragment, false)
backHandler()
true
val supportFragmentManager = supportFragmentManager
if (supportFragmentManager.hasBackStackEntry()) {
supportFragmentManager.popBackStack()
return true
}

return false
}

private fun hideKeyboard() {
inputManager?.hideSoftInputFromWindow((currentFocus ?: window.decorView).windowToken, 0)
}

internal fun onToolbarCreated(toolbar: Toolbar) {
if (fragmentStack.isNotEmpty()) {
if (supportFragmentManager.hasBackStackEntry()) {
toolbar.navigationIcon = toolbar.context.homeAsUp
toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
}
Expand All @@ -247,7 +217,6 @@ class MainActivity : AppCompatActivity() {
navigateToTabsFragment()
val tabsFragment = currentFragment as TabsFragment
tabsFragment.selectUpdates()
backHandler()
}

ACTION_INSTALL -> {
Expand Down Expand Up @@ -289,14 +258,21 @@ class MainActivity : AppCompatActivity() {
navigateProduct(packageName)
}
}

}
}
}

private fun clearFragmentBackStack() {
val supportFragmentManager = supportFragmentManager
while (supportFragmentManager.hasBackStackEntry()) {
supportFragmentManager.popBackStack()
}
}

private fun navigateToTabsFragment() {
clearFragmentBackStack()

if (currentFragment !is TabsFragment) {
fragmentStack.clear()
replaceFragment(TabsFragment(), true)
}
}
Expand All @@ -322,3 +298,7 @@ class MainActivity : AppCompatActivity() {
fun navigateEditRepository(repositoryId: Long) =
pushFragment(EditRepositoryFragment(repositoryId, null))
}

private fun FragmentManager.hasBackStackEntry(): Boolean {
return backStackEntryCount > 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ package com.looker.droidify.content

import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.looker.droidify.database.Database
import com.looker.droidify.model.ProductPreference
import com.looker.droidify.utility.common.extension.Json
import com.looker.droidify.utility.common.extension.parseDictionary
import com.looker.droidify.utility.common.extension.writeDictionary
import com.looker.droidify.model.ProductPreference
import com.looker.droidify.database.Database
import com.looker.droidify.utility.serialization.productPreference
import com.looker.droidify.utility.serialization.serialize
import java.io.ByteArrayOutputStream
import java.nio.charset.Charset
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import java.io.ByteArrayOutputStream
import java.nio.charset.Charset

object ProductPreferences {
private val defaultProductPreference = ProductPreference(false, 0L)
Expand All @@ -27,7 +28,7 @@ object ProductPreferences {
Database.LockAdapter.putAll(
preferences.all.keys.mapNotNull { packageName ->
this[packageName].databaseVersionCode?.let { Pair(packageName, it) }
}
},
)
scope.launch {
subject.collect { (packageName, versionCode) ->
Expand Down Expand Up @@ -63,13 +64,15 @@ object ProductPreferences {

operator fun set(packageName: String, productPreference: ProductPreference) {
val oldProductPreference = this[packageName]
preferences.edit().putString(
packageName,
ByteArrayOutputStream().apply {
Json.factory.createGenerator(this)
.use { it.writeDictionary(productPreference::serialize) }
}.toByteArray().toString(Charset.defaultCharset())
).apply()
preferences.edit {
putString(
packageName,
ByteArrayOutputStream().apply {
Json.factory.createGenerator(this)
.use { it.writeDictionary(productPreference::serialize) }
}.toByteArray().toString(Charset.defaultCharset()),
)
}
if (oldProductPreference.ignoreUpdates != productPreference.ignoreUpdates ||
oldProductPreference.ignoreVersionCode != productPreference.ignoreVersionCode
) {
Expand Down
Loading