Avoid memory leak in ExceptionResolver

(cherry picked from commit 7a3b2a9bb4)
devel
Koitharu 6 months ago
parent 881f154b5e
commit cceaefc896
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -8,9 +8,10 @@ import androidx.collection.MutableScatterMap
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.async
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
@ -32,14 +33,15 @@ import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredExcept
import org.koitharu.kotatsu.scrobbling.common.ui.ScrobblerAuthHelper
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import java.security.cert.CertPathValidatorException
import javax.inject.Inject
import javax.inject.Provider
import javax.net.ssl.SSLException
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class ExceptionResolver @AssistedInject constructor(
@Assisted private val host: Host,
class ExceptionResolver private constructor(
private val host: Host,
private val settings: AppSettings,
private val scrobblerAuthHelperProvider: Provider<ScrobblerAuthHelper>,
) {
@ -56,10 +58,11 @@ class ExceptionResolver @AssistedInject constructor(
}
fun showErrorDetails(e: Throwable, url: String? = null) {
host.router()?.showErrorDialog(e, url)
host.router.showErrorDialog(e, url)
}
suspend fun resolve(e: Throwable): Boolean = when (e) {
suspend fun resolve(e: Throwable): Boolean = host.lifecycleScope.async {
when (e) {
is CloudFlareProtectedException -> resolveCF(e)
is AuthRequiredException -> resolveAuthException(e.source)
is SSLException,
@ -71,7 +74,7 @@ class ExceptionResolver @AssistedInject constructor(
is InteractiveActionRequiredException -> resolveBrowserAction(e)
is ProxyConfigException -> {
host.router()?.openProxySettings()
host.router.openProxySettings()
false
}
@ -99,6 +102,7 @@ class ExceptionResolver @AssistedInject constructor(
else -> false
}
}.await()
private suspend fun resolveBrowserAction(
e: InteractiveActionRequiredException
@ -118,11 +122,11 @@ class ExceptionResolver @AssistedInject constructor(
}
private fun openInBrowser(url: String) {
host.router()?.openBrowser(url, null, null)
host.router.openBrowser(url, null, null)
}
private fun openAlternatives(manga: Manga) {
host.router()?.openAlternatives(manga)
host.router.openAlternatives(manga)
}
private fun handleActivityResult(tag: String, result: Boolean) {
@ -130,7 +134,7 @@ class ExceptionResolver @AssistedInject constructor(
}
private fun showSslErrorDialog() {
val ctx = host.getContext() ?: return
val ctx = host.context ?: return
if (settings.isSSLBypassEnabled) {
Toast.makeText(ctx, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
return
@ -147,27 +151,65 @@ class ExceptionResolver @AssistedInject constructor(
}.show()
}
private inline fun Host.withContext(block: Context.() -> Unit) {
getContext()?.apply(block)
class Factory @Inject constructor(
private val settings: AppSettings,
private val scrobblerAuthHelperProvider: Provider<ScrobblerAuthHelper>,
) {
fun create(fragment: Fragment) = ExceptionResolver(
host = Host.FragmentHost(fragment),
settings = settings,
scrobblerAuthHelperProvider = scrobblerAuthHelperProvider,
)
fun create(activity: FragmentActivity) = ExceptionResolver(
host = Host.ActivityHost(activity),
settings = settings,
scrobblerAuthHelperProvider = scrobblerAuthHelperProvider,
)
}
private fun Host.router(): AppRouter? = when (this) {
is FragmentActivity -> router
is Fragment -> router
else -> null
private sealed interface Host : ActivityResultCaller, LifecycleOwner {
val context: Context?
val router: AppRouter
val fragmentManager: FragmentManager
inline fun withContext(block: Context.() -> Unit) {
context?.apply(block)
}
interface Host : ActivityResultCaller {
class ActivityHost(val activity: FragmentActivity) : Host,
ActivityResultCaller by activity,
LifecycleOwner by activity {
override val context: Context
get() = activity
fun getChildFragmentManager(): FragmentManager
override val router: AppRouter
get() = activity.router
fun getContext(): Context?
override val fragmentManager: FragmentManager
get() = activity.supportFragmentManager
}
@AssistedFactory
interface Factory {
class FragmentHost(val fragment: Fragment) : Host,
ActivityResultCaller by fragment {
override val context: Context?
get() = fragment.context
fun create(host: Host): ExceptionResolver
override val router: AppRouter
get() = fragment.router
override val fragmentManager: FragmentManager
get() = fragment.childFragmentManager
override val lifecycle: Lifecycle
get() = fragment.viewLifecycleOwner.lifecycle
}
}
companion object {

@ -33,7 +33,6 @@ import androidx.appcompat.R as appcompatR
abstract class BaseActivity<B : ViewBinding> :
AppCompatActivity(),
ExceptionResolver.Host,
OnApplyWindowInsetsListener,
ScreenshotPolicyHelper.ContentContainer {
@ -87,10 +86,6 @@ abstract class BaseActivity<B : ViewBinding> :
@Deprecated("Use ViewBinding", level = DeprecationLevel.ERROR)
override fun setContentView(view: View?) = throw UnsupportedOperationException()
override fun getContext() = this
override fun getChildFragmentManager(): FragmentManager = supportFragmentManager
protected fun setContentView(binding: B) {
this.viewBinding = binding
super.setContentView(binding.root)

@ -15,8 +15,7 @@ import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate
abstract class BaseFragment<B : ViewBinding> :
OnApplyWindowInsetsListener,
Fragment(),
ExceptionResolver.Host {
Fragment() {
var viewBinding: B? = null
private set

@ -36,8 +36,7 @@ import com.google.android.material.R as materialR
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
PreferenceFragmentCompat(),
OnApplyWindowInsetsListener,
RecyclerViewOwner,
ExceptionResolver.Host {
RecyclerViewOwner {
protected lateinit var exceptionResolver: ExceptionResolver
private set

@ -32,8 +32,7 @@ import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate
import com.google.android.material.R as materialR
abstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment(),
OnApplyWindowInsetsListener,
ExceptionResolver.Host {
OnApplyWindowInsetsListener {
private var waitingForDismissAllowingStateLoss = false
private var isFitToContentsDisabled = false

Loading…
Cancel
Save