Remove unused code

pull/26/head
Koitharu 5 years ago
parent 49eebdf554
commit 5b9922d509

@ -15,9 +15,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.AppCrashHandler import org.koitharu.kotatsu.core.ui.AppCrashHandler
import org.koitharu.kotatsu.core.ui.uiModule import org.koitharu.kotatsu.core.ui.uiModule
import org.koitharu.kotatsu.details.detailsModule import org.koitharu.kotatsu.details.detailsModule
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.favouritesModule import org.koitharu.kotatsu.favourites.favouritesModule
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.historyModule import org.koitharu.kotatsu.history.historyModule
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
@ -37,29 +35,15 @@ class KotatsuApp : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy( enableStrictMode()
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.setClassInstanceLimit(LocalMangaRepository::class.java, 1)
.setClassInstanceLimit(PagesCache::class.java, 1)
.setClassInstanceLimit(MangaLoaderContext::class.java, 1)
.penaltyLog()
.build()
)
} }
initKoin() initKoin()
Thread.setDefaultUncaughtExceptionHandler(AppCrashHandler(applicationContext)) Thread.setDefaultUncaughtExceptionHandler(AppCrashHandler(applicationContext))
AppCompatDelegate.setDefaultNightMode(get<AppSettings>().theme) AppCompatDelegate.setDefaultNightMode(get<AppSettings>().theme)
registerActivityLifecycleCallbacks(get<AppProtectHelper>()) registerActivityLifecycleCallbacks(get<AppProtectHelper>())
val widgetUpdater = WidgetUpdater(applicationContext) val widgetUpdater = WidgetUpdater(applicationContext)
FavouritesRepository.subscribe(widgetUpdater) widgetUpdater.subscribeToFavourites(get())
HistoryRepository.subscribe(widgetUpdater) widgetUpdater.subscribeToHistory(get())
} }
private fun initKoin() { private fun initKoin() {
@ -85,4 +69,22 @@ class KotatsuApp : Application() {
) )
} }
} }
private fun enableStrictMode() {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.setClassInstanceLimit(LocalMangaRepository::class.java, 1)
.setClassInstanceLimit(PagesCache::class.java, 1)
.setClassInstanceLimit(MangaLoaderContext::class.java, 1)
.penaltyLog()
.build()
)
}
} }

@ -13,6 +13,7 @@ import androidx.core.graphics.drawable.toBitmap
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.utils.PendingIntentCompat
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -63,7 +64,7 @@ class DownloadNotification(private val context: Context) {
context, context,
startId, startId,
intent, intent,
PendingIntent.FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
} }
@ -146,7 +147,7 @@ class DownloadNotification(private val context: Context) {
context, context,
manga.hashCode(), manga.hashCode(),
DetailsActivity.newIntent(context, manga), DetailsActivity.newIntent(context, manga),
PendingIntent.FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
} }
} }

@ -85,7 +85,7 @@ class DownloadService : BaseService() {
wakeLock.acquire(TimeUnit.HOURS.toMillis(1)) wakeLock.acquire(TimeUnit.HOURS.toMillis(1))
notification.fillFrom(manga) notification.fillFrom(manga)
notification.setCancelId(startId) notification.setCancelId(startId)
withContext(Dispatchers.Main.immediate) { withContext(Dispatchers.Main) {
startForeground(DownloadNotification.NOTIFICATION_ID, notification()) startForeground(DownloadNotification.NOTIFICATION_ID, notification())
} }
val destination = settings.getStorageDir(this@DownloadService) val destination = settings.getStorageDir(this@DownloadService)
@ -174,8 +174,8 @@ class DownloadService : BaseService() {
withContext(NonCancellable) { withContext(NonCancellable) {
jobs.remove(startId) jobs.remove(startId)
output?.cleanup() output?.cleanup()
destination.sub("page.tmp").delete() destination.sub(TEMP_PAGE_FILE).deleteAwait()
withContext(Dispatchers.Main.immediate) { withContext(Dispatchers.Main) {
stopForeground(true) stopForeground(true)
notification.dismiss() notification.dismiss()
stopSelf(startId) stopSelf(startId)
@ -196,13 +196,25 @@ class DownloadService : BaseService() {
.cacheControl(CacheUtils.CONTROL_DISABLED) .cacheControl(CacheUtils.CONTROL_DISABLED)
.get() .get()
.build() .build()
return retryUntilSuccess(3) { val call = okHttp.newCall(request)
okHttp.newCall(request).await().use { response -> var attempts = MAX_DOWNLOAD_ATTEMPTS
val file = destination.sub("page.tmp") val file = destination.sub(TEMP_PAGE_FILE)
file.outputStream().use { out -> while (true) {
response.body!!.byteStream().copyTo(out) try {
val response = call.clone().await()
withContext(Dispatchers.IO) {
file.outputStream().use { out ->
checkNotNull(response.body).byteStream().copyTo(out)
}
}
return file
} catch (e: IOException) {
attempts--
if (attempts <= 0) {
throw e
} else {
delay(DOWNLOAD_ERROR_DELAY)
} }
file
} }
} }
} }
@ -218,6 +230,10 @@ class DownloadService : BaseService() {
private const val EXTRA_CHAPTERS_IDS = "chapters_ids" private const val EXTRA_CHAPTERS_IDS = "chapters_ids"
private const val EXTRA_CANCEL_ID = "cancel_id" private const val EXTRA_CANCEL_ID = "cancel_id"
private const val MAX_DOWNLOAD_ATTEMPTS = 3
private const val DOWNLOAD_ERROR_DELAY = 500L
private const val TEMP_PAGE_FILE = "page.tmp"
fun start(context: Context, manga: Manga, chaptersIds: Collection<Long>? = null) { fun start(context: Context, manga: Manga, chaptersIds: Collection<Long>? = null) {
confirmDataTransfer(context) { confirmDataTransfer(context) {
val intent = Intent(context, DownloadService::class.java) val intent = Intent(context, DownloadService::class.java)

@ -1,12 +1,9 @@
package org.koitharu.kotatsu.favourites.domain package org.koitharu.kotatsu.favourites.domain
import androidx.collection.ArraySet
import androidx.room.withTransaction import androidx.room.withTransaction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.db.entity.TagEntity
@ -83,18 +80,15 @@ class FavouritesRepository(private val db: MangaDatabase) {
categoryId = 0 categoryId = 0
) )
val id = db.favouriteCategoriesDao.insert(entity) val id = db.favouriteCategoriesDao.insert(entity)
notifyCategoriesChanged()
return entity.toFavouriteCategory(id) return entity.toFavouriteCategory(id)
} }
suspend fun renameCategory(id: Long, title: String) { suspend fun renameCategory(id: Long, title: String) {
db.favouriteCategoriesDao.update(id, title) db.favouriteCategoriesDao.update(id, title)
notifyCategoriesChanged()
} }
suspend fun removeCategory(id: Long) { suspend fun removeCategory(id: Long) {
db.favouriteCategoriesDao.delete(id) db.favouriteCategoriesDao.delete(id)
notifyCategoriesChanged()
} }
suspend fun reorderCategories(orderedIds: List<Long>) { suspend fun reorderCategories(orderedIds: List<Long>) {
@ -104,7 +98,6 @@ class FavouritesRepository(private val db: MangaDatabase) {
dao.update(id, i) dao.update(id, i)
} }
} }
notifyCategoriesChanged()
} }
suspend fun addToCategory(manga: Manga, categoryId: Long) { suspend fun addToCategory(manga: Manga, categoryId: Long) {
@ -115,41 +108,13 @@ class FavouritesRepository(private val db: MangaDatabase) {
val entity = FavouriteEntity(manga.id, categoryId, System.currentTimeMillis()) val entity = FavouriteEntity(manga.id, categoryId, System.currentTimeMillis())
db.favouritesDao.insert(entity) db.favouritesDao.insert(entity)
} }
notifyFavouritesChanged(manga.id)
} }
suspend fun removeFromCategory(manga: Manga, categoryId: Long) { suspend fun removeFromCategory(manga: Manga, categoryId: Long) {
db.favouritesDao.delete(categoryId, manga.id) db.favouritesDao.delete(categoryId, manga.id)
notifyFavouritesChanged(manga.id)
} }
suspend fun removeFromFavourites(manga: Manga) { suspend fun removeFromFavourites(manga: Manga) {
db.favouritesDao.delete(manga.id) db.favouritesDao.delete(manga.id)
notifyFavouritesChanged(manga.id)
}
companion object {
private val listeners = ArraySet<OnFavouritesChangeListener>()
fun subscribe(listener: OnFavouritesChangeListener) {
listeners += listener
}
fun unsubscribe(listener: OnFavouritesChangeListener) {
listeners -= listener
}
private suspend fun notifyFavouritesChanged(mangaId: Long) {
withContext(Dispatchers.Main) {
listeners.forEach { x -> x.onFavouritesChanged(mangaId) }
}
}
private suspend fun notifyCategoriesChanged() {
withContext(Dispatchers.Main) {
listeners.forEach { x -> x.onCategoriesChanged() }
}
}
} }
} }

@ -1,9 +0,0 @@
package org.koitharu.kotatsu.favourites.domain
@Deprecated("Use flow")
fun interface OnFavouritesChangeListener {
fun onFavouritesChanged(mangaId: Long)
fun onCategoriesChanged() = Unit
}

@ -8,6 +8,6 @@ import org.koitharu.kotatsu.history.ui.HistoryListViewModel
val historyModule val historyModule
get() = module { get() = module {
single { HistoryRepository(get()) } single { HistoryRepository(get(), get()) }
viewModel { HistoryListViewModel(get(), get(), get()) } viewModel { HistoryListViewModel(get(), get(), get()) }
} }

@ -1,11 +1,8 @@
package org.koitharu.kotatsu.history.domain package org.koitharu.kotatsu.history.domain
import androidx.collection.ArraySet
import androidx.room.withTransaction import androidx.room.withTransaction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.db.entity.TagEntity
@ -64,7 +61,6 @@ class HistoryRepository(
) )
trackingRepository.upsert(manga) trackingRepository.upsert(manga)
} }
notifyHistoryChanged()
} }
suspend fun getOne(manga: Manga): MangaHistory? { suspend fun getOne(manga: Manga): MangaHistory? {
@ -73,12 +69,10 @@ class HistoryRepository(
suspend fun clear() { suspend fun clear() {
db.historyDao.clear() db.historyDao.clear()
notifyHistoryChanged()
} }
suspend fun delete(manga: Manga) { suspend fun delete(manga: Manga) {
db.historyDao.delete(manga.id) db.historyDao.delete(manga.id)
notifyHistoryChanged()
} }
/** /**
@ -88,26 +82,6 @@ class HistoryRepository(
suspend fun deleteOrSwap(manga: Manga, alternative: Manga?) { suspend fun deleteOrSwap(manga: Manga, alternative: Manga?) {
if (alternative == null || db.mangaDao.update(MangaEntity.from(alternative)) <= 0) { if (alternative == null || db.mangaDao.update(MangaEntity.from(alternative)) <= 0) {
db.historyDao.delete(manga.id) db.historyDao.delete(manga.id)
notifyHistoryChanged()
}
}
companion object {
private val listeners = ArraySet<OnHistoryChangeListener>()
fun subscribe(listener: OnHistoryChangeListener) {
listeners += listener
}
fun unsubscribe(listener: OnHistoryChangeListener) {
listeners -= listener
}
private suspend fun notifyHistoryChanged() {
withContext(Dispatchers.Main) {
listeners.forEach { x -> x.onHistoryChanged() }
}
} }
} }
} }

@ -1,6 +0,0 @@
package org.koitharu.kotatsu.history.domain
fun interface OnHistoryChangeListener {
fun onHistoryChanged()
}

@ -9,14 +9,9 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
class AppProtectHelper(private val settings: AppSettings) : Application.ActivityLifecycleCallbacks { class AppProtectHelper(private val settings: AppSettings) : Application.ActivityLifecycleCallbacks {
private var isUnlocked = settings.appPassword.isNullOrEmpty() private var isUnlocked = settings.appPassword.isNullOrEmpty()
private var activityCounter = 0
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (activity is ProtectActivity) { if (activity !is ProtectActivity && !isUnlocked) {
return
}
activityCounter++
if (!isUnlocked) {
val sourceIntent = Intent(activity, activity.javaClass) val sourceIntent = Intent(activity, activity.javaClass)
activity.intent?.let { activity.intent?.let {
sourceIntent.putExtras(it) sourceIntent.putExtras(it)
@ -39,11 +34,7 @@ class AppProtectHelper(private val settings: AppSettings) : Application.Activity
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
override fun onActivityDestroyed(activity: Activity) { override fun onActivityDestroyed(activity: Activity) {
if (activity is ProtectActivity) { if (activity !is ProtectActivity && activity.isTaskRoot) {
return
}
activityCounter--
if (activityCounter == 0) {
restoreLock() restoreLock()
} }
} }

@ -23,6 +23,7 @@ import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.PendingIntentCompat
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
import org.koitharu.kotatsu.utils.ext.toUriOrNull import org.koitharu.kotatsu.utils.ext.toUriOrNull
@ -174,7 +175,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
setContentIntent( setContentIntent(
PendingIntent.getActivity( PendingIntent.getActivity(
applicationContext, id, applicationContext, id,
intent, PendingIntent.FLAG_UPDATE_CURRENT intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
setAutoCancel(true) setAutoCancel(true)

@ -7,7 +7,6 @@ import okhttp3.Cache
import okhttp3.CacheControl import okhttp3.CacheControl
import org.koitharu.kotatsu.utils.ext.computeSize import org.koitharu.kotatsu.utils.ext.computeSize
import org.koitharu.kotatsu.utils.ext.sub import org.koitharu.kotatsu.utils.ext.sub
import org.koitharu.kotatsu.utils.ext.sumByLong
import java.io.File import java.io.File
object CacheUtils { object CacheUtils {
@ -25,7 +24,7 @@ object CacheUtils {
@WorkerThread @WorkerThread
fun computeCacheSize(context: Context, name: String) = getCacheDirs(context) fun computeCacheSize(context: Context, name: String) = getCacheDirs(context)
.map { it.sub(name) } .map { it.sub(name) }
.sumByLong { x -> x.computeSize() } .sumOf { x -> x.computeSize() }
@WorkerThread @WorkerThread
fun clearCache(context: Context, name: String) = getCacheDirs(context) fun clearCache(context: Context, name: String) = getCacheDirs(context)

@ -0,0 +1,13 @@
package org.koitharu.kotatsu.utils
import android.app.PendingIntent
import android.os.Build
object PendingIntentCompat {
val FLAG_IMMUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
}

@ -9,22 +9,6 @@ fun <T> MutableCollection<T>.replaceWith(subject: Iterable<T>) {
addAll(subject) addAll(subject)
} }
inline fun <T> Array<out T>.sumByLong(selector: (T) -> Long): Long {
var sum = 0L
for (element in this) {
sum += selector(element)
}
return sum
}
inline fun <T> Iterable<T>.sumByLong(selector: (T) -> Long): Long {
var sum = 0L
for (element in this) {
sum += selector(element)
}
return sum
}
fun <T> List<T>.medianOrNull(): T? = when { fun <T> List<T>.medianOrNull(): T? = when {
isEmpty() -> null isEmpty() -> null
else -> get((size / 2).coerceIn(indices)) else -> get((size / 2).coerceIn(indices))

@ -8,22 +8,6 @@ import org.koitharu.kotatsu.core.exceptions.*
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
suspend inline fun <T, R> T.retryUntilSuccess(maxAttempts: Int, action: T.() -> R): R {
var attempts = maxAttempts
while (true) {
try {
return this.action()
} catch (e: Exception) {
attempts--
if (attempts <= 0) {
throw e
} else {
delay(1000)
}
}
}
}
fun Throwable.getDisplayMessage(resources: Resources) = when (this) { fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
is AuthRequiredException -> resources.getString(R.string.auth_required) is AuthRequiredException -> resources.getString(R.string.auth_required)
is CloudFlareProtectedException -> resources.getString(R.string.captcha_required) is CloudFlareProtectedException -> resources.getString(R.string.captcha_required)

@ -14,25 +14,6 @@ import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
suspend fun Call.await() = suspendCancellableCoroutine<Response> { cont ->
this.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (cont.isActive) {
cont.resumeWithException(e)
}
}
override fun onResponse(call: Call, response: Response) {
if (cont.isActive) {
cont.resume(response)
}
}
})
cont.invokeOnCancellation {
this.cancel()
}
}
inline fun CoroutineScope.launchAfter( inline fun CoroutineScope.launchAfter(
job: Job?, job: Job?,
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,

@ -5,6 +5,8 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.os.storage.StorageManager import android.os.storage.StorageManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import java.io.File import java.io.File
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
@ -19,7 +21,7 @@ fun ZipFile.readText(entry: ZipEntry) = getInputStream(entry).bufferedReader().u
it.readText() it.readText()
} }
fun File.computeSize(): Long = listFiles()?.sumByLong { x -> fun File.computeSize(): Long = listFiles()?.sumOf { x ->
if (x.isDirectory) { if (x.isDirectory) {
x.computeSize() x.computeSize()
} else { } else {
@ -49,4 +51,8 @@ fun File.getStorageName(context: Context): String = runCatching {
} }
}.getOrNull() ?: context.getString(R.string.other_storage) }.getOrNull() ?: context.getString(R.string.other_storage)
fun Uri.toFileOrNull() = if (scheme == "file") path?.let(::File) else null fun Uri.toFileOrNull() = if (scheme == "file") path?.let(::File) else null
suspend fun File.deleteAwait() = withContext(Dispatchers.IO) {
delete()
}

@ -13,7 +13,7 @@ inline fun <T : Fragment> T.withArgs(size: Int, block: Bundle.() -> Unit): T {
} }
val Fragment.viewLifecycleScope val Fragment.viewLifecycleScope
get() = viewLifecycleOwner.lifecycle.coroutineScope inline get() = viewLifecycleOwner.lifecycle.coroutineScope
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T : Parcelable> Fragment.parcelableArgument(name: String): Lazy<T> { inline fun <T : Parcelable> Fragment.parcelableArgument(name: String): Lazy<T> {

@ -1,7 +1,32 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response import okhttp3.Response
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
suspend fun Call.await() = suspendCancellableCoroutine<Response> { cont ->
this.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (cont.isActive) {
cont.resumeWithException(e)
}
}
override fun onResponse(call: Call, response: Response) {
if (cont.isActive) {
cont.resume(response)
}
}
})
cont.invokeOnCancellation {
this.cancel()
}
}
val Response.mimeType: String? val Response.mimeType: String?
get() = body?.contentType()?.run { "$type/$subtype" } get() = body?.contentType()?.run { "$type/$subtype" }

@ -7,7 +7,7 @@ import androidx.core.view.isGone
var TextView.textAndVisible: CharSequence? var TextView.textAndVisible: CharSequence?
inline get() = text?.takeIf { visibility == View.VISIBLE } inline get() = text?.takeIf { visibility == View.VISIBLE }
set(value) { inline set(value) {
text = value text = value
isGone = value.isNullOrEmpty() isGone = value.isNullOrEmpty()
} }

@ -2,26 +2,20 @@ package org.koitharu.kotatsu.utils.ext
import android.app.Activity import android.app.Activity
import android.graphics.Rect import android.graphics.Rect
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.MenuRes import androidx.annotation.MenuRes
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
fun View.hideKeyboard() { fun View.hideKeyboard() {
val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
@ -54,13 +48,6 @@ var RecyclerView.firstItem: Int
} }
} }
fun View.disableFor(timeInMillis: Long) {
isEnabled = false
postDelayed(timeInMillis) {
isEnabled = true
}
}
inline fun View.showPopupMenu( inline fun View.showPopupMenu(
@MenuRes menuRes: Int, @MenuRes menuRes: Int,
onPrepare: (Menu) -> Unit = {}, onPrepare: (Menu) -> Unit = {},
@ -134,7 +121,7 @@ inline fun ViewPager2.doOnPageChanged(crossinline callback: (Int) -> Unit) {
} }
val ViewPager2.recyclerView: RecyclerView? val ViewPager2.recyclerView: RecyclerView?
get() = children.find { it is RecyclerView } as? RecyclerView inline get() = children.find { it is RecyclerView } as? RecyclerView
fun View.resetTransformations() { fun View.resetTransformations() {
alpha = 1f alpha = 1f
@ -161,49 +148,9 @@ inline fun RecyclerView.doOnCurrentItemChanged(crossinline callback: (Int) -> Un
}) })
} }
@Deprecated("Reflection")
fun RecyclerView.callOnScrollListeners() {
try {
val field = RecyclerView::class.java.getDeclaredField("mScrollListeners")
field.isAccessible = true
val listeners = field.get(this) as List<*>
for (x in listeners) {
(x as RecyclerView.OnScrollListener).onScrolled(this, 0, 0)
}
} catch (e: Throwable) {
Log.e(null, "RecyclerView.callOnScrollListeners() failed", e)
}
}
@Deprecated("Reflection")
fun ViewPager2.callOnPageChaneListeners() {
try {
val field = ViewPager2::class.java.getDeclaredField("mExternalPageChangeCallbacks")
field.isAccessible = true
val compositeCallback = field.get(this)
val field2 = compositeCallback.javaClass.getDeclaredField("mCallbacks")
field2.isAccessible = true
val listeners = field2.get(compositeCallback) as List<*>
val position = currentItem
for (x in listeners) {
(x as ViewPager2.OnPageChangeCallback).onPageSelected(position)
}
} catch (e: Throwable) {
Log.e(null, "ViewPager2.callOnPageChaneListeners() failed", e)
}
}
fun RecyclerView.findCenterViewPosition(): Int { fun RecyclerView.findCenterViewPosition(): Int {
val centerX = width / 2f val centerX = width / 2f
val centerY = height / 2f val centerY = height / 2f
val view = findChildViewUnder(centerX, centerY) ?: return RecyclerView.NO_POSITION val view = findChildViewUnder(centerX, centerY) ?: return RecyclerView.NO_POSITION
return getChildAdapterPosition(view) return getChildAdapterPosition(view)
}
fun ViewPager2.swapAdapter(newAdapter: RecyclerView.Adapter<*>?) {
val position = currentItem
adapter = newAdapter
if (adapter != null && position != RecyclerView.NO_POSITION) {
setCurrentItem(position, false)
}
} }

@ -4,20 +4,30 @@ import android.appwidget.AppWidgetManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import org.koitharu.kotatsu.favourites.domain.OnFavouritesChangeListener import kotlinx.coroutines.CancellationException
import org.koitharu.kotatsu.history.domain.OnHistoryChangeListener import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.retry
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.widget.recent.RecentWidgetProvider import org.koitharu.kotatsu.widget.recent.RecentWidgetProvider
import org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider import org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider
class WidgetUpdater(private val context: Context) : OnFavouritesChangeListener, class WidgetUpdater(private val context: Context) {
OnHistoryChangeListener {
override fun onFavouritesChanged(mangaId: Long) { fun subscribeToFavourites(repository: FavouritesRepository) {
updateWidget(ShelfWidgetProvider::class.java) repository.observeAll()
.onEach { updateWidget(ShelfWidgetProvider::class.java) }
.retry { error -> error !is CancellationException }
.launchIn(processLifecycleScope)
} }
override fun onHistoryChanged() { fun subscribeToHistory(repository: HistoryRepository) {
updateWidget(RecentWidgetProvider::class.java) repository.observeAll()
.onEach { updateWidget(RecentWidgetProvider::class.java) }
.retry { error -> error !is CancellationException }
.launchIn(processLifecycleScope)
} }
private fun updateWidget(cls: Class<*>) { private fun updateWidget(cls: Class<*>) {

@ -9,6 +9,7 @@ import android.net.Uri
import android.widget.RemoteViews import android.widget.RemoteViews
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.utils.PendingIntentCompat
class RecentWidgetProvider : AppWidgetProvider() { class RecentWidgetProvider : AppWidgetProvider() {
@ -30,7 +31,7 @@ class RecentWidgetProvider : AppWidgetProvider() {
context, context,
0, 0,
intent, intent,
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
views.setEmptyView(R.id.stackView, R.id.textView_holder) views.setEmptyView(R.id.stackView, R.id.textView_holder)

@ -9,6 +9,7 @@ import android.net.Uri
import android.widget.RemoteViews import android.widget.RemoteViews
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.utils.PendingIntentCompat
class ShelfWidgetProvider : AppWidgetProvider() { class ShelfWidgetProvider : AppWidgetProvider() {
@ -30,7 +31,7 @@ class ShelfWidgetProvider : AppWidgetProvider() {
context, context,
0, 0,
intent, intent,
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
views.setEmptyView(R.id.gridView, R.id.textView_holder) views.setEmptyView(R.id.gridView, R.id.textView_holder)

Loading…
Cancel
Save