Remove unused code
This commit is contained in:
@@ -15,9 +15,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.AppCrashHandler
|
||||
import org.koitharu.kotatsu.core.ui.uiModule
|
||||
import org.koitharu.kotatsu.details.detailsModule
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.favouritesModule
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.historyModule
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
@@ -37,29 +35,15 @@ class KotatsuApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) {
|
||||
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()
|
||||
)
|
||||
enableStrictMode()
|
||||
}
|
||||
initKoin()
|
||||
Thread.setDefaultUncaughtExceptionHandler(AppCrashHandler(applicationContext))
|
||||
AppCompatDelegate.setDefaultNightMode(get<AppSettings>().theme)
|
||||
registerActivityLifecycleCallbacks(get<AppProtectHelper>())
|
||||
val widgetUpdater = WidgetUpdater(applicationContext)
|
||||
FavouritesRepository.subscribe(widgetUpdater)
|
||||
HistoryRepository.subscribe(widgetUpdater)
|
||||
widgetUpdater.subscribeToFavourites(get())
|
||||
widgetUpdater.subscribeToHistory(get())
|
||||
}
|
||||
|
||||
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.core.model.Manga
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -63,7 +64,7 @@ class DownloadNotification(private val context: Context) {
|
||||
context,
|
||||
startId,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -146,7 +147,7 @@ class DownloadNotification(private val context: Context) {
|
||||
context,
|
||||
manga.hashCode(),
|
||||
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))
|
||||
notification.fillFrom(manga)
|
||||
notification.setCancelId(startId)
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
withContext(Dispatchers.Main) {
|
||||
startForeground(DownloadNotification.NOTIFICATION_ID, notification())
|
||||
}
|
||||
val destination = settings.getStorageDir(this@DownloadService)
|
||||
@@ -174,8 +174,8 @@ class DownloadService : BaseService() {
|
||||
withContext(NonCancellable) {
|
||||
jobs.remove(startId)
|
||||
output?.cleanup()
|
||||
destination.sub("page.tmp").delete()
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
destination.sub(TEMP_PAGE_FILE).deleteAwait()
|
||||
withContext(Dispatchers.Main) {
|
||||
stopForeground(true)
|
||||
notification.dismiss()
|
||||
stopSelf(startId)
|
||||
@@ -196,13 +196,25 @@ class DownloadService : BaseService() {
|
||||
.cacheControl(CacheUtils.CONTROL_DISABLED)
|
||||
.get()
|
||||
.build()
|
||||
return retryUntilSuccess(3) {
|
||||
okHttp.newCall(request).await().use { response ->
|
||||
val file = destination.sub("page.tmp")
|
||||
file.outputStream().use { out ->
|
||||
response.body!!.byteStream().copyTo(out)
|
||||
val call = okHttp.newCall(request)
|
||||
var attempts = MAX_DOWNLOAD_ATTEMPTS
|
||||
val file = destination.sub(TEMP_PAGE_FILE)
|
||||
while (true) {
|
||||
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_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) {
|
||||
confirmDataTransfer(context) {
|
||||
val intent = Intent(context, DownloadService::class.java)
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package org.koitharu.kotatsu.favourites.domain
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.room.withTransaction
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
@@ -83,18 +80,15 @@ class FavouritesRepository(private val db: MangaDatabase) {
|
||||
categoryId = 0
|
||||
)
|
||||
val id = db.favouriteCategoriesDao.insert(entity)
|
||||
notifyCategoriesChanged()
|
||||
return entity.toFavouriteCategory(id)
|
||||
}
|
||||
|
||||
suspend fun renameCategory(id: Long, title: String) {
|
||||
db.favouriteCategoriesDao.update(id, title)
|
||||
notifyCategoriesChanged()
|
||||
}
|
||||
|
||||
suspend fun removeCategory(id: Long) {
|
||||
db.favouriteCategoriesDao.delete(id)
|
||||
notifyCategoriesChanged()
|
||||
}
|
||||
|
||||
suspend fun reorderCategories(orderedIds: List<Long>) {
|
||||
@@ -104,7 +98,6 @@ class FavouritesRepository(private val db: MangaDatabase) {
|
||||
dao.update(id, i)
|
||||
}
|
||||
}
|
||||
notifyCategoriesChanged()
|
||||
}
|
||||
|
||||
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())
|
||||
db.favouritesDao.insert(entity)
|
||||
}
|
||||
notifyFavouritesChanged(manga.id)
|
||||
}
|
||||
|
||||
suspend fun removeFromCategory(manga: Manga, categoryId: Long) {
|
||||
db.favouritesDao.delete(categoryId, manga.id)
|
||||
notifyFavouritesChanged(manga.id)
|
||||
}
|
||||
|
||||
suspend fun removeFromFavourites(manga: Manga) {
|
||||
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
|
||||
get() = module {
|
||||
|
||||
single { HistoryRepository(get()) }
|
||||
single { HistoryRepository(get(), get()) }
|
||||
viewModel { HistoryListViewModel(get(), get(), get()) }
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
package org.koitharu.kotatsu.history.domain
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.room.withTransaction
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
@@ -64,7 +61,6 @@ class HistoryRepository(
|
||||
)
|
||||
trackingRepository.upsert(manga)
|
||||
}
|
||||
notifyHistoryChanged()
|
||||
}
|
||||
|
||||
suspend fun getOne(manga: Manga): MangaHistory? {
|
||||
@@ -73,12 +69,10 @@ class HistoryRepository(
|
||||
|
||||
suspend fun clear() {
|
||||
db.historyDao.clear()
|
||||
notifyHistoryChanged()
|
||||
}
|
||||
|
||||
suspend fun delete(manga: Manga) {
|
||||
db.historyDao.delete(manga.id)
|
||||
notifyHistoryChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,26 +82,6 @@ class HistoryRepository(
|
||||
suspend fun deleteOrSwap(manga: Manga, alternative: Manga?) {
|
||||
if (alternative == null || db.mangaDao.update(MangaEntity.from(alternative)) <= 0) {
|
||||
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 {
|
||||
|
||||
private var isUnlocked = settings.appPassword.isNullOrEmpty()
|
||||
private var activityCounter = 0
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
if (activity is ProtectActivity) {
|
||||
return
|
||||
}
|
||||
activityCounter++
|
||||
if (!isUnlocked) {
|
||||
if (activity !is ProtectActivity && !isUnlocked) {
|
||||
val sourceIntent = Intent(activity, activity.javaClass)
|
||||
activity.intent?.let {
|
||||
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 onActivityDestroyed(activity: Activity) {
|
||||
if (activity is ProtectActivity) {
|
||||
return
|
||||
}
|
||||
activityCounter--
|
||||
if (activityCounter == 0) {
|
||||
if (activity !is ProtectActivity && activity.isTaskRoot) {
|
||||
restoreLock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
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.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.utils.ext.toUriOrNull
|
||||
@@ -174,7 +175,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
applicationContext, id,
|
||||
intent, PendingIntent.FLAG_UPDATE_CURRENT
|
||||
intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
setAutoCancel(true)
|
||||
|
||||
@@ -7,7 +7,6 @@ import okhttp3.Cache
|
||||
import okhttp3.CacheControl
|
||||
import org.koitharu.kotatsu.utils.ext.computeSize
|
||||
import org.koitharu.kotatsu.utils.ext.sub
|
||||
import org.koitharu.kotatsu.utils.ext.sumByLong
|
||||
import java.io.File
|
||||
|
||||
object CacheUtils {
|
||||
@@ -25,7 +24,7 @@ object CacheUtils {
|
||||
@WorkerThread
|
||||
fun computeCacheSize(context: Context, name: String) = getCacheDirs(context)
|
||||
.map { it.sub(name) }
|
||||
.sumByLong { x -> x.computeSize() }
|
||||
.sumOf { x -> x.computeSize() }
|
||||
|
||||
@WorkerThread
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
isEmpty() -> null
|
||||
else -> get((size / 2).coerceIn(indices))
|
||||
|
||||
@@ -8,22 +8,6 @@ import org.koitharu.kotatsu.core.exceptions.*
|
||||
import java.io.FileNotFoundException
|
||||
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) {
|
||||
is AuthRequiredException -> resources.getString(R.string.auth_required)
|
||||
is CloudFlareProtectedException -> resources.getString(R.string.captcha_required)
|
||||
|
||||
@@ -14,25 +14,6 @@ import kotlin.coroutines.EmptyCoroutineContext
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun CoroutineScope.launchAfter(
|
||||
job: Job?,
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.storage.StorageManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
@@ -19,7 +21,7 @@ fun ZipFile.readText(entry: ZipEntry) = getInputStream(entry).bufferedReader().u
|
||||
it.readText()
|
||||
}
|
||||
|
||||
fun File.computeSize(): Long = listFiles()?.sumByLong { x ->
|
||||
fun File.computeSize(): Long = listFiles()?.sumOf { x ->
|
||||
if (x.isDirectory) {
|
||||
x.computeSize()
|
||||
} else {
|
||||
@@ -49,4 +51,8 @@ fun File.getStorageName(context: Context): String = runCatching {
|
||||
}
|
||||
}.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
|
||||
get() = viewLifecycleOwner.lifecycle.coroutineScope
|
||||
inline get() = viewLifecycleOwner.lifecycle.coroutineScope
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T : Parcelable> Fragment.parcelableArgument(name: String): Lazy<T> {
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Response
|
||||
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?
|
||||
get() = body?.contentType()?.run { "$type/$subtype" }
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.core.view.isGone
|
||||
|
||||
var TextView.textAndVisible: CharSequence?
|
||||
inline get() = text?.takeIf { visibility == View.VISIBLE }
|
||||
set(value) {
|
||||
inline set(value) {
|
||||
text = value
|
||||
isGone = value.isNullOrEmpty()
|
||||
}
|
||||
|
||||
@@ -2,26 +2,20 @@ package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Rect
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
|
||||
fun View.hideKeyboard() {
|
||||
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(
|
||||
@MenuRes menuRes: Int,
|
||||
onPrepare: (Menu) -> Unit = {},
|
||||
@@ -134,7 +121,7 @@ inline fun ViewPager2.doOnPageChanged(crossinline callback: (Int) -> Unit) {
|
||||
}
|
||||
|
||||
val ViewPager2.recyclerView: RecyclerView?
|
||||
get() = children.find { it is RecyclerView } as? RecyclerView
|
||||
inline get() = children.find { it is RecyclerView } as? RecyclerView
|
||||
|
||||
fun View.resetTransformations() {
|
||||
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 {
|
||||
val centerX = width / 2f
|
||||
val centerY = height / 2f
|
||||
val view = findChildViewUnder(centerX, centerY) ?: return RecyclerView.NO_POSITION
|
||||
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.Context
|
||||
import android.content.Intent
|
||||
import org.koitharu.kotatsu.favourites.domain.OnFavouritesChangeListener
|
||||
import org.koitharu.kotatsu.history.domain.OnHistoryChangeListener
|
||||
import kotlinx.coroutines.CancellationException
|
||||
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.shelf.ShelfWidgetProvider
|
||||
|
||||
class WidgetUpdater(private val context: Context) : OnFavouritesChangeListener,
|
||||
OnHistoryChangeListener {
|
||||
class WidgetUpdater(private val context: Context) {
|
||||
|
||||
override fun onFavouritesChanged(mangaId: Long) {
|
||||
updateWidget(ShelfWidgetProvider::class.java)
|
||||
fun subscribeToFavourites(repository: FavouritesRepository) {
|
||||
repository.observeAll()
|
||||
.onEach { updateWidget(ShelfWidgetProvider::class.java) }
|
||||
.retry { error -> error !is CancellationException }
|
||||
.launchIn(processLifecycleScope)
|
||||
}
|
||||
|
||||
override fun onHistoryChanged() {
|
||||
updateWidget(RecentWidgetProvider::class.java)
|
||||
fun subscribeToHistory(repository: HistoryRepository) {
|
||||
repository.observeAll()
|
||||
.onEach { updateWidget(RecentWidgetProvider::class.java) }
|
||||
.retry { error -> error !is CancellationException }
|
||||
.launchIn(processLifecycleScope)
|
||||
}
|
||||
|
||||
private fun updateWidget(cls: Class<*>) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.net.Uri
|
||||
import android.widget.RemoteViews
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||
|
||||
class RecentWidgetProvider : AppWidgetProvider() {
|
||||
|
||||
@@ -30,7 +31,7 @@ class RecentWidgetProvider : AppWidgetProvider() {
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
views.setEmptyView(R.id.stackView, R.id.textView_holder)
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.net.Uri
|
||||
import android.widget.RemoteViews
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||
|
||||
class ShelfWidgetProvider : AppWidgetProvider() {
|
||||
|
||||
@@ -30,7 +31,7 @@ class ShelfWidgetProvider : AppWidgetProvider() {
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
views.setEmptyView(R.id.gridView, R.id.textView_holder)
|
||||
|
||||
Reference in New Issue
Block a user