From cc6b114e4dc105a789b4681e595bf652b2a20606 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 7 Apr 2022 17:04:11 +0300 Subject: [PATCH] Improve suggestions worker --- .../base/domain/MangaDataRepository.kt | 4 +- .../kotatsu/base/domain/MangaUtils.kt | 2 +- .../kotatsu/core/db/entity/MangaWithTags.kt | 6 +- .../core/db/entity/TrackLogWithManga.kt | 5 +- .../kotatsu/details/ui/DetailsViewModel.kt | 2 +- .../details/ui/adapter/BranchesAdapter.kt | 2 +- .../kotatsu/download/ui/DownloadItemAD.kt | 1 + .../ui/service/DownloadNotification.kt | 2 +- .../favourites/domain/FavouritesRepository.kt | 2 +- .../kotatsu/history/data/HistoryDao.kt | 13 ++-- .../history/domain/HistoryRepository.kt | 6 +- .../suggestion/model/SearchSuggestionItem.kt | 2 +- .../settings/onboard/OnboardViewModel.kt | 1 + .../domain/SuggestionRepository.kt | 2 +- .../suggestions/ui/SuggestionsWorker.kt | 64 +++++++++++++------ .../kotatsu/tracker/work/TrackWorker.kt | 5 +- .../koitharu/kotatsu/utils/ext/AndroidExt.kt | 8 ++- .../kotatsu/utils/ext/CollectionExt.kt | 54 ++-------------- .../koitharu/kotatsu/utils/ext/CommonExt.kt | 5 +- .../kotatsu/utils/ext/PrimitiveExt.kt | 24 ------- 20 files changed, 92 insertions(+), 118 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt index a658a474a..21f9c9989 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt @@ -9,7 +9,7 @@ import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag -import org.koitharu.kotatsu.utils.ext.mapToSet +import org.koitharu.kotatsu.parsers.util.mapToSet class MangaDataRepository(private val db: MangaDatabase) { @@ -37,7 +37,7 @@ class MangaDataRepository(private val db: MangaDatabase) { suspend fun resolveIntent(intent: MangaIntent): Manga? = when { intent.manga != null -> intent.manga - intent.mangaId != 0L -> db.mangaDao.find(intent.mangaId)?.toManga() + intent.mangaId != 0L -> findMangaById(intent.mangaId) else -> null // TODO resolve uri } diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt index 9ed4e714b..09a970eee 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt @@ -14,7 +14,7 @@ import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.util.await -import org.koitharu.kotatsu.utils.ext.medianOrNull +import org.koitharu.kotatsu.parsers.util.medianOrNull import java.io.InputStream import java.util.zip.ZipFile diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt index ad80b0beb..524fe906d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt @@ -3,7 +3,7 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.Embedded import androidx.room.Junction import androidx.room.Relation -import org.koitharu.kotatsu.utils.ext.mapToSet +import org.koitharu.kotatsu.parsers.util.mapToSet class MangaWithTags( @Embedded val manga: MangaEntity, @@ -15,7 +15,5 @@ class MangaWithTags( val tags: List ) { - fun toManga() = manga.toManga(tags.mapToSet { - it.toMangaTag() - }) + fun toManga() = manga.toManga(tags.mapToSet { it.toMangaTag() }) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt index 263e91938..037b22ad8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt @@ -3,9 +3,10 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.Embedded import androidx.room.Junction import androidx.room.Relation -import org.koitharu.kotatsu.core.model.TrackingLogItem -import org.koitharu.kotatsu.utils.ext.mapToSet import java.util.* +import org.koitharu.kotatsu.core.model.TrackingLogItem +import org.koitharu.kotatsu.parsers.util.mapToSet +import org.koitharu.kotatsu.utils.ext.mapToSet class TrackLogWithManga( @Embedded val trackLog: TrackLogEntity, diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 53546ea6a..7c5fe8574 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -24,11 +24,11 @@ import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.iterator -import org.koitharu.kotatsu.utils.ext.mapToSet import java.io.IOException class DetailsViewModel( diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt index 5cee213a7..2ca602df9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt @@ -6,7 +6,7 @@ import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.TextView import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.utils.ext.replaceWith +import org.koitharu.kotatsu.parsers.util.replaceWith class BranchesAdapter : BaseAdapter() { diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt index c16896205..1a86d0153 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.onEach import org.koitharu.kotatsu.R import org.koitharu.kotatsu.databinding.ItemDownloadBinding import org.koitharu.kotatsu.download.domain.DownloadState +import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.progress.ProgressJob diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt index aef7a1667..dd3fa3035 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt @@ -16,8 +16,8 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.download.ui.DownloadsActivity import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.utils.PendingIntentCompat -import org.koitharu.kotatsu.utils.ext.format import org.koitharu.kotatsu.utils.ext.getDisplayMessage import com.google.android.material.R as materialR diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index 868e2495c..ad9605cc3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -13,8 +13,8 @@ import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity import org.koitharu.kotatsu.favourites.data.FavouriteEntity import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.utils.ext.mapItems -import org.koitharu.kotatsu.utils.ext.mapToSet class FavouritesRepository(private val db: MangaDatabase) { diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt index 0b973aa64..a2527f59e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.flow.Flow import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.TagEntity - @Dao abstract class HistoryDao { @@ -23,8 +22,15 @@ abstract class HistoryDao { @Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM history)") abstract suspend fun findAllManga(): List - @Query("SELECT * FROM tags WHERE tag_id IN (SELECT tag_id FROM manga_tags WHERE manga_id IN (SELECT manga_id FROM history))") - abstract suspend fun findAllTags(): List + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + INNER JOIN history ON history.manga_id = manga_tags.manga_id + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_tags.manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findPopularTags(limit: Int): List @Query("SELECT * FROM history WHERE manga_id = :id") abstract suspend fun find(id: Long): HistoryEntity? @@ -60,5 +66,4 @@ abstract class HistoryDao { true } else false } - } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt index 4235765fd..89586e805 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt @@ -11,9 +11,9 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.history.data.HistoryEntity import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.ext.mapItems -import org.koitharu.kotatsu.utils.ext.mapToSet class HistoryRepository( private val db: MangaDatabase, @@ -91,7 +91,7 @@ class HistoryRepository( } } - suspend fun getAllTags(): Set { - return db.historyDao.findAllTags().mapToSet { x -> x.toMangaTag() } + suspend fun getPopularTags(limit: Int): List { + return db.historyDao.findPopularTags(limit).map { x -> x.toMangaTag() } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt index 1b89b32d3..341c89ac8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.utils.ext.areItemsEquals +import org.koitharu.kotatsu.parsers.util.areItemsEquals sealed interface SearchSuggestionItem { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt index 4e336c181..b9f97f37d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.settings.onboard.model.SourceLocale import org.koitharu.kotatsu.utils.ext.map diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt index a7bc69ace..17dd71840 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt @@ -6,9 +6,9 @@ import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.suggestions.data.SuggestionEntity import org.koitharu.kotatsu.utils.ext.mapItems -import org.koitharu.kotatsu.utils.ext.mapToSet class SuggestionRepository( private val db: MangaDatabase, diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt index fe6b73ffb..46bb42b5a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt @@ -4,21 +4,28 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.os.Build +import androidx.annotation.FloatRange import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.work.* -import java.util.concurrent.TimeUnit -import kotlin.math.pow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.history.domain.HistoryRepository -import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository +import org.koitharu.kotatsu.utils.ext.asArrayList +import org.koitharu.kotatsu.utils.ext.trySetForeground +import java.util.concurrent.TimeUnit +import kotlin.math.pow class SuggestionsWorker(appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params), KoinComponent { @@ -27,11 +34,10 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : private val historyRepository by inject() private val appSettings by inject() - override suspend fun doWork(): Result = try { + override suspend fun doWork(): Result { val count = doWorkImpl() - Result.success(workDataOf(DATA_COUNT to count)) - } catch (t: Throwable) { - Result.failure() + val outputData = workDataOf(DATA_COUNT to count) + return Result.success(outputData) } override suspend fun getForegroundInfo(): ForegroundInfo { @@ -70,21 +76,28 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : suggestionRepository.clear() return 0 } - val rawResults = ArrayList() - val allTags = historyRepository.getAllTags() + val allTags = historyRepository.getPopularTags(TAGS_LIMIT) if (allTags.isEmpty()) { return 0 } + if (TAG in tags) { // not expedited + trySetForeground() + } val tagsBySources = allTags.groupBy { x -> x.source } - for ((source, tags) in tagsBySources) { - val repo = MangaRepository(source) - tags.flatMapTo(rawResults) { tag -> - repo.getList( - offset = 0, - sortOrder = SortOrder.UPDATED, - tags = setOf(tag), - ) - } + val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM) + val rawResults = coroutineScope { + tagsBySources.flatMap { (source, tags) -> + val repo = MangaRepository(source) + tags.map { tag -> + async(dispatcher) { + repo.getList( + offset = 0, + sortOrder = SortOrder.UPDATED, + tags = setOf(tag), + ) + } + } + }.awaitAll().flatten().asArrayList() } if (appSettings.isSuggestionsExcludeNsfw) { rawResults.removeAll { it.isNsfw } @@ -95,21 +108,32 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : val suggestions = rawResults.distinctBy { manga -> manga.id }.map { manga -> - val jointTags = manga.tags intersect allTags MangaSuggestion( manga = manga, - relevance = (jointTags.size / manga.tags.size.toDouble()).pow(2.0).toFloat(), + relevance = computeRelevance(manga.tags, allTags) ) }.sortedBy { it.relevance }.take(LIMIT) suggestionRepository.replace(suggestions) return suggestions.size } + @FloatRange(from = 0.0, to = 1.0) + private fun computeRelevance(mangaTags: Set, allTags: List): Float { + val maxWeight = (allTags.size + allTags.size + 1 - mangaTags.size) * mangaTags.size / 2.0 + val weight = mangaTags.sumOf { tag -> + val index = allTags.indexOf(tag) + if (index < 0) 0 else allTags.size - index + } + return (weight / maxWeight).pow(2.0).toFloat() + } + companion object { private const val TAG = "suggestions" private const val TAG_ONESHOT = "suggestions_oneshot" private const val LIMIT = 140 + private const val TAGS_LIMIT = 20 + private const val MAX_PARALLELISM = 4 private const val DATA_COUNT = "count" private const val WORKER_CHANNEL_ID = "suggestion_worker" private const val WORKER_NOTIFICATION_ID = 36 diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt index 1144bcce0..a61b25c47 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt @@ -27,6 +27,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.PendingIntentCompat import org.koitharu.kotatsu.utils.ext.referer import org.koitharu.kotatsu.utils.ext.toBitmapOrNull +import org.koitharu.kotatsu.utils.ext.trySetForeground import org.koitharu.kotatsu.utils.progress.Progress import java.util.concurrent.TimeUnit @@ -53,7 +54,9 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) : if (tracks.isEmpty()) { return Result.success() } - setForeground(getForegroundInfo()) + if (TAG in tags) { // not expedited + trySetForeground() + } var success = 0 val workData = Data.Builder() .putInt(DATA_TOTAL, tracks.size) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt index 2fc663c9b..21140be00 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt @@ -5,6 +5,7 @@ import android.net.ConnectivityManager import android.net.Network import android.net.NetworkRequest import android.net.Uri +import androidx.work.CoroutineWorker import kotlin.coroutines.resume import kotlinx.coroutines.suspendCancellableCoroutine @@ -28,4 +29,9 @@ suspend fun ConnectivityManager.waitForNetwork(): Network { } } -fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this) \ No newline at end of file +fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this) + +suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatching { + val info = getForegroundInfo() + setForeground(info) +}.isSuccess \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt index 5ba3d0aa2..916d83491 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt @@ -3,46 +3,12 @@ package org.koitharu.kotatsu.utils.ext import androidx.collection.ArraySet import java.util.* -fun MutableCollection.replaceWith(subject: Iterable) { - clear() - addAll(subject) -} - -fun List.medianOrNull(): T? = when { - isEmpty() -> null - else -> get((size / 2).coerceIn(indices)) -} - -inline fun Collection.mapToSet(transform: (T) -> R): Set { - return mapTo(ArraySet(size), transform) -} - fun LongArray.toArraySet(): Set = createSet(size) { i -> this[i] } fun > Array.names() = Array(size) { i -> this[i].name } -fun Collection.isDistinct(): Boolean { - val set = HashSet(size) - for (item in this) { - if (!set.add(item)) { - return false - } - } - return set.size == size -} - -fun Collection.isDistinctBy(selector: (T) -> K): Boolean { - val set = HashSet(size) - for (item in this) { - if (!set.add(selector(item))) { - return false - } - } - return set.size == size -} - fun MutableList.move(sourceIndex: Int, targetIndex: Int) { if (sourceIndex <= targetIndex) { Collections.rotate(subList(sourceIndex, targetIndex + 1), -1) @@ -51,20 +17,6 @@ fun MutableList.move(sourceIndex: Int, targetIndex: Int) { } } -inline fun List.areItemsEquals(other: List, equals: (T, T) -> Boolean): Boolean { - if (size != other.size) { - return false - } - for (i in indices) { - val a = this[i] - val b = other[i] - if (!equals(a, b)) { - return false - } - } - return true -} - @Suppress("FunctionName") inline fun MutableSet(size: Int, init: (index: Int) -> T): MutableSet { val set = ArraySet(size) @@ -82,4 +34,10 @@ inline fun createList(size: Int, init: (index: Int) -> T): List = when (s 0 -> emptyList() 1 -> Collections.singletonList(init(0)) else -> MutableList(size, init) +} + +fun List.asArrayList(): ArrayList = if (this is ArrayList<*>) { + this as ArrayList +} else { + ArrayList(this) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt index 7e104e0f4..67ca43114 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt @@ -2,14 +2,15 @@ package org.koitharu.kotatsu.utils.ext import android.content.res.Resources import android.util.Log +import java.io.FileNotFoundException +import java.net.SocketTimeoutException import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException import org.koitharu.kotatsu.core.exceptions.WrongPasswordException import org.koitharu.kotatsu.parsers.exception.AuthRequiredException -import java.io.FileNotFoundException -import java.net.SocketTimeoutException +import org.koitharu.kotatsu.parsers.util.format fun Throwable.getDisplayMessage(resources: Resources) = when (this) { is AuthRequiredException -> resources.getString(R.string.auth_required) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/PrimitiveExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/PrimitiveExt.kt index 14e7cbb9d..a8359f5a1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/PrimitiveExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/PrimitiveExt.kt @@ -1,27 +1,3 @@ package org.koitharu.kotatsu.utils.ext -import java.text.DecimalFormat -import java.text.NumberFormat -import java.util.* - -fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = ' '): String { - val formatter = NumberFormat.getInstance(Locale.US) as DecimalFormat - val symbols = formatter.decimalFormatSymbols - if (thousandsSep != null) { - symbols.groupingSeparator = thousandsSep - formatter.isGroupingUsed = true - } else { - formatter.isGroupingUsed = false - } - symbols.decimalSeparator = decPoint - formatter.decimalFormatSymbols = symbols - formatter.minimumFractionDigits = decimals - formatter.maximumFractionDigits = decimals - return when (this) { - is Float, - is Double -> formatter.format(this.toDouble()) - else -> formatter.format(this.toLong()) - } -} - inline fun Int.ifZero(defaultValue: () -> Int): Int = if (this == 0) defaultValue() else this \ No newline at end of file