Remove AsyncLayoutInflater; fixes

This commit is contained in:
Koitharu
2022-03-29 19:54:30 +03:00
parent b57e4c520b
commit 8b5a985842
13 changed files with 60 additions and 149 deletions

View File

@@ -14,8 +14,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 32
versionCode 399
versionName '3.0-alpha1'
versionCode 400
versionName '3.0'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -65,7 +65,7 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'com.github.nv95:kotatsu-parsers:1ba2bba12e'
implementation 'com.github.nv95:kotatsu-parsers:3ea7e92e64'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
@@ -82,7 +82,6 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.asynclayoutinflater:asynclayoutinflater:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'com.google.android.material:material:1.6.0-beta01'

View File

@@ -3,20 +3,12 @@ package org.koitharu.kotatsu.core.ui
import android.content.Context
import android.content.Intent
import android.util.Log
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.system.exitProcess
class AppCrashHandler(private val applicationContext: Context) : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread, e: Throwable) {
val crashInfo = buildString {
val writer = StringWriter()
e.printStackTrace(PrintWriter(writer))
append(writer.toString().trimIndent())
}
val intent = Intent(applicationContext, CrashActivity::class.java)
intent.putExtra(Intent.EXTRA_TEXT, crashInfo)
val intent = CrashActivity.newIntent(applicationContext, e)
intent.flags = (Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
try {
applicationContext.startActivity(intent)

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.ui
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@@ -11,6 +12,7 @@ import android.view.View
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ActivityCrashBinding
import org.koitharu.kotatsu.main.ui.MainActivity
import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.utils.ShareHelper
class CrashActivity : Activity(), View.OnClickListener {
@@ -63,4 +65,19 @@ class CrashActivity : Activity(), View.OnClickListener {
}
}
}
companion object {
private const val MAX_TRACE_SIZE = 131071
fun newIntent(context: Context, error: Throwable): Intent {
val crashInfo = error
.stackTraceToString()
.trimIndent()
.ellipsize(MAX_TRACE_SIZE)
val intent = Intent(context, CrashActivity::class.java)
intent.putExtra(Intent.EXTRA_TEXT, crashInfo)
return intent
}
}
}

View File

@@ -8,6 +8,7 @@ import coil.ImageLoader
import coil.request.ImageRequest
import coil.size.Scale
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import okhttp3.OkHttpClient
@@ -39,7 +40,7 @@ class DownloadManager(
private val localMangaRepository: LocalMangaRepository,
) {
private val connectivityManager = context.getSystemService(
private val connectivityManager = context.applicationContext.getSystemService(
Context.CONNECTIVITY_SERVICE
) as ConnectivityManager
private val coverWidth = context.resources.getDimensionPixelSize(
@@ -49,7 +50,7 @@ class DownloadManager(
androidx.core.R.dimen.compat_notification_large_icon_max_height
)
fun downloadManga(manga: Manga, chaptersIds: Set<Long>?, startId: Int) = flow<State> {
fun downloadManga(manga: Manga, chaptersIds: Set<Long>?, startId: Int): Flow<State> = flow {
emit(State.Preparing(startId, manga, null))
var cover: Drawable? = null
val destination = localMangaRepository.getOutputDir()
@@ -102,13 +103,15 @@ class DownloadManager(
}
} while (false)
emit(State.Progress(
startId, manga, cover,
totalChapters = chapters.size,
currentChapter = chapterIndex,
totalPages = pages.size,
currentPage = pageIndex,
))
emit(
State.Progress(
startId, manga, cover,
totalChapters = chapters.size,
currentChapter = chapterIndex,
totalPages = pages.size,
currentPage = pageIndex,
)
)
}
}
}
@@ -191,7 +194,7 @@ class DownloadManager(
val currentChapter: Int,
val totalPages: Int,
val currentPage: Int,
): State {
) : State {
val max: Int = totalChapters * totalPages
@@ -204,7 +207,7 @@ class DownloadManager(
override val startId: Int,
override val manga: Manga,
override val cover: Drawable?,
): State
) : State
data class Done(
override val startId: Int,
@@ -224,7 +227,7 @@ class DownloadManager(
override val startId: Int,
override val manga: Manga,
override val cover: Drawable?,
): State
) : State
data class PostProcessing(
override val startId: Int,
@@ -232,4 +235,4 @@ class DownloadManager(
override val cover: Drawable?,
) : State
}
}
}

View File

@@ -11,6 +11,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.CrashActivity
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.domain.DownloadManager
import org.koitharu.kotatsu.download.ui.DownloadsActivity
@@ -81,6 +82,14 @@ class DownloadNotification(
builder.setSubText(context.getString(R.string.error))
builder.setContentText(message)
builder.setAutoCancel(true)
builder.setContentIntent(
PendingIntent.getActivity(
context,
state.manga.hashCode(),
CrashActivity.newIntent(context, state.error),
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
)
builder.setCategory(NotificationCompat.CATEGORY_ERROR)
builder.setStyle(NotificationCompat.BigTextStyle().bigText(message))
}

View File

@@ -15,6 +15,7 @@ import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@@ -105,6 +106,7 @@ class DownloadService : BaseService() {
try {
withContext(Dispatchers.Default) {
downloadManager.downloadManga(manga, chaptersIds, startId)
.distinctUntilChanged()
.collect { state ->
stateFlow.value = state
notificationManager.notify(startId, notification.create(state))
@@ -181,7 +183,7 @@ class DownloadService : BaseService() {
}
fun getCancelIntent(startId: Int) = Intent(ACTION_DOWNLOAD_CANCEL)
.putExtra(ACTION_DOWNLOAD_CANCEL, startId)
.putExtra(EXTRA_CANCEL_ID, startId)
private fun confirmDataTransfer(context: Context, callback: () -> Unit) {
val settings = GlobalContext.get().get<AppSettings>()

View File

@@ -24,7 +24,6 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.AsyncViewFactory
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter.Companion.ITEM_TYPE_MANGA_GRID
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
@@ -35,12 +34,10 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.utils.ext.*
private const val PREFETCH_ITEM_LIST = 10
private const val PREFETCH_ITEM_DETAILED = 8
private const val PREFETCH_ITEM_GRID = 16
abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
PaginationScrollListener.Callback, MangaListListener,
abstract class MangaListFragment :
BaseFragment<FragmentListBinding>(),
PaginationScrollListener.Callback,
MangaListListener,
SwipeRefreshLayout.OnRefreshListener {
private var listAdapter: MangaListAdapter? = null
@@ -50,7 +47,6 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
private val listCommitCallback = Runnable {
spanSizeLookup.invalidateCache()
}
private var asyncViewFactory: AsyncViewFactory? = null
open val isSwipeRefreshEnabled = true
protected abstract val viewModel: MangaListViewModel
@@ -67,12 +63,10 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
asyncViewFactory = AsyncViewFactory(binding.recyclerView)
listAdapter = MangaListAdapter(
coil = get(),
lifecycleOwner = viewLifecycleOwner,
listener = this,
viewFactory = checkNotNull(asyncViewFactory),
)
paginationListener = PaginationScrollListener(4, this)
with(binding.recyclerView) {
@@ -97,8 +91,6 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
override fun onDestroyView() {
listAdapter = null
paginationListener = null
asyncViewFactory?.clear()
asyncViewFactory = null
spanSizeLookup.invalidateCache()
super.onDestroyView()
}
@@ -172,7 +164,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
@CallSuper
protected open fun onLoadingStateChanged(isLoading: Boolean) {
binding.swipeRefreshLayout.isEnabled = binding.swipeRefreshLayout.isRefreshing ||
isSwipeRefreshEnabled && !isLoading
isSwipeRefreshEnabled && !isLoading
if (!isLoading) {
binding.swipeRefreshLayout.isRefreshing = false
}
@@ -219,26 +211,18 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
with(binding.recyclerView) {
clearItemDecorations()
removeOnLayoutChangeListener(spanResolver)
asyncViewFactory?.clear()
val isListPending = viewModel.isListPending()
when (mode) {
ListMode.LIST -> {
layoutManager = FitHeightLinearLayoutManager(context)
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
addItemDecoration(SpacingItemDecoration(spacing))
updatePadding(left = spacing, right = spacing)
if (isListPending) {
asyncViewFactory?.prefetch(R.layout.item_manga_list, PREFETCH_ITEM_LIST)
}
}
ListMode.DETAILED_LIST -> {
layoutManager = FitHeightLinearLayoutManager(context)
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
updatePadding(left = spacing, right = spacing)
addItemDecoration(SpacingItemDecoration(spacing))
if (isListPending) {
asyncViewFactory?.prefetch(R.layout.item_manga_list_details, PREFETCH_ITEM_DETAILED)
}
}
ListMode.GRID -> {
layoutManager = FitHeightGridLayoutManager(context, spanResolver.spanCount).also {
@@ -248,9 +232,6 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
addItemDecoration(SpacingItemDecoration(spacing))
updatePadding(left = spacing, right = spacing)
addOnLayoutChangeListener(spanResolver)
if (isListPending) {
asyncViewFactory?.prefetch(R.layout.item_manga_grid, PREFETCH_ITEM_GRID)
}
}
}
}

View File

@@ -44,10 +44,4 @@ abstract class MangaListViewModel(
abstract fun onRefresh()
abstract fun onRetry()
fun isListPending(): Boolean {
return content.value?.any {
it is MangaListModel || it is MangaGridModel || it is MangaListDetailedModel
} != true
}
}

View File

@@ -1,58 +0,0 @@
package org.koitharu.kotatsu.list.ui.adapter
import android.util.Log
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.util.valueIterator
import org.koitharu.kotatsu.BuildConfig
import java.util.*
class AsyncViewFactory(private val parent: ViewGroup) : AsyncLayoutInflater.OnInflateFinishedListener {
private val asyncInflater = AsyncLayoutInflater(parent.context)
private val pool = SparseArray<LinkedList<View>>()
override fun onInflateFinished(view: View, resid: Int, parent: ViewGroup?) {
var list = pool.get(resid)
if (list != null) {
list.addLast(view)
} else {
list = LinkedList()
list.add(view)
pool.put(resid, list)
}
}
fun clear() {
if (BuildConfig.DEBUG) {
pool.valueIterator().forEach {
if (it.isNotEmpty()) {
Log.w("AsyncViewFactory", "You have ${it.size} unconsumed prefetched items")
}
}
}
pool.clear()
}
fun prefetch(@LayoutRes resId: Int, count: Int) {
if (count <= 0) return
repeat(count) {
asyncInflater.inflate(resId, parent, this)
}
}
operator fun get(@LayoutRes resId: Int): View? {
val result = pool.get(resId)?.removeFirstOrNull()
if (BuildConfig.DEBUG && result == null) {
Log.w("AsyncViewFactory", "Item requested but missing")
}
return result
}
fun getCount(@LayoutRes resId: Int): Int {
return pool[resId]?.size ?: 0
}
}

View File

@@ -20,15 +20,8 @@ fun mangaGridItemAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Manga>,
viewFactory: AsyncViewFactory,
) = adapterDelegateViewBinding<MangaGridModel, ListModel, ItemMangaGridBinding>(
{ inflater, parent ->
viewFactory[R.layout.item_manga_grid]?.let {
ItemMangaGridBinding.bind(it)
} ?: run {
ItemMangaGridBinding.inflate(inflater, parent, false)
}
}
{ inflater, parent -> ItemMangaGridBinding.inflate(inflater, parent, false) }
) {
var imageRequest: Disposable? = null

View File

@@ -12,20 +12,13 @@ class MangaListAdapter(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
listener: MangaListListener,
viewFactory: AsyncViewFactory,
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
init {
delegatesManager
.addDelegate(
ITEM_TYPE_MANGA_LIST,
mangaListItemAD(coil, lifecycleOwner, listener, viewFactory)
)
.addDelegate(
ITEM_TYPE_MANGA_LIST_DETAILED,
mangaListDetailedItemAD(coil, lifecycleOwner, listener, viewFactory)
)
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, listener, viewFactory))
.addDelegate(ITEM_TYPE_MANGA_LIST, mangaListItemAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_MANGA_LIST_DETAILED, mangaListDetailedItemAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD())
.addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD())
.addDelegate(ITEM_TYPE_DATE, relatedDateItemAD())

View File

@@ -21,15 +21,8 @@ fun mangaListDetailedItemAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Manga>,
viewFactory: AsyncViewFactory,
) = adapterDelegateViewBinding<MangaListDetailedModel, ListModel, ItemMangaListDetailsBinding>(
{ inflater, parent ->
viewFactory[R.layout.item_manga_list_details]?.let {
ItemMangaListDetailsBinding.bind(it)
} ?: run {
ItemMangaListDetailsBinding.inflate(inflater, parent, false)
}
}
{ inflater, parent -> ItemMangaListDetailsBinding.inflate(inflater, parent, false) }
) {
var imageRequest: Disposable? = null

View File

@@ -21,15 +21,8 @@ fun mangaListItemAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Manga>,
viewFactory: AsyncViewFactory,
) = adapterDelegateViewBinding<MangaListModel, ListModel, ItemMangaListBinding>(
{ inflater, parent ->
viewFactory[R.layout.item_manga_list]?.let {
ItemMangaListBinding.bind(it)
} ?: run {
ItemMangaListBinding.inflate(inflater, parent, false)
}
}
{ inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) }
) {
var imageRequest: Disposable? = null