Remove AsyncLayoutInflater; fixes
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user