Run default Android Studio formatter on code

This commit is contained in:
arkon 2020-02-17 17:23:37 -05:00
parent a1fadce7c6
commit 3ecc883944
109 changed files with 640 additions and 585 deletions

View File

@ -1,4 +1,5 @@
package eu.kanade.tachiyomi.data.backup package eu.kanade.tachiyomi.data.backup
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID

View File

@ -217,10 +217,14 @@ class BackupRestoreService : Service() {
.concatMap { .concatMap {
val obj = it.asJsonObject val obj = it.asJsonObject
val manga = backupManager.parser.fromJson<MangaImpl>(obj.get(MANGA)) val manga = backupManager.parser.fromJson<MangaImpl>(obj.get(MANGA))
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(obj.get(CHAPTERS) ?: JsonArray()) val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(obj.get(CHAPTERS)
val categories = backupManager.parser.fromJson<List<String>>(obj.get(CATEGORIES) ?: JsonArray()) ?: JsonArray())
val history = backupManager.parser.fromJson<List<DHistory>>(obj.get(HISTORY) ?: JsonArray()) val categories = backupManager.parser.fromJson<List<String>>(obj.get(CATEGORIES)
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(obj.get(TRACK) ?: JsonArray()) ?: JsonArray())
val history = backupManager.parser.fromJson<List<DHistory>>(obj.get(HISTORY)
?: JsonArray())
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(obj.get(TRACK)
?: JsonArray())
val observable = getMangaRestoreObservable(manga, chapters, categories, history, tracks) val observable = getMangaRestoreObservable(manga, chapters, categories, history, tracks)
if (observable != null) { if (observable != null) {

View File

@ -1,3 +1,3 @@
package eu.kanade.tachiyomi.data.backup.models package eu.kanade.tachiyomi.data.backup.models
data class DHistory(val url: String,val lastRead: Long) data class DHistory(val url: String, val lastRead: Long)

View File

@ -20,8 +20,8 @@ class CoverCache(private val context: Context) {
/** /**
* Cache directory used for cache management. * Cache directory used for cache management.
*/ */
private val cacheDir = context.getExternalFilesDir("covers") ?: private val cacheDir = context.getExternalFilesDir("covers")
File(context.filesDir, "covers").also { it.mkdirs() } ?: File(context.filesDir, "covers").also { it.mkdirs() }
/** /**
* Returns the cover from cache. * Returns the cover from cache.

View File

@ -12,12 +12,12 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
* This class provides operations to manage the database through its interfaces. * This class provides operations to manage the database through its interfaces.
*/ */
open class DatabaseHelper(context: Context) open class DatabaseHelper(context: Context)
: MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries { : MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME) .name(DbOpenCallback.DATABASE_NAME)
.callback(DbOpenCallback()) .callback(DbOpenCallback())
.build() .build()
override val db = DefaultStorIOSQLite.builder() override val db = DefaultStorIOSQLite.builder()
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration)) .sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))

View File

@ -35,7 +35,7 @@ interface History : Serializable {
* @param chapter chapter object * @param chapter chapter object
* @return history object * @return history object
*/ */
fun create(chapter: Chapter): History = HistoryImpl().apply { fun create(chapter: Chapter): History = HistoryImpl().apply {
this.chapter_id = chapter.id!! this.chapter_id = chapter.id!!
} }
} }

View File

@ -53,13 +53,13 @@ interface ChapterQueries : DbProvider {
.prepare() .prepare()
fun getChapter(url: String, mangaId: Long) = db.get() fun getChapter(url: String, mangaId: Long) = db.get()
.`object`(Chapter::class.java) .`object`(Chapter::class.java)
.withQuery(Query.builder() .withQuery(Query.builder()
.table(ChapterTable.TABLE) .table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?") .where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(url, mangaId) .whereArgs(url, mangaId)
.build()) .build())
.prepare() .prepare()
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare() fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()

View File

@ -161,7 +161,8 @@ internal class DownloadNotifier(private val context: Context) {
fun onError(error: String? = null, chapter: String? = null) { fun onError(error: String? = null, chapter: String? = null) {
// Create notification // Create notification
with(notificationBuilder) { with(notificationBuilder) {
setContentTitle(chapter ?: context.getString(R.string.download_notifier_downloader_title)) setContentTitle(chapter
?: context.getString(R.string.download_notifier_downloader_title))
setContentText(error ?: context.getString(R.string.download_notifier_unkown_error)) setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
setSmallIcon(android.R.drawable.stat_sys_warning) setSmallIcon(android.R.drawable.stat_sys_warning)
clearActions() clearActions()

View File

@ -131,7 +131,8 @@ class DownloadService : Service() {
subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext) subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ state -> onNetworkStateChanged(state) .subscribe({ state ->
onNetworkStateChanged(state)
}, { }, {
toast(R.string.download_queue_error) toast(R.string.download_queue_error)
stopSelf() stopSelf()
@ -156,7 +157,9 @@ class DownloadService : Service() {
DISCONNECTED -> { DISCONNECTED -> {
downloadManager.stopDownloads(getString(R.string.download_notifier_no_network)) downloadManager.stopDownloads(getString(R.string.download_notifier_no_network))
} }
else -> { /* Do nothing */ } else -> {
/* Do nothing */
}
} }
} }

View File

@ -82,7 +82,8 @@ class Downloader(
/** /**
* Whether the downloader is running. * Whether the downloader is running.
*/ */
@Volatile private var isRunning: Boolean = false @Volatile
private var isRunning: Boolean = false
init { init {
launchNow { launchNow {
@ -175,7 +176,8 @@ class Downloader(
.concatMap { downloadChapter(it).subscribeOn(Schedulers.io()) } .concatMap { downloadChapter(it).subscribeOn(Schedulers.io()) }
.onBackpressureBuffer() .onBackpressureBuffer()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ completeDownload(it) .subscribe({
completeDownload(it)
}, { error -> }, { error ->
DownloadService.stop(context) DownloadService.stop(context)
Timber.e(error) Timber.e(error)
@ -376,10 +378,10 @@ class Downloader(
private fun getImageExtension(response: Response, file: UniFile): String { private fun getImageExtension(response: Response, file: UniFile): String {
// Read content type if available. // Read content type if available.
val mime = response.body?.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" } val mime = response.body?.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" }
// Else guess from the uri. // Else guess from the uri.
?: context.contentResolver.getType(file.uri) ?: context.contentResolver.getType(file.uri)
// Else read magic numbers. // Else read magic numbers.
?: ImageUtil.findImageType { file.openInputStream() }?.mime ?: ImageUtil.findImageType { file.openInputStream() }?.mime
return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) ?: "jpg" return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) ?: "jpg"
} }

View File

@ -10,17 +10,24 @@ class Download(val source: HttpSource, val manga: Manga, val chapter: Chapter) {
var pages: List<Page>? = null var pages: List<Page>? = null
@Volatile @Transient var totalProgress: Int = 0 @Volatile
@Transient
var totalProgress: Int = 0
@Volatile @Transient var downloadedImages: Int = 0 @Volatile
@Transient
var downloadedImages: Int = 0
@Volatile @Transient var status: Int = 0 @Volatile
@Transient
var status: Int = 0
set(status) { set(status) {
field = status field = status
statusSubject?.onNext(this) statusSubject?.onNext(this)
} }
@Transient private var statusSubject: PublishSubject<Download>? = null @Transient
private var statusSubject: PublishSubject<Download>? = null
fun setStatusSubject(subject: PublishSubject<Download>?) { fun setStatusSubject(subject: PublishSubject<Download>?) {
statusSubject = subject statusSubject = subject

View File

@ -12,7 +12,7 @@ import java.util.concurrent.CopyOnWriteArrayList
class DownloadQueue( class DownloadQueue(
private val store: DownloadStore, private val store: DownloadStore,
private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>()) private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>())
: List<Download> by queue { : List<Download> by queue {
private val statusSubject = PublishSubject.create<Download>() private val statusSubject = PublishSubject.create<Download>()
@ -42,7 +42,9 @@ class DownloadQueue(
} }
fun remove(chapters: List<Chapter>) { fun remove(chapters: List<Chapter>) {
for (chapter in chapters) { remove(chapter) } for (chapter in chapters) {
remove(chapter)
}
} }
fun remove(manga: Manga) { fun remove(manga: Manga) {
@ -59,7 +61,7 @@ class DownloadQueue(
} }
fun getActiveDownloads(): Observable<Download> = fun getActiveDownloads(): Observable<Download> =
Observable.from(this).filter { download -> download.status == Download.DOWNLOADING } Observable.from(this).filter { download -> download.status == Download.DOWNLOADING }
fun getStatusObservable(): Observable<Download> = statusSubject.onBackpressureBuffer() fun getStatusObservable(): Observable<Download> = statusSubject.onBackpressureBuffer()

View File

@ -19,7 +19,7 @@ import java.io.InputStream
class LibraryMangaUrlFetcher(private val networkFetcher: DataFetcher<InputStream>, class LibraryMangaUrlFetcher(private val networkFetcher: DataFetcher<InputStream>,
private val manga: Manga, private val manga: Manga,
private val file: File) private val file: File)
: FileFetcher(file) { : FileFetcher(file) {
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) { override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
if (!file.exists()) { if (!file.exists()) {

View File

@ -38,6 +38,6 @@ class TachiGlideModule : AppGlideModule() {
registry.replace(GlideUrl::class.java, InputStream::class.java, networkFactory) registry.replace(GlideUrl::class.java, InputStream::class.java, networkFactory)
registry.append(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory()) registry.append(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
registry.append(InputStream::class.java, InputStream::class.java, PassthroughModelLoader registry.append(InputStream::class.java, InputStream::class.java, PassthroughModelLoader
.Factory()) .Factory())
} }
} }

View File

@ -35,7 +35,7 @@ object LibraryUpdateRanker {
*/ */
fun lexicographicRanking(): Comparator<Manga> { fun lexicographicRanking(): Comparator<Manga> {
return Comparator { mangaFirst: Manga, return Comparator { mangaFirst: Manga,
mangaSecond: Manga -> mangaSecond: Manga ->
compareValues(mangaFirst.title, mangaSecond.title) compareValues(mangaFirst.title, mangaSecond.title)
} }
} }

View File

@ -312,7 +312,7 @@ class LibraryUpdateService(
.filter { pair -> pair.first.isNotEmpty() } .filter { pair -> pair.first.isNotEmpty() }
.doOnNext { .doOnNext {
if (downloadNew && (categoriesToDownload.isEmpty() || if (downloadNew && (categoriesToDownload.isEmpty() ||
manga.category in categoriesToDownload)) { manga.category in categoriesToDownload)) {
downloadChapters(manga, it.first) downloadChapters(manga, it.first)
hasDownloads = true hasDownloads = true
@ -321,8 +321,8 @@ class LibraryUpdateService(
// Convert to the manga that contains new chapters. // Convert to the manga that contains new chapters.
.map { .map {
Pair( Pair(
manga, manga,
(it.first.sortedByDescending { ch -> ch.source_order }.toTypedArray()) (it.first.sortedByDescending { ch -> ch.source_order }.toTypedArray())
) )
} }
} }
@ -573,7 +573,7 @@ class LibraryUpdateService(
var description = resources.getQuantityString(R.plurals.notification_chapters, chapters.size, chaptersDescription) var description = resources.getQuantityString(R.plurals.notification_chapters, chapters.size, chaptersDescription)
if (shouldTruncate) { if (shouldTruncate) {
description += " ${resources.getString(R.string.notification_and_n_more, (chapterNumbers.size - (NOTIF_MAX_CHAPTERS - 1)))}" description += " ${resources.getString(R.string.notification_and_n_more, (chapterNumbers.size - (NOTIF_MAX_CHAPTERS - 1)))}"
} }
return description return description

View File

@ -393,11 +393,11 @@ class NotificationReceiver : BroadcastReceiver() {
*/ */
internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int): PendingIntent { internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int): PendingIntent {
val newIntent = val newIntent =
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA) Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(MangaController.MANGA_EXTRA, manga.id) .putExtra(MangaController.MANGA_EXTRA, manga.id)
.putExtra("notificationId", manga.id.hashCode()) .putExtra("notificationId", manga.id.hashCode())
.putExtra("groupId", groupId) .putExtra("groupId", groupId)
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
@ -408,8 +408,8 @@ class NotificationReceiver : BroadcastReceiver() {
* @param manga manga of chapter * @param manga manga of chapter
*/ */
internal fun markAsReadPendingBroadcast(context: Context, manga: Manga, chapters: internal fun markAsReadPendingBroadcast(context: Context, manga: Manga, chapters:
Array<Chapter>, groupId: Int): Array<Chapter>, groupId: Int):
PendingIntent { PendingIntent {
val newIntent = Intent(context, NotificationReceiver::class.java).apply { val newIntent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_MARK_AS_READ action = ACTION_MARK_AS_READ
putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray()) putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())

View File

@ -137,7 +137,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
track.status = COMPLETED track.status = COMPLETED
} }
// If user was using API v1 fetch library_id // If user was using API v1 fetch library_id
if (track.library_id == null || track.library_id!! == 0L){ if (track.library_id == null || track.library_id!! == 0L) {
return api.findLibManga(track, getUsername().toInt()).flatMap { return api.findLibManga(track, getUsername().toInt()).flatMap {
if (it == null) { if (it == null) {
throw Exception("$track not found on user library") throw Exception("$track not found on user library")
@ -187,7 +187,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
return api.getCurrentUser().map { (username, scoreType) -> return api.getCurrentUser().map { (username, scoreType) ->
scorePreference.set(scoreType) scorePreference.set(scoreType)
saveCredentials(username.toString(), oauth.access_token) saveCredentials(username.toString(), oauth.access_token)
}.doOnError{ }.doOnError {
logout() logout()
}.toCompletable() }.toCompletable()
} }

View File

@ -250,7 +250,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private fun jsonToALManga(struct: JsonObject): ALManga { private fun jsonToALManga(struct: JsonObject): ALManga {
val date = try { val date = try {
val date = Calendar.getInstance() val date = Calendar.getInstance()
date.set(struct["startDate"]["year"].nullInt ?: 0, (struct["startDate"]["month"].nullInt ?: 0) - 1, date.set(struct["startDate"]["year"].nullInt ?: 0, (struct["startDate"]["month"].nullInt
?: 0) - 1,
struct["startDate"]["day"].nullInt ?: 0) struct["startDate"]["day"].nullInt ?: 0)
date.timeInMillis date.timeInMillis
} catch (_: Exception) { } catch (_: Exception) {

View File

@ -23,7 +23,7 @@ class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Int
if (token.isNullOrEmpty()) { if (token.isNullOrEmpty()) {
throw Exception("Not authenticated with Anilist") throw Exception("Not authenticated with Anilist")
} }
if (oauth == null){ if (oauth == null) {
oauth = anilist.loadOAuth() oauth = anilist.loadOAuth()
} }
// Refresh access token if null or expired. // Refresh access token if null or expired.

View File

@ -86,7 +86,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
fun findLibManga(track: Track): Observable<Track?> { fun findLibManga(track: Track): Observable<Track?> {
return authClient.newCall(GET(url = listEntryUrl(track.media_id))) return authClient.newCall(GET(url = listEntryUrl(track.media_id)))
.asObservable() .asObservable()
.map {response -> .map { response ->
var libTrack: Track? = null var libTrack: Track? = null
response.use { response.use {
if (it.priorResponse?.isRedirect != true) { if (it.priorResponse?.isRedirect != true) {
@ -96,7 +96,8 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
last_chapter_read = trackForm.select("#add_manga_num_read_chapters").`val`().toInt() last_chapter_read = trackForm.select("#add_manga_num_read_chapters").`val`().toInt()
total_chapters = trackForm.select("#totalChap").text().toInt() total_chapters = trackForm.select("#totalChap").text().toInt()
status = trackForm.select("#add_manga_status > option[selected]").`val`().toInt() status = trackForm.select("#add_manga_status > option[selected]").`val`().toInt()
score = trackForm.select("#add_manga_score > option[selected]").`val`().toFloatOrNull() ?: 0f score = trackForm.select("#add_manga_score > option[selected]").`val`().toFloatOrNull()
?: 0f
} }
} }
} }
@ -158,7 +159,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
private fun getListUrl(): Observable<String> { private fun getListUrl(): Observable<String> {
return authClient.newCall(POST(url = exportListUrl(), body = exportPostBody())) return authClient.newCall(POST(url = exportListUrl(), body = exportPostBody()))
.asObservable() .asObservable()
.map {response -> .map { response ->
baseUrl + Jsoup.parse(response.consumeBody()) baseUrl + Jsoup.parse(response.consumeBody())
.select("div.goodresult") .select("div.goodresult")
.select("a") .select("a")
@ -233,7 +234,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.toString() .toString()
private fun addUrl() = Uri.parse(baseModifyListUrl).buildUpon() private fun addUrl() = Uri.parse(baseModifyListUrl).buildUpon()
.appendPath( "add.json") .appendPath("add.json")
.toString() .toString()
private fun listEntryUrl(mediaId: Int) = Uri.parse(baseModifyListUrl).buildUpon() private fun listEntryUrl(mediaId: Int) = Uri.parse(baseModifyListUrl).buildUpon()
@ -300,6 +301,6 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
"Dropped" -> 4 "Dropped" -> 4
"Plan to Read" -> 6 "Plan to Read" -> 6
else -> 1 else -> 1
} }
} }
} }

View File

@ -8,7 +8,7 @@ import okhttp3.Response
import okio.Buffer import okio.Buffer
import org.json.JSONObject import org.json.JSONObject
class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor { class MyAnimeListInterceptor(private val myanimelist: Myanimelist) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
myanimelist.ensureLoggedIn() myanimelist.ensureLoggedIn()

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.updater
abstract class UpdateResult { abstract class UpdateResult {
open class NewUpdate<T : Release>(val release: T): UpdateResult() open class NewUpdate<T : Release>(val release: T) : UpdateResult()
open class NoNewUpdate: UpdateResult() open class NoNewUpdate : UpdateResult()
} }

View File

@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.data.updater.UpdateResult
sealed class DevRepoUpdateResult : UpdateResult() { sealed class DevRepoUpdateResult : UpdateResult() {
class NewUpdate(release: DevRepoRelease): UpdateResult.NewUpdate<DevRepoRelease>(release) class NewUpdate(release: DevRepoRelease) : UpdateResult.NewUpdate<DevRepoRelease>(release)
class NoNewUpdate: UpdateResult.NoNewUpdate() class NoNewUpdate : UpdateResult.NoNewUpdate()
} }

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.data.updater.Release
*/ */
class GithubRelease(@SerializedName("tag_name") val version: String, class GithubRelease(@SerializedName("tag_name") val version: String,
@SerializedName("body") override val info: String, @SerializedName("body") override val info: String,
@SerializedName("assets") private val assets: List<Assets>): Release { @SerializedName("assets") private val assets: List<Assets>) : Release {
/** /**
* Get download link of latest release from the assets. * Get download link of latest release from the assets.

View File

@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.data.updater.UpdateResult
sealed class GithubUpdateResult : UpdateResult() { sealed class GithubUpdateResult : UpdateResult() {
class NewUpdate(release: GithubRelease): UpdateResult.NewUpdate<GithubRelease>(release) class NewUpdate(release: GithubRelease) : UpdateResult.NewUpdate<GithubRelease>(release)
class NoNewUpdate : UpdateResult.NoNewUpdate() class NoNewUpdate : UpdateResult.NoNewUpdate()
} }

View File

@ -206,7 +206,7 @@ class ExtensionManager(
* *
* @param extension The extension to be updated. * @param extension The extension to be updated.
*/ */
fun updateExtension(extension: Extension.Installed): Observable<InstallStep> { fun updateExtension(extension: Extension.Installed): Observable<InstallStep> {
val availableExt = availableExtensions.find { it.pkgName == extension.pkgName } val availableExt = availableExtensions.find { it.pkgName == extension.pkgName }
?: return Observable.empty() ?: return Observable.empty()
return installExtension(availableExt) return installExtension(availableExt)

View File

@ -25,7 +25,7 @@ internal class ExtensionGithubApi {
val call = GET("$REPO_URL/index.json") val call = GET("$REPO_URL/index.json")
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
parseResponse(network.client.newCall(call).await()) parseResponse(network.client.newCall(call).await())
} }
} }

View File

@ -31,12 +31,13 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
/** /**
* Returns the intent filter this receiver should subscribe to. * Returns the intent filter this receiver should subscribe to.
*/ */
private val filter get() = IntentFilter().apply { private val filter
addAction(Intent.ACTION_PACKAGE_ADDED) get() = IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_REPLACED) addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED) addAction(Intent.ACTION_PACKAGE_REPLACED)
addDataScheme("package") addAction(Intent.ACTION_PACKAGE_REMOVED)
} addDataScheme("package")
}
/** /**
* Called when one of the events of the [filter] is received. When the package is an extension, * Called when one of the events of the [filter] is received. When the package is an extension,
@ -61,7 +62,8 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
when (result) { when (result) {
is LoadResult.Success -> listener.onExtensionUpdated(result.extension) is LoadResult.Success -> listener.onExtensionUpdated(result.extension)
// Not needed as a package can't be upgraded if the signature is different // Not needed as a package can't be upgraded if the signature is different
is LoadResult.Untrusted -> {} is LoadResult.Untrusted -> {
}
} }
} }
} }
@ -92,8 +94,8 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
* @param intent The intent containing the package name of the extension. * @param intent The intent containing the package name of the extension.
*/ */
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent) ?: val pkgName = getPackageNameFromIntent(intent)
return LoadResult.Error("Package name not found") ?: return LoadResult.Error("Package name not found")
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await() return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await()
} }

View File

@ -95,8 +95,8 @@ internal object ExtensionLoader {
return LoadResult.Error(error) return LoadResult.Error(error)
} }
val extName = pkgManager.getApplicationLabel(appInfo)?.toString() val extName = pkgManager.getApplicationLabel(appInfo).toString()
.orEmpty().substringAfter("Tachiyomi: ") .orEmpty().substringAfter("Tachiyomi: ")
val versionName = pkgInfo.versionName val versionName = pkgInfo.versionName
val versionCode = pkgInfo.versionCode val versionCode = pkgInfo.versionCode

View File

@ -44,9 +44,9 @@ class AndroidCookieJar : CookieJar {
} }
cookies.split(";") cookies.split(";")
.map { it.substringBefore("=") } .map { it.substringBefore("=") }
.filterNames() .filterNames()
.onEach { manager.setCookie(urlString, "$it=;Max-Age=$maxAge") } .onEach { manager.setCookie(urlString, "$it=;Max-Age=$maxAge") }
} }
fun removeAll() { fun removeAll() {

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.network
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
@ -104,7 +103,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
// HTTP error codes are only received since M // HTTP error codes are only received since M
if (WebViewFeature.isFeatureSupported(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR) && if (WebViewFeature.isFeatureSupported(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR) &&
url == origRequestUrl && !challengeFound url == origRequestUrl && !challengeFound
) { ) {
// The first request didn't return the challenge, abort. // The first request didn't return the challenge, abort.
latch.countDown() latch.countDown()

View File

@ -129,17 +129,17 @@ class LocalSource(private val context: Context) : CatalogueSource {
.filter { it.extension.equals("json") } .filter { it.extension.equals("json") }
.firstOrNull() .firstOrNull()
?.apply { ?.apply {
val json = Gson().fromJson(Scanner(this).useDelimiter("\\Z").next(), JsonObject::class.java) val json = Gson().fromJson(Scanner(this).useDelimiter("\\Z").next(), JsonObject::class.java)
manga.title = json["title"]?.asString ?: manga.title manga.title = json["title"]?.asString ?: manga.title
manga.author = json["author"]?.asString ?: manga.author manga.author = json["author"]?.asString ?: manga.author
manga.artist = json["artist"]?.asString ?: manga.artist manga.artist = json["artist"]?.asString ?: manga.artist
manga.description = json["description"]?.asString ?: manga.description manga.description = json["description"]?.asString ?: manga.description
manga.genre = json["genre"]?.asJsonArray manga.genre = json["genre"]?.asJsonArray
?.map { it.asString } ?.map { it.asString }
?.joinToString(", ") ?.joinToString(", ")
?: manga.genre ?: manga.genre
manga.status = json["status"]?.asInt ?: manga.status manga.status = json["status"]?.asInt ?: manga.status
} }
return Observable.just(manga) return Observable.just(manga)
} }
@ -210,34 +210,34 @@ class LocalSource(private val context: Context) : CatalogueSource {
return when (format) { return when (format) {
is Format.Directory -> { is Format.Directory -> {
val entry = format.file.listFiles() val entry = format.file.listFiles()
.sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }) .sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } .find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { updateCover(context, manga, it.inputStream())} entry?.let { updateCover(context, manga, it.inputStream()) }
} }
is Format.Zip -> { is Format.Zip -> {
ZipFile(format.file).use { zip -> ZipFile(format.file).use { zip ->
val entry = zip.entries().toList() val entry = zip.entries().toList()
.sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }) .sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
entry?.let { updateCover(context, manga, zip.getInputStream(it) )} entry?.let { updateCover(context, manga, zip.getInputStream(it)) }
} }
} }
is Format.Rar -> { is Format.Rar -> {
Archive(format.file).use { archive -> Archive(format.file).use { archive ->
val entry = archive.fileHeaders val entry = archive.fileHeaders
.sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) }) .sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) })
.find { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } } .find { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
entry?.let { updateCover(context, manga, archive.getInputStream(it) )} entry?.let { updateCover(context, manga, archive.getInputStream(it)) }
} }
} }
is Format.Epub -> { is Format.Epub -> {
EpubFile(format.file).use { epub -> EpubFile(format.file).use { epub ->
val entry = epub.getImagesFromPages() val entry = epub.getImagesFromPages()
.firstOrNull() .firstOrNull()
?.let { epub.getEntry(it) } ?.let { epub.getEntry(it) }
entry?.let { updateCover(context, manga, epub.getInputStream(it)) } entry?.let { updateCover(context, manga, epub.getInputStream(it)) }
} }
@ -252,7 +252,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
sealed class Format { sealed class Format {
data class Directory(val file: File) : Format() data class Directory(val file: File) : Format()
data class Zip(val file: File) : Format() data class Zip(val file: File) : Format()
data class Rar(val file: File): Format() data class Rar(val file: File) : Format()
data class Epub(val file: File) : Format() data class Epub(val file: File) : Format()
} }

View File

@ -17,7 +17,8 @@ sealed class Filter<T>(val name: String, var state: T) {
const val STATE_EXCLUDE = 2 const val STATE_EXCLUDE = 2
} }
} }
abstract class Group<V>(name: String, state: List<V>): Filter<List<V>>(name, state)
abstract class Group<V>(name: String, state: List<V>) : Filter<List<V>>(name, state)
abstract class Sort(name: String, val values: Array<String>, state: Selection? = null) abstract class Sort(name: String, val values: Array<String>, state: Selection? = null)
: Filter<Sort.Selection?>(name, state) { : Filter<Sort.Selection?>(name, state) {

View File

@ -14,15 +14,20 @@ open class Page(
val number: Int val number: Int
get() = index + 1 get() = index + 1
@Transient @Volatile var status: Int = 0 @Transient
@Volatile
var status: Int = 0
set(value) { set(value) {
field = value field = value
statusSubject?.onNext(value) statusSubject?.onNext(value)
} }
@Transient @Volatile var progress: Int = 0 @Transient
@Volatile
var progress: Int = 0
@Transient private var statusSubject: Subject<Int, Int>? = null @Transient
private var statusSubject: Subject<Int, Int>? = null
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
progress = if (contentLength > 0) { progress = if (contentLength > 0) {

View File

@ -10,6 +10,6 @@ class SChapterImpl : SChapter {
override var chapter_number: Float = -1f override var chapter_number: Float = -1f
override var scanlator: String? = null override var scanlator: String? = null
} }

View File

@ -69,7 +69,7 @@ abstract class HttpSource : CatalogueSource {
/** /**
* Headers builder for requests. Implementations can override this method for custom headers. * Headers builder for requests. Implementations can override this method for custom headers.
*/ */
open protected fun headersBuilder() = Headers.Builder().apply { protected open fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)")
} }
@ -97,14 +97,14 @@ abstract class HttpSource : CatalogueSource {
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
abstract protected fun popularMangaRequest(page: Int): Request protected abstract fun popularMangaRequest(page: Int): Request
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun popularMangaParse(response: Response): MangasPage protected abstract fun popularMangaParse(response: Response): MangasPage
/** /**
* Returns an observable containing a page with a list of manga. Normally it's not needed to * Returns an observable containing a page with a list of manga. Normally it's not needed to
@ -129,14 +129,14 @@ abstract class HttpSource : CatalogueSource {
* @param query the search query. * @param query the search query.
* @param filters the list of filters to apply. * @param filters the list of filters to apply.
*/ */
abstract protected fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request protected abstract fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun searchMangaParse(response: Response): MangasPage protected abstract fun searchMangaParse(response: Response): MangasPage
/** /**
* Returns an observable containing a page with a list of latest manga updates. * Returns an observable containing a page with a list of latest manga updates.
@ -156,14 +156,14 @@ abstract class HttpSource : CatalogueSource {
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
abstract protected fun latestUpdatesRequest(page: Int): Request protected abstract fun latestUpdatesRequest(page: Int): Request
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun latestUpdatesParse(response: Response): MangasPage protected abstract fun latestUpdatesParse(response: Response): MangasPage
/** /**
* Returns an observable with the updated details for a manga. Normally it's not needed to * Returns an observable with the updated details for a manga. Normally it's not needed to
@ -194,7 +194,7 @@ abstract class HttpSource : CatalogueSource {
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun mangaDetailsParse(response: Response): SManga protected abstract fun mangaDetailsParse(response: Response): SManga
/** /**
* Returns an observable with the updated chapter list for a manga. Normally it's not needed to * Returns an observable with the updated chapter list for a manga. Normally it's not needed to
@ -220,7 +220,7 @@ abstract class HttpSource : CatalogueSource {
* *
* @param manga the manga to look for chapters. * @param manga the manga to look for chapters.
*/ */
open protected fun chapterListRequest(manga: SManga): Request { protected open fun chapterListRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers) return GET(baseUrl + manga.url, headers)
} }
@ -229,7 +229,7 @@ abstract class HttpSource : CatalogueSource {
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun chapterListParse(response: Response): List<SChapter> protected abstract fun chapterListParse(response: Response): List<SChapter>
/** /**
* Returns an observable with the page list for a chapter. * Returns an observable with the page list for a chapter.
@ -250,7 +250,7 @@ abstract class HttpSource : CatalogueSource {
* *
* @param chapter the chapter whose page list has to be fetched. * @param chapter the chapter whose page list has to be fetched.
*/ */
open protected fun pageListRequest(chapter: SChapter): Request { protected open fun pageListRequest(chapter: SChapter): Request {
return GET(baseUrl + chapter.url, headers) return GET(baseUrl + chapter.url, headers)
} }
@ -259,7 +259,7 @@ abstract class HttpSource : CatalogueSource {
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun pageListParse(response: Response): List<Page> protected abstract fun pageListParse(response: Response): List<Page>
/** /**
* Returns an observable with the page containing the source url of the image. If there's any * Returns an observable with the page containing the source url of the image. If there's any
@ -279,7 +279,7 @@ abstract class HttpSource : CatalogueSource {
* *
* @param page the chapter whose page list has to be fetched * @param page the chapter whose page list has to be fetched
*/ */
open protected fun imageUrlRequest(page: Page): Request { protected open fun imageUrlRequest(page: Page): Request {
return GET(page.url, headers) return GET(page.url, headers)
} }
@ -288,7 +288,7 @@ abstract class HttpSource : CatalogueSource {
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun imageUrlParse(response: Response): String protected abstract fun imageUrlParse(response: Response): String
/** /**
* Returns an observable with the response of the source image. * Returns an observable with the response of the source image.
@ -306,7 +306,7 @@ abstract class HttpSource : CatalogueSource {
* *
* @param page the chapter whose page list has to be fetched * @param page the chapter whose page list has to be fetched
*/ */
open protected fun imageRequest(page: Page): Request { protected open fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers) return GET(page.imageUrl!!, headers)
} }

View File

@ -6,10 +6,10 @@ import rx.Observable
fun HttpSource.getImageUrl(page: Page): Observable<Page> { fun HttpSource.getImageUrl(page: Page): Observable<Page> {
page.status = Page.LOAD_PAGE page.status = Page.LOAD_PAGE
return fetchImageUrl(page) return fetchImageUrl(page)
.doOnError { page.status = Page.ERROR } .doOnError { page.status = Page.ERROR }
.onErrorReturn { null } .onErrorReturn { null }
.doOnNext { page.imageUrl = it } .doOnNext { page.imageUrl = it }
.map { page } .map { page }
} }
fun HttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> { fun HttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {

View File

@ -36,7 +36,7 @@ abstract class ParsedHttpSource : HttpSource() {
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
*/ */
abstract protected fun popularMangaSelector(): String protected abstract fun popularMangaSelector(): String
/** /**
* Returns a manga from the given [element]. Most sites only show the title and the url, it's * Returns a manga from the given [element]. Most sites only show the title and the url, it's
@ -44,13 +44,13 @@ abstract class ParsedHttpSource : HttpSource() {
* *
* @param element an element obtained from [popularMangaSelector]. * @param element an element obtained from [popularMangaSelector].
*/ */
abstract protected fun popularMangaFromElement(element: Element): SManga protected abstract fun popularMangaFromElement(element: Element): SManga
/** /**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if * Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page. * there's no next page.
*/ */
abstract protected fun popularMangaNextPageSelector(): String? protected abstract fun popularMangaNextPageSelector(): String?
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
@ -74,7 +74,7 @@ abstract class ParsedHttpSource : HttpSource() {
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
*/ */
abstract protected fun searchMangaSelector(): String protected abstract fun searchMangaSelector(): String
/** /**
* Returns a manga from the given [element]. Most sites only show the title and the url, it's * Returns a manga from the given [element]. Most sites only show the title and the url, it's
@ -82,13 +82,13 @@ abstract class ParsedHttpSource : HttpSource() {
* *
* @param element an element obtained from [searchMangaSelector]. * @param element an element obtained from [searchMangaSelector].
*/ */
abstract protected fun searchMangaFromElement(element: Element): SManga protected abstract fun searchMangaFromElement(element: Element): SManga
/** /**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if * Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page. * there's no next page.
*/ */
abstract protected fun searchMangaNextPageSelector(): String? protected abstract fun searchMangaNextPageSelector(): String?
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
@ -112,7 +112,7 @@ abstract class ParsedHttpSource : HttpSource() {
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
*/ */
abstract protected fun latestUpdatesSelector(): String protected abstract fun latestUpdatesSelector(): String
/** /**
* Returns a manga from the given [element]. Most sites only show the title and the url, it's * Returns a manga from the given [element]. Most sites only show the title and the url, it's
@ -120,13 +120,13 @@ abstract class ParsedHttpSource : HttpSource() {
* *
* @param element an element obtained from [latestUpdatesSelector]. * @param element an element obtained from [latestUpdatesSelector].
*/ */
abstract protected fun latestUpdatesFromElement(element: Element): SManga protected abstract fun latestUpdatesFromElement(element: Element): SManga
/** /**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if * Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page. * there's no next page.
*/ */
abstract protected fun latestUpdatesNextPageSelector(): String? protected abstract fun latestUpdatesNextPageSelector(): String?
/** /**
* Parses the response from the site and returns the details of a manga. * Parses the response from the site and returns the details of a manga.
@ -142,7 +142,7 @@ abstract class ParsedHttpSource : HttpSource() {
* *
* @param document the parsed document. * @param document the parsed document.
*/ */
abstract protected fun mangaDetailsParse(document: Document): SManga protected abstract fun mangaDetailsParse(document: Document): SManga
/** /**
* Parses the response from the site and returns a list of chapters. * Parses the response from the site and returns a list of chapters.
@ -157,14 +157,14 @@ abstract class ParsedHttpSource : HttpSource() {
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter.
*/ */
abstract protected fun chapterListSelector(): String protected abstract fun chapterListSelector(): String
/** /**
* Returns a chapter from the given element. * Returns a chapter from the given element.
* *
* @param element an element obtained from [chapterListSelector]. * @param element an element obtained from [chapterListSelector].
*/ */
abstract protected fun chapterFromElement(element: Element): SChapter protected abstract fun chapterFromElement(element: Element): SChapter
/** /**
* Parses the response from the site and returns the page list. * Parses the response from the site and returns the page list.
@ -180,7 +180,7 @@ abstract class ParsedHttpSource : HttpSource() {
* *
* @param document the parsed document. * @param document the parsed document.
*/ */
abstract protected fun pageListParse(document: Document): List<Page> protected abstract fun pageListParse(document: Document): List<Page>
/** /**
* Parse the response from the site and returns the absolute url to the source image. * Parse the response from the site and returns the absolute url to the source image.
@ -196,5 +196,5 @@ abstract class ParsedHttpSource : HttpSource() {
* *
* @param document the parsed document. * @param document the parsed document.
*/ */
abstract protected fun imageUrlParse(document: Document): String protected abstract fun imageUrlParse(document: Document): String
} }

View File

@ -55,7 +55,7 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View
open fun onViewCreated(view: View) { } open fun onViewCreated(view: View) {}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
if (type.isEnter) { if (type.isEnter) {
@ -90,6 +90,7 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
* Issue link: https://issuetracker.google.com/issues/37657375 * Issue link: https://issuetracker.google.com/issues/37657375
*/ */
var expandActionViewFromInteraction = false var expandActionViewFromInteraction = false
fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) { fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
setOnActionExpandListener(object : MenuItem.OnActionExpandListener { setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {

View File

@ -23,8 +23,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onNext function to execute when the observable emits an item. * @param onNext function to execute when the observable emits an item.
* @param onError function to execute when the observable throws an error. * @param onError function to execute when the observable throws an error.
*/ */
fun <T> Observable<T>.subscribeFirst(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) fun <T> Observable<T>.subscribeFirst(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = compose(deliverFirst<T>()).subscribe(split(onNext, onError)).apply { add(this) }
= compose(deliverFirst<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/** /**
* Subscribes an observable with [deliverLatestCache] and adds it to the presenter's lifecycle * Subscribes an observable with [deliverLatestCache] and adds it to the presenter's lifecycle
@ -33,8 +32,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onNext function to execute when the observable emits an item. * @param onNext function to execute when the observable emits an item.
* @param onError function to execute when the observable throws an error. * @param onError function to execute when the observable throws an error.
*/ */
fun <T> Observable<T>.subscribeLatestCache(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) fun <T> Observable<T>.subscribeLatestCache(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = compose(deliverLatestCache<T>()).subscribe(split(onNext, onError)).apply { add(this) }
= compose(deliverLatestCache<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/** /**
* Subscribes an observable with [deliverReplay] and adds it to the presenter's lifecycle * Subscribes an observable with [deliverReplay] and adds it to the presenter's lifecycle
@ -43,8 +41,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onNext function to execute when the observable emits an item. * @param onNext function to execute when the observable emits an item.
* @param onError function to execute when the observable throws an error. * @param onError function to execute when the observable throws an error.
*/ */
fun <T> Observable<T>.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) fun <T> Observable<T>.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = compose(deliverReplay<T>()).subscribe(split(onNext, onError)).apply { add(this) }
= compose(deliverReplay<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/** /**
* Subscribes an observable with [DeliverWithView] and adds it to the presenter's lifecycle * Subscribes an observable with [DeliverWithView] and adds it to the presenter's lifecycle
@ -53,8 +50,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onNext function to execute when the observable emits an item. * @param onNext function to execute when the observable emits an item.
* @param onError function to execute when the observable throws an error. * @param onError function to execute when the observable throws an error.
*/ */
fun <T> Observable<T>.subscribeWithView(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) fun <T> Observable<T>.subscribeWithView(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = compose(DeliverWithView<V, T>(view())).subscribe(split(onNext, onError)).apply { add(this) }
= compose(DeliverWithView<V, T>(view())).subscribe(split(onNext, onError)).apply { add(this) }
/** /**
* A deliverable that only emits to the view if attached, otherwise the event is ignored. * A deliverable that only emits to the view if attached, otherwise the event is ignored.

View File

@ -192,7 +192,7 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
.subscribeUntilDestroy { performGlobalSearch(it.queryText().toString()) } .subscribeUntilDestroy { performGlobalSearch(it.queryText().toString()) }
} }
fun performGlobalSearch(query: String){ fun performGlobalSearch(query: String) {
router.pushController(CatalogueSearchController(query).withFadeTransaction()) router.pushController(CatalogueSearchController(query).withFadeTransaction())
} }

View File

@ -347,7 +347,8 @@ open class BrowseCatalogueController(bundle: Bundle) :
snack?.dismiss() snack?.dismiss()
if (catalogue_view != null) { if (catalogue_view != null) {
val message = if (error is NoResultsException) catalogue_view.context.getString(R.string.no_results_found) else (error.message ?: "") val message = if (error is NoResultsException) catalogue_view.context.getString(R.string.no_results_found) else (error.message
?: "")
snack = catalogue_view.snack(message, Snackbar.LENGTH_INDEFINITE) { snack = catalogue_view.snack(message, Snackbar.LENGTH_INDEFINITE) {
setAction(R.string.action_retry) { setAction(R.string.action_retry) {
@ -497,7 +498,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
0 -> { 0 -> {
presenter.changeMangaFavorite(manga) presenter.changeMangaFavorite(manga)
adapter?.notifyItemChanged(position) adapter?.notifyItemChanged(position)
activity?.toast(activity?.getString(R.string.manga_removed_library)) activity.toast(activity.getString(R.string.manga_removed_library))
} }
} }
}.show() }.show()
@ -522,7 +523,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
.showDialog(router) .showDialog(router)
} }
} }
activity?.toast(activity?.getString(R.string.manga_added_library)) activity.toast(activity.getString(R.string.manga_added_library))
} }
} }

View File

@ -74,11 +74,12 @@ open class CatalogueSearchPresenter(
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
extensionFilter = savedState?.getString(CatalogueSearchPresenter::extensionFilter.name) ?: extensionFilter = savedState?.getString(CatalogueSearchPresenter::extensionFilter.name)
initialExtensionFilter ?: initialExtensionFilter
// Perform a search with previous or initial state // Perform a search with previous or initial state
search(savedState?.getString(BrowseCataloguePresenter::query.name) ?: initialQuery.orEmpty()) search(savedState?.getString(BrowseCataloguePresenter::query.name)
?: initialQuery.orEmpty())
} }
override fun onDestroy() { override fun onDestroy() {
@ -117,10 +118,10 @@ open class CatalogueSearchPresenter(
} }
val filterSources = extensionManager.installedExtensions val filterSources = extensionManager.installedExtensions
.filter { it.pkgName == filter } .filter { it.pkgName == filter }
.flatMap { it.sources } .flatMap { it.sources }
.filter { it in enabledSources } .filter { it in enabledSources }
.filterIsInstance<CatalogueSource>() .filterIsInstance<CatalogueSource>()
if (filterSources.isEmpty()) { if (filterSources.isEmpty()) {
return enabledSources return enabledSources

View File

@ -10,7 +10,7 @@ import rx.schedulers.Schedulers
/** /**
* LatestUpdatesPager inherited from the general Pager. * LatestUpdatesPager inherited from the general Pager.
*/ */
class LatestUpdatesPager(val source: CatalogueSource): Pager() { class LatestUpdatesPager(val source: CatalogueSource) : Pager() {
override fun requestNext(): Observable<MangasPage> { override fun requestNext(): Observable<MangasPage> {
return source.fetchLatestUpdates(currentPage) return source.fetchLatestUpdates(currentPage)

View File

@ -37,7 +37,7 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
// Update circle letter image. // Update circle letter image.
itemView.post { itemView.post {
image.setImageDrawable(image.getRound(category.name.take(1).toUpperCase(),false)) image.setImageDrawable(image.getRound(category.name.take(1).toUpperCase(), false))
} }
} }

View File

@ -126,11 +126,11 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
} }
searchView.queryTextChanges() searchView.queryTextChanges()
.filter { router.backstack.lastOrNull()?.controller() == this } .filter { router.backstack.lastOrNull()?.controller() == this }
.subscribeUntilDestroy { .subscribeUntilDestroy {
query = it.toString() query = it.toString()
drawExtensions() drawExtensions()
} }
// Fixes problem with the overflow icon showing up in lieu of search // Fixes problem with the overflow icon showing up in lieu of search
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })

View File

@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class ExtensionFilterController: SettingsController() { class ExtensionFilterController : SettingsController() {
override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
titleRes = R.string.action_filter titleRes = R.string.action_filter

View File

@ -46,7 +46,7 @@ open class ExtensionPresenter(
.startWith(emptyList<Extension.Available>()) .startWith(emptyList<Extension.Available>())
return Observable.combineLatest(installedObservable, untrustedObservable, availableObservable) return Observable.combineLatest(installedObservable, untrustedObservable, availableObservable)
{ installed, untrusted, available -> Triple(installed, untrusted, available) } { installed, untrusted, available -> Triple(installed, untrusted, available) }
.debounce(100, TimeUnit.MILLISECONDS) .debounce(100, TimeUnit.MILLISECONDS)
.map(::toItems) .map(::toItems)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -67,9 +67,11 @@ open class ExtensionPresenter(
val untrustedSorted = untrusted.sortedBy { it.pkgName } val untrustedSorted = untrusted.sortedBy { it.pkgName }
val availableSorted = available val availableSorted = available
// Filter out already installed extensions and disabled languages // Filter out already installed extensions and disabled languages
.filter { avail -> installed.none { it.pkgName == avail.pkgName } .filter { avail ->
&& untrusted.none { it.pkgName == avail.pkgName } installed.none { it.pkgName == avail.pkgName }
&& (avail.lang in activeLangs || avail.lang == "all")} && untrusted.none { it.pkgName == avail.pkgName }
&& (avail.lang in activeLangs || avail.lang == "all")
}
.sortedBy { it.pkgName } .sortedBy { it.pkgName }
if (updatesSorted.isNotEmpty()) { if (updatesSorted.isNotEmpty()) {

View File

@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle) class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
where T : Controller, T: ExtensionTrustDialog.Listener { where T : Controller, T : ExtensionTrustDialog.Listener {
constructor(target: T, signatureHash: String, pkgName: String) : this(Bundle().apply { constructor(target: T, signatureHash: String, pkgName: String) : this(Bundle().apply {
putString(SIGNATURE_KEY, signatureHash) putString(SIGNATURE_KEY, signatureHash)

View File

@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.DialogCheckboxView import eu.kanade.tachiyomi.widget.DialogCheckboxView
class DeleteLibraryMangasDialog<T>(bundle: Bundle? = null) : class DeleteLibraryMangasDialog<T>(bundle: Bundle? = null) :
DialogController(bundle) where T : Controller, T: DeleteLibraryMangasDialog.Listener { DialogController(bundle) where T : Controller, T : DeleteLibraryMangasDialog.Listener {
private var mangas = emptyList<Manga>() private var mangas = emptyList<Manga>()

View File

@ -43,7 +43,7 @@ class LibraryGridHolder(
text = item.downloadCount.toString() text = item.downloadCount.toString()
} }
//set local visibility if its local manga //set local visibility if its local manga
local_text.visibility = if(item.manga.source == LocalSource.ID) View.VISIBLE else View.GONE local_text.visibility = if (item.manga.source == LocalSource.ID) View.VISIBLE else View.GONE
// Update the cover. // Update the cover.
GlideApp.with(view.context).clear(thumbnail) GlideApp.with(view.context).clear(thumbnail)

View File

@ -64,15 +64,15 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
*/ */
override fun filter(constraint: String): Boolean { override fun filter(constraint: String): Boolean {
return manga.title.contains(constraint, true) || return manga.title.contains(constraint, true) ||
(manga.author?.contains(constraint, true) ?: false) || (manga.author?.contains(constraint, true) ?: false) ||
(manga.artist?.contains(constraint, true) ?: false) || (manga.artist?.contains(constraint, true) ?: false) ||
sourceManager.getOrStub(manga.source).name.contains(constraint, true) || sourceManager.getOrStub(manga.source).name.contains(constraint, true) ||
if (constraint.contains(",")) { if (constraint.contains(",")) {
val genres = manga.genre?.split(", ") val genres = manga.genre?.split(", ")
constraint.split(",").all { containsGenre(it.trim(), genres) } constraint.split(",").all { containsGenre(it.trim(), genres) }
} else { } else {
containsGenre(constraint, manga.genre?.split(", ")) containsGenre(constraint, manga.genre?.split(", "))
} }
} }
private fun containsGenre(tag: String, genres: List<String>?): Boolean { private fun containsGenre(tag: String, genres: List<String>?): Boolean {

View File

@ -89,14 +89,14 @@ class LibraryPresenter(
fun subscribeLibrary() { fun subscribeLibrary() {
if (librarySubscription.isNullOrUnsubscribed()) { if (librarySubscription.isNullOrUnsubscribed()) {
librarySubscription = getLibraryObservable() librarySubscription = getLibraryObservable()
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io())) { .combineLatest(downloadTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib, _ -> lib.apply { setDownloadCount(mangaMap) } lib.apply { setDownloadCount(mangaMap) }
} }
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { .combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib, _ -> lib.copy(mangaMap = applyFilters(lib.mangaMap)) lib.copy(mangaMap = applyFilters(lib.mangaMap))
} }
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) { .combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib, _ -> lib.copy(mangaMap = applySort(lib.mangaMap)) lib.copy(mangaMap = applySort(lib.mangaMap))
} }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, (categories, mangaMap) -> .subscribeLatestCache({ view, (categories, mangaMap) ->
@ -117,7 +117,7 @@ class LibraryPresenter(
val filterCompleted = preferences.filterCompleted().getOrDefault() val filterCompleted = preferences.filterCompleted().getOrDefault()
val filterFn: (LibraryItem) -> Boolean = f@ { item -> val filterFn: (LibraryItem) -> Boolean = f@{ item ->
// Filter when there isn't unread chapters. // Filter when there isn't unread chapters.
if (filterUnread && item.manga.unread == 0) { if (filterUnread && item.manga.unread == 0) {
return@f false return@f false
@ -231,16 +231,15 @@ class LibraryPresenter(
* @return an observable of the categories and its manga. * @return an observable of the categories and its manga.
*/ */
private fun getLibraryObservable(): Observable<Library> { private fun getLibraryObservable(): Observable<Library> {
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga ->
dbCategories, libraryManga -> val categories = if (libraryManga.containsKey(0))
val categories = if (libraryManga.containsKey(0)) arrayListOf(Category.createDefault()) + dbCategories
arrayListOf(Category.createDefault()) + dbCategories else
else dbCategories
dbCategories
this.categories = categories this.categories = categories
Library(categories, libraryManga) Library(categories, libraryManga)
} }
} }
/** /**

View File

@ -6,5 +6,5 @@ sealed class LibrarySelectionEvent {
class Selected(val manga: Manga) : LibrarySelectionEvent() class Selected(val manga: Manga) : LibrarySelectionEvent()
class Unselected(val manga: Manga) : LibrarySelectionEvent() class Unselected(val manga: Manga) : LibrarySelectionEvent()
class Cleared() : LibrarySelectionEvent() class Cleared : LibrarySelectionEvent()
} }

View File

@ -4,7 +4,7 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
class DeepLinkActivity: Activity() { class DeepLinkActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@ -29,23 +29,23 @@ class TabsAnimator(val tabs: TabLayout) {
init { init {
tabs.viewTreeObserver.addOnGlobalLayoutListener( tabs.viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener { object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
if (tabs.height > 0) { if (tabs.height > 0) {
tabs.viewTreeObserver.removeOnGlobalLayoutListener(this) tabs.viewTreeObserver.removeOnGlobalLayoutListener(this)
// Save the tabs default height. // Save the tabs default height.
tabsHeight = tabs.height tabsHeight = tabs.height
// Now that we know the height, set the initial height. // Now that we know the height, set the initial height.
if (isLastStateShown) { if (isLastStateShown) {
setHeight(tabsHeight) setHeight(tabsHeight)
} else { } else {
setHeight(0) setHeight(0)
}
} }
} }
} }
}
) )
} }

View File

@ -17,9 +17,12 @@ class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem
var status: Int var status: Int
get() = download?.status ?: _status get() = download?.status ?: _status
set(value) { _status = value } set(value) {
_status = value
}
@Transient var download: Download? = null @Transient
var download: Download? = null
val isDownloaded: Boolean val isDownloaded: Boolean
get() = status == Download.DOWNLOADED get() = status == Download.DOWNLOADED

View File

@ -139,11 +139,11 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
menuFilterDownloaded.isChecked = presenter.onlyDownloaded() menuFilterDownloaded.isChecked = presenter.onlyDownloaded()
menuFilterBookmarked.isChecked = presenter.onlyBookmarked() menuFilterBookmarked.isChecked = presenter.onlyBookmarked()
// Disable unread filter option if read filter is enabled.
if (presenter.onlyRead()) if (presenter.onlyRead())
//Disable unread filter option if read filter is enabled.
menuFilterUnread.isEnabled = false menuFilterUnread.isEnabled = false
// Disable read filter option if unread filter is enabled.
if (presenter.onlyUnread()) if (presenter.onlyUnread())
//Disable read filter option if unread filter is enabled.
menuFilterRead.isEnabled = false menuFilterRead.isEnabled = false
// Display mode submenu // Display mode submenu

View File

@ -109,8 +109,8 @@ class ChaptersPresenter(
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.filter { download -> download.manga.id == manga.id } .filter { download -> download.manga.id == manga.id }
.doOnNext { onDownloadStatusChange(it) } .doOnNext { onDownloadStatusChange(it) }
.subscribeLatestCache(ChaptersController::onChapterStatusChange) { .subscribeLatestCache(ChaptersController::onChapterStatusChange) { _, error ->
_, error -> Timber.e(error) Timber.e(error)
} }
} }
@ -176,8 +176,7 @@ class ChaptersPresenter(
var observable = Observable.from(chapters).subscribeOn(Schedulers.io()) var observable = Observable.from(chapters).subscribeOn(Schedulers.io())
if (onlyUnread()) { if (onlyUnread()) {
observable = observable.filter { !it.read } observable = observable.filter { !it.read }
} } else if (onlyRead()) {
else if (onlyRead()) {
observable = observable.filter { it.read } observable = observable.filter { it.read }
} }
if (onlyDownloaded()) { if (onlyDownloaded()) {

View File

@ -91,7 +91,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
fab_favorite.clicks().subscribeUntilDestroy { onFabClick() } fab_favorite.clicks().subscribeUntilDestroy { onFabClick() }
// Set onLongClickListener to manage categories when FAB is clicked. // Set onLongClickListener to manage categories when FAB is clicked.
fab_favorite.longClicks().subscribeUntilDestroy{ onFabLongClick() } fab_favorite.longClicks().subscribeUntilDestroy { onFabLongClick() }
// Set SwipeRefresh to refresh manga data. // Set SwipeRefresh to refresh manga data.
swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() } swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() }
@ -488,7 +488,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
activity?.toast(R.string.icon_creation_fail) activity?.toast(R.string.icon_creation_fail)
} }
override fun onLoadCleared(placeholder: Drawable?) { } override fun onLoadCleared(placeholder: Drawable?) {}
}) })
} }

View File

@ -59,7 +59,7 @@ class TrackSearchDialog : DialogController {
.onPositive { _, _ -> onPositiveButtonClick() } .onPositive { _, _ -> onPositiveButtonClick() }
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.neutralText(R.string.action_remove) .neutralText(R.string.action_remove)
.onNeutral { _, _ -> onRemoveButtonClick() } .onNeutral { _, _ -> onRemoveButtonClick() }
.build() .build()
if (subscriptions.isUnsubscribed) { if (subscriptions.isUnsubscribed) {

View File

@ -4,13 +4,13 @@ import eu.kanade.tachiyomi.R
object MigrationFlags { object MigrationFlags {
private const val CHAPTERS = 0b001 private const val CHAPTERS = 0b001
private const val CATEGORIES = 0b010 private const val CATEGORIES = 0b010
private const val TRACK = 0b100 private const val TRACK = 0b100
private const val CHAPTERS2 = 0x1 private const val CHAPTERS2 = 0x1
private const val CATEGORIES2 = 0x2 private const val CATEGORIES2 = 0x2
private const val TRACK2 = 0x4 private const val TRACK2 = 0x4
val titles get() = arrayOf(R.string.chapters, R.string.categories, R.string.track) val titles get() = arrayOf(R.string.chapters, R.string.categories, R.string.track)

View File

@ -37,7 +37,7 @@ class SourceHolder(view: View, override val adapter: SourceAdapter) :
// Set circle letter image. // Set circle letter image.
itemView.post { itemView.post {
image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(),false)) image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(), false))
} }
} }
} }

View File

@ -32,7 +32,7 @@ class PageIndicatorTextView(
// Also add a bit of spacing between each character, as the stroke overlaps them // Also add a bit of spacing between each character, as the stroke overlaps them
val finalText = SpannableString(currText.asIterable().joinToString("\u00A0")).apply { val finalText = SpannableString(currText.asIterable().joinToString("\u00A0")).apply {
// Apply text outline // Apply text outline
setSpan(spanOutline, 1, length-1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) setSpan(spanOutline, 1, length - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
for (i in 1..lastIndex step 2) { for (i in 1..lastIndex step 2) {
setSpan(ScaleXSpan(0.2f), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) setSpan(ScaleXSpan(0.2f), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

View File

@ -555,40 +555,40 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
val sharedRotation = preferences.rotation().asObservable().share() val sharedRotation = preferences.rotation().asObservable().share()
val initialRotation = sharedRotation.take(1) val initialRotation = sharedRotation.take(1)
val rotationUpdates = sharedRotation.skip(1) val rotationUpdates = sharedRotation.skip(1)
.delay(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .delay(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
subscriptions += Observable.merge(initialRotation, rotationUpdates) subscriptions += Observable.merge(initialRotation, rotationUpdates)
.subscribe { setOrientation(it) } .subscribe { setOrientation(it) }
subscriptions += preferences.readerTheme().asObservable() subscriptions += preferences.readerTheme().asObservable()
.skip(1) // We only care about updates .skip(1) // We only care about updates
.subscribe { recreate() } .subscribe { recreate() }
subscriptions += preferences.showPageNumber().asObservable() subscriptions += preferences.showPageNumber().asObservable()
.subscribe { setPageNumberVisibility(it) } .subscribe { setPageNumberVisibility(it) }
subscriptions += preferences.trueColor().asObservable() subscriptions += preferences.trueColor().asObservable()
.subscribe { setTrueColor(it) } .subscribe { setTrueColor(it) }
subscriptions += preferences.fullscreen().asObservable() subscriptions += preferences.fullscreen().asObservable()
.subscribe { setFullscreen(it) } .subscribe { setFullscreen(it) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
subscriptions += preferences.cutoutShort().asObservable() subscriptions += preferences.cutoutShort().asObservable()
.subscribe { setCutoutShort(it)} .subscribe { setCutoutShort(it) }
} }
subscriptions += preferences.keepScreenOn().asObservable() subscriptions += preferences.keepScreenOn().asObservable()
.subscribe { setKeepScreenOn(it) } .subscribe { setKeepScreenOn(it) }
subscriptions += preferences.customBrightness().asObservable() subscriptions += preferences.customBrightness().asObservable()
.subscribe { setCustomBrightness(it) } .subscribe { setCustomBrightness(it) }
subscriptions += preferences.colorFilter().asObservable() subscriptions += preferences.colorFilter().asObservable()
.subscribe { setColorFilter(it) } .subscribe { setColorFilter(it) }
subscriptions += preferences.colorFilterMode().asObservable() subscriptions += preferences.colorFilterMode().asObservable()
.subscribe { setColorFilter(preferences.colorFilter().getOrDefault()) } .subscribe { setColorFilter(preferences.colorFilter().getOrDefault()) }
} }
/** /**
@ -684,8 +684,8 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
private fun setCustomBrightness(enabled: Boolean) { private fun setCustomBrightness(enabled: Boolean) {
if (enabled) { if (enabled) {
customBrightnessSubscription = preferences.customBrightnessValue().asObservable() customBrightnessSubscription = preferences.customBrightnessValue().asObservable()
.sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe { setCustomBrightnessValue(it) } .subscribe { setCustomBrightnessValue(it) }
subscriptions.add(customBrightnessSubscription) subscriptions.add(customBrightnessSubscription)
} else { } else {
@ -700,8 +700,8 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
private fun setColorFilter(enabled: Boolean) { private fun setColorFilter(enabled: Boolean) {
if (enabled) { if (enabled) {
customFilterColorSubscription = preferences.colorFilterValue().asObservable() customFilterColorSubscription = preferences.colorFilterValue().asObservable()
.sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe { setColorFilterValue(it) } .subscribe { setColorFilterValue(it) }
subscriptions.add(customFilterColorSubscription) subscriptions.add(customFilterColorSubscription)
} else { } else {

View File

@ -55,13 +55,13 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
// Initialize subscriptions. // Initialize subscriptions.
subscriptions += preferences.colorFilter().asObservable() subscriptions += preferences.colorFilter().asObservable()
.subscribe { setColorFilter(it, view) } .subscribe { setColorFilter(it, view) }
subscriptions += preferences.colorFilterMode().asObservable() subscriptions += preferences.colorFilterMode().asObservable()
.subscribe { setColorFilter(preferences.colorFilter().getOrDefault(), view) } .subscribe { setColorFilter(preferences.colorFilter().getOrDefault(), view) }
subscriptions += preferences.customBrightness().asObservable() subscriptions += preferences.customBrightness().asObservable()
.subscribe { setCustomBrightness(it, view) } .subscribe { setCustomBrightness(it, view) }
// Get color and update values // Get color and update values
val color = preferences.colorFilterValue().getOrDefault() val color = preferences.colorFilterValue().getOrDefault()
@ -202,8 +202,8 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
private fun setCustomBrightness(enabled: Boolean, view: View) { private fun setCustomBrightness(enabled: Boolean, view: View) {
if (enabled) { if (enabled) {
customBrightnessSubscription = preferences.customBrightnessValue().asObservable() customBrightnessSubscription = preferences.customBrightnessValue().asObservable()
.sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe { setCustomBrightnessValue(it, view) } .subscribe { setCustomBrightnessValue(it, view) }
subscriptions.add(customBrightnessSubscription) subscriptions.add(customBrightnessSubscription)
} else { } else {
@ -241,8 +241,8 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
private fun setColorFilter(enabled: Boolean, view: View) { private fun setColorFilter(enabled: Boolean, view: View) {
if (enabled) { if (enabled) {
customFilterColorSubscription = preferences.colorFilterValue().asObservable() customFilterColorSubscription = preferences.colorFilterValue().asObservable()
.sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe { setColorFilterValue(it, view) } .subscribe { setColorFilterValue(it, view) }
subscriptions.add(customFilterColorSubscription) subscriptions.add(customFilterColorSubscription)
} else { } else {

View File

@ -47,14 +47,14 @@ class ReaderPageSheet(
if (page.status != Page.READY) return if (page.status != Page.READY) return
MaterialDialog.Builder(activity) MaterialDialog.Builder(activity)
.content(activity.getString(R.string.confirm_set_image_as_cover)) .content(activity.getString(R.string.confirm_set_image_as_cover))
.positiveText(android.R.string.yes) .positiveText(android.R.string.yes)
.negativeText(android.R.string.no) .negativeText(android.R.string.no)
.onPositive { _, _ -> .onPositive { _, _ ->
activity.setAsCover(page) activity.setAsCover(page)
dismiss() dismiss()
} }
.show() .show()
} }
/** /**

View File

@ -37,12 +37,12 @@ class SaveImageNotifier(private val context: Context) {
*/ */
fun onComplete(file: File) { fun onComplete(file: File) {
val bitmap = GlideApp.with(context) val bitmap = GlideApp.with(context)
.asBitmap() .asBitmap()
.load(file) .load(file)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true) .skipMemoryCache(true)
.submit(720, 1280) .submit(720, 1280)
.get() .get()
if (bitmap != null) { if (bitmap != null) {
showCompleteNotification(file, bitmap) showCompleteNotification(file, bitmap)

View File

@ -31,34 +31,34 @@ class ChapterLoader(
} }
return Observable.just(chapter) return Observable.just(chapter)
.doOnNext { chapter.state = ReaderChapter.State.Loading } .doOnNext { chapter.state = ReaderChapter.State.Loading }
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.flatMap { .flatMap {
Timber.d("Loading pages for ${chapter.chapter.name}") Timber.d("Loading pages for ${chapter.chapter.name}")
val loader = getPageLoader(it) val loader = getPageLoader(it)
chapter.pageLoader = loader chapter.pageLoader = loader
loader.getPages().take(1).doOnNext { pages -> loader.getPages().take(1).doOnNext { pages ->
pages.forEach { it.chapter = chapter } pages.forEach { it.chapter = chapter }
} }
}
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { pages ->
if (pages.isEmpty()) {
throw Exception("Page list is empty")
} }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { pages ->
if (pages.isEmpty()) {
throw Exception("Page list is empty")
}
chapter.state = ReaderChapter.State.Loaded(pages) chapter.state = ReaderChapter.State.Loaded(pages)
// If the chapter is partially read, set the starting page to the last the user read // If the chapter is partially read, set the starting page to the last the user read
// otherwise use the requested page. // otherwise use the requested page.
if (!chapter.chapter.read) { if (!chapter.chapter.read) {
chapter.requestedPage = chapter.chapter.last_page_read chapter.requestedPage = chapter.chapter.last_page_read
}
} }
} .toCompletable()
.toCompletable() .doOnError { chapter.state = ReaderChapter.State.Error(it) }
.doOnError { chapter.state = ReaderChapter.State.Error(it) }
} }
/** /**

View File

@ -19,16 +19,16 @@ class DirectoryPageLoader(val file: File) : PageLoader() {
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return file.listFiles() return file.listFiles()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } .filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
.sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }) .sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, file -> .mapIndexed { i, file ->
val streamFn = { FileInputStream(file) } val streamFn = { FileInputStream(file) }
ReaderPage(i).apply { ReaderPage(i).apply {
stream = streamFn stream = streamFn
status = Page.READY status = Page.READY
}
} }
} .let { Observable.just(it) }
.let { Observable.just(it) }
} }
/** /**

View File

@ -31,15 +31,15 @@ class DownloadPageLoader(
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return downloadManager.buildPageList(source, manga, chapter.chapter) return downloadManager.buildPageList(source, manga, chapter.chapter)
.map { pages -> .map { pages ->
pages.map { page -> pages.map { page ->
ReaderPage(page.index, page.url, page.imageUrl) { ReaderPage(page.index, page.url, page.imageUrl) {
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!! context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
}.apply { }.apply {
status = Page.READY status = Page.READY
}
} }
} }
}
} }
override fun getPage(page: ReaderPage): Observable<Int> { override fun getPage(page: ReaderPage): Observable<Int> {

View File

@ -30,14 +30,14 @@ class EpubPageLoader(file: File) : PageLoader() {
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return epub.getImagesFromPages() return epub.getImagesFromPages()
.mapIndexed { i, path -> .mapIndexed { i, path ->
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) } val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
ReaderPage(i).apply { ReaderPage(i).apply {
stream = streamFn stream = streamFn
status = Page.READY status = Page.READY
}
} }
} .let { Observable.just(it) }
.let { Observable.just(it) }
} }
/** /**

View File

@ -66,14 +66,14 @@ class HttpPageLoader(
val pages = chapter.pages val pages = chapter.pages
if (pages != null) { if (pages != null) {
Completable Completable
.fromAction { .fromAction {
// Convert to pages without reader information // Convert to pages without reader information
val pagesToSave = pages.map { Page(it.index, it.url, it.imageUrl) } val pagesToSave = pages.map { Page(it.index, it.url, it.imageUrl) }
chapterCache.putPageListToCache(chapter.chapter, pagesToSave) chapterCache.putPageListToCache(chapter.chapter, pagesToSave)
} }
.onErrorComplete() .onErrorComplete()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe() .subscribe()
} }
} }
@ -86,7 +86,8 @@ class HttpPageLoader(
.getPageListFromCache(chapter.chapter) .getPageListFromCache(chapter.chapter)
.onErrorResumeNext { source.fetchPageList(chapter.chapter) } .onErrorResumeNext { source.fetchPageList(chapter.chapter) }
.map { pages -> .map { pages ->
pages.mapIndexed { index, page -> // Don't trust sources and use our own indexing pages.mapIndexed { index, page ->
// Don't trust sources and use our own indexing
ReaderPage(index, page.url, page.imageUrl) ReaderPage(index, page.url, page.imageUrl)
} }
} }
@ -166,7 +167,7 @@ class HttpPageLoader(
private class PriorityPage( private class PriorityPage(
val page: ReaderPage, val page: ReaderPage,
val priority: Int val priority: Int
): Comparable<PriorityPage> { ) : Comparable<PriorityPage> {
companion object { companion object {
private val idGenerator = AtomicInteger() private val idGenerator = AtomicInteger()
@ -196,10 +197,10 @@ class HttpPageLoader(
private fun HttpSource.getImageUrl(page: ReaderPage): Observable<ReaderPage> { private fun HttpSource.getImageUrl(page: ReaderPage): Observable<ReaderPage> {
page.status = Page.LOAD_PAGE page.status = Page.LOAD_PAGE
return fetchImageUrl(page) return fetchImageUrl(page)
.doOnError { page.status = Page.ERROR } .doOnError { page.status = Page.ERROR }
.onErrorReturn { null } .onErrorReturn { null }
.doOnNext { page.imageUrl = it } .doOnNext { page.imageUrl = it }
.map { page } .map { page }
} }
/** /**
@ -212,19 +213,19 @@ class HttpPageLoader(
val imageUrl = page.imageUrl ?: return Observable.just(page) val imageUrl = page.imageUrl ?: return Observable.just(page)
return Observable.just(page) return Observable.just(page)
.flatMap { .flatMap {
if (!chapterCache.isImageInCache(imageUrl)) { if (!chapterCache.isImageInCache(imageUrl)) {
cacheImage(page) cacheImage(page)
} else { } else {
Observable.just(page) Observable.just(page)
}
} }
} .doOnNext {
.doOnNext { page.stream = { chapterCache.getImageFile(imageUrl).inputStream() }
page.stream = { chapterCache.getImageFile(imageUrl).inputStream() } page.status = Page.READY
page.status = Page.READY }
} .doOnError { page.status = Page.ERROR }
.doOnError { page.status = Page.ERROR } .onErrorReturn { page }
.onErrorReturn { page }
} }
/** /**
@ -235,7 +236,7 @@ class HttpPageLoader(
private fun HttpSource.cacheImage(page: ReaderPage): Observable<ReaderPage> { private fun HttpSource.cacheImage(page: ReaderPage): Observable<ReaderPage> {
page.status = Page.DOWNLOAD_IMAGE page.status = Page.DOWNLOAD_IMAGE
return fetchImage(page) return fetchImage(page)
.doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) } .doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) }
.map { page } .map { page }
} }
} }

View File

@ -43,17 +43,17 @@ class RarPageLoader(file: File) : PageLoader() {
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return archive.fileHeaders return archive.fileHeaders
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } } .filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
.sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) }) .sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) })
.mapIndexed { i, header -> .mapIndexed { i, header ->
val streamFn = { getStream(header) } val streamFn = { getStream(header) }
ReaderPage(i).apply { ReaderPage(i).apply {
stream = streamFn stream = streamFn
status = Page.READY status = Page.READY
}
} }
} .let { Observable.just(it) }
.let { Observable.just(it) }
} }
/** /**

View File

@ -33,16 +33,16 @@ class ZipPageLoader(file: File) : PageLoader() {
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return zip.entries().toList() return zip.entries().toList()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } .filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }) .sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, entry -> .mapIndexed { i, entry ->
val streamFn = { zip.getInputStream(entry) } val streamFn = { zip.getInputStream(entry) }
ReaderPage(i).apply { ReaderPage(i).apply {
stream = streamFn stream = streamFn
status = Page.READY status = Page.READY
}
} }
} .let { Observable.just(it) }
.let { Observable.just(it) }
} }
/** /**

View File

@ -8,6 +8,7 @@ sealed class ChapterTransition {
class Prev( class Prev(
override val from: ReaderChapter, override val to: ReaderChapter? override val from: ReaderChapter, override val to: ReaderChapter?
) : ChapterTransition() ) : ChapterTransition()
class Next( class Next(
override val from: ReaderChapter, override val to: ReaderChapter? override val from: ReaderChapter, override val to: ReaderChapter?
) : ChapterTransition() ) : ChapterTransition()

View File

@ -157,7 +157,7 @@ class ReaderProgressBar @JvmOverloads constructor(
if (!animate) { if (!animate) {
visibility = View.GONE visibility = View.GONE
} else { } else {
ObjectAnimator.ofFloat(this, "alpha", 1f, 0f).apply { ObjectAnimator.ofFloat(this, "alpha", 1f, 0f).apply {
interpolator = DecelerateInterpolator() interpolator = DecelerateInterpolator()
duration = 1000 duration = 1000
addListener(object : AnimatorListenerAdapter() { addListener(object : AnimatorListenerAdapter() {

View File

@ -45,31 +45,31 @@ class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelpe
init { init {
preferences.readWithTapping() preferences.readWithTapping()
.register({ tappingEnabled = it }) .register({ tappingEnabled = it })
preferences.readWithLongTap() preferences.readWithLongTap()
.register({ longTapEnabled = it }) .register({ longTapEnabled = it })
preferences.pageTransitions() preferences.pageTransitions()
.register({ usePageTransitions = it }) .register({ usePageTransitions = it })
preferences.imageScaleType() preferences.imageScaleType()
.register({ imageScaleType = it }, { imagePropertyChangedListener?.invoke() }) .register({ imageScaleType = it }, { imagePropertyChangedListener?.invoke() })
preferences.zoomStart() preferences.zoomStart()
.register({ zoomTypeFromPreference(it) }, { imagePropertyChangedListener?.invoke() }) .register({ zoomTypeFromPreference(it) }, { imagePropertyChangedListener?.invoke() })
preferences.cropBorders() preferences.cropBorders()
.register({ imageCropBorders = it }, { imagePropertyChangedListener?.invoke() }) .register({ imageCropBorders = it }, { imagePropertyChangedListener?.invoke() })
preferences.doubleTapAnimSpeed() preferences.doubleTapAnimSpeed()
.register({ doubleTapAnimDuration = it }) .register({ doubleTapAnimDuration = it })
preferences.readWithVolumeKeys() preferences.readWithVolumeKeys()
.register({ volumeKeysEnabled = it }) .register({ volumeKeysEnabled = it })
preferences.readWithVolumeKeysInverted() preferences.readWithVolumeKeysInverted()
.register({ volumeKeysInverted = it }) .register({ volumeKeysInverted = it })
} }
fun unsubscribe() { fun unsubscribe() {
@ -81,12 +81,12 @@ class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelpe
onChanged: (T) -> Unit = {} onChanged: (T) -> Unit = {}
) { ) {
asObservable() asObservable()
.doOnNext(valueAssignment) .doOnNext(valueAssignment)
.skip(1) .skip(1)
.distinctUntilChanged() .distinctUntilChanged()
.doOnNext(onChanged) .doOnNext(onChanged)
.subscribe() .subscribe()
.addTo(subscriptions) .addTo(subscriptions)
} }
private fun zoomTypeFromPreference(value: Int) { private fun zoomTypeFromPreference(value: Int) {

View File

@ -127,8 +127,8 @@ class PagerPageHolder(
val loader = page.chapter.pageLoader ?: return val loader = page.chapter.pageLoader ?: return
statusSubscription = loader.getPage(page) statusSubscription = loader.getPage(page)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { processStatus(it) } .subscribe { processStatus(it) }
} }
/** /**
@ -138,11 +138,11 @@ class PagerPageHolder(
progressSubscription?.unsubscribe() progressSubscription?.unsubscribe()
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
.map { page.progress } .map { page.progress }
.distinctUntilChanged() .distinctUntilChanged()
.onBackpressureLatest() .onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> progressBar.setProgress(value) } .subscribe { value -> progressBar.setProgress(value) }
} }
/** /**
@ -234,25 +234,25 @@ class PagerPageHolder(
var openStream: InputStream? = null var openStream: InputStream? = null
readImageHeaderSubscription = Observable readImageHeaderSubscription = Observable
.fromCallable { .fromCallable {
val stream = streamFn().buffered(16) val stream = streamFn().buffered(16)
openStream = stream openStream = stream
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (!isAnimated) {
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
} else {
initImageView().setImage(openStream!!)
} }
} .subscribeOn(Schedulers.io())
// Keep the Rx stream alive to close the input stream only when unsubscribed .observeOn(AndroidSchedulers.mainThread())
.flatMap { Observable.never<Unit>() } .doOnNext { isAnimated ->
.doOnUnsubscribe { openStream?.close() } if (!isAnimated) {
.subscribe({}, {}) initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
} else {
initImageView().setImage(openStream!!)
}
}
// Keep the Rx stream alive to close the input stream only when unsubscribed
.flatMap { Observable.never<Unit>() }
.doOnUnsubscribe { openStream?.close() }
.subscribe({}, {})
} }
/** /**
@ -436,36 +436,36 @@ class PagerPageHolder(
*/ */
private fun ImageView.setImage(stream: InputStream) { private fun ImageView.setImage(stream: InputStream) {
GlideApp.with(this) GlideApp.with(this)
.load(stream) .load(stream)
.skipMemoryCache(true) .skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.transition(DrawableTransitionOptions.with(NoTransition.getFactory())) .transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
.listener(object : RequestListener<Drawable> { .listener(object : RequestListener<Drawable> {
override fun onLoadFailed( override fun onLoadFailed(
e: GlideException?, e: GlideException?,
model: Any?, model: Any?,
target: Target<Drawable>?, target: Target<Drawable>?,
isFirstResource: Boolean isFirstResource: Boolean
): Boolean { ): Boolean {
onImageDecodeError() onImageDecodeError()
return false return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
} }
onImageDecoded()
return false override fun onResourceReady(
} resource: Drawable?,
}) model: Any?,
.into(this) target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
}
onImageDecoded()
return false
}
})
.into(this)
} }
} }

View File

@ -140,16 +140,17 @@ class PagerTransitionHolder(
private fun observeStatus(chapter: ReaderChapter) { private fun observeStatus(chapter: ReaderChapter) {
statusSubscription?.unsubscribe() statusSubscription?.unsubscribe()
statusSubscription = chapter.stateObserver statusSubscription = chapter.stateObserver
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { state -> .subscribe { state ->
pagesContainer.removeAllViews() pagesContainer.removeAllViews()
when (state) { when (state) {
is ReaderChapter.State.Wait -> {} is ReaderChapter.State.Wait -> {
is ReaderChapter.State.Loading -> setLoading() }
is ReaderChapter.State.Error -> setError(state.error) is ReaderChapter.State.Loading -> setLoading()
is ReaderChapter.State.Loaded -> setLoaded() is ReaderChapter.State.Error -> setError(state.error)
is ReaderChapter.State.Loaded -> setLoaded()
}
} }
}
} }
/** /**

View File

@ -85,7 +85,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
else -> activity.toggleMenu() else -> activity.toggleMenu()
} }
} }
pager.longTapListener = f@ { pager.longTapListener = f@{
if (activity.menuVisible || config.longTapEnabled) { if (activity.menuVisible || config.longTapEnabled) {
val item = adapter.items.getOrNull(pager.currentItem) val item = adapter.items.getOrNull(pager.currentItem)
if (item is ReaderPage) { if (item is ReaderPage) {

View File

@ -36,22 +36,22 @@ class WebtoonConfig(preferences: PreferencesHelper = Injekt.get()) {
init { init {
preferences.readWithTapping() preferences.readWithTapping()
.register({ tappingEnabled = it }) .register({ tappingEnabled = it })
preferences.readWithLongTap() preferences.readWithLongTap()
.register({ longTapEnabled = it }) .register({ longTapEnabled = it })
preferences.cropBordersWebtoon() preferences.cropBordersWebtoon()
.register({ imageCropBorders = it }, { imagePropertyChangedListener?.invoke() }) .register({ imageCropBorders = it }, { imagePropertyChangedListener?.invoke() })
preferences.doubleTapAnimSpeed() preferences.doubleTapAnimSpeed()
.register({ doubleTapAnimDuration = it }) .register({ doubleTapAnimDuration = it })
preferences.readWithVolumeKeys() preferences.readWithVolumeKeys()
.register({ volumeKeysEnabled = it }) .register({ volumeKeysEnabled = it })
preferences.readWithVolumeKeysInverted() preferences.readWithVolumeKeysInverted()
.register({ volumeKeysInverted = it }) .register({ volumeKeysInverted = it })
} }
fun unsubscribe() { fun unsubscribe() {
@ -63,12 +63,12 @@ class WebtoonConfig(preferences: PreferencesHelper = Injekt.get()) {
onChanged: (T) -> Unit = {} onChanged: (T) -> Unit = {}
) { ) {
asObservable() asObservable()
.doOnNext(valueAssignment) .doOnNext(valueAssignment)
.skip(1) .skip(1)
.distinctUntilChanged() .distinctUntilChanged()
.doOnNext(onChanged) .doOnNext(onChanged)
.subscribe() .subscribe()
.addTo(subscriptions) .addTo(subscriptions)
} }
} }

View File

@ -44,10 +44,10 @@ class WebtoonLayoutManager(activity: ReaderActivity) : LinearLayoutManager(activ
val child = if (mOrientation == HORIZONTAL) val child = if (mOrientation == HORIZONTAL)
mHorizontalBoundCheck mHorizontalBoundCheck
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0) .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
else else
mVerticalBoundCheck mVerticalBoundCheck
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0) .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
return if (child == null) NO_POSITION else getPosition(child) return if (child == null) NO_POSITION else getPosition(child)
} }

View File

@ -149,8 +149,8 @@ class WebtoonPageHolder(
val page = page ?: return val page = page ?: return
val loader = page.chapter.pageLoader ?: return val loader = page.chapter.pageLoader ?: return
statusSubscription = loader.getPage(page) statusSubscription = loader.getPage(page)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { processStatus(it) } .subscribe { processStatus(it) }
addSubscription(statusSubscription) addSubscription(statusSubscription)
} }
@ -164,11 +164,11 @@ class WebtoonPageHolder(
val page = page ?: return val page = page ?: return
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
.map { page.progress } .map { page.progress }
.distinctUntilChanged() .distinctUntilChanged()
.onBackpressureLatest() .onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> progressBar.setProgress(value) } .subscribe { value -> progressBar.setProgress(value) }
addSubscription(progressSubscription) addSubscription(progressSubscription)
} }
@ -266,29 +266,29 @@ class WebtoonPageHolder(
var openStream: InputStream? = null var openStream: InputStream? = null
readImageHeaderSubscription = Observable readImageHeaderSubscription = Observable
.fromCallable { .fromCallable {
val stream = streamFn().buffered(16) val stream = streamFn().buffered(16)
openStream = stream openStream = stream
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (!isAnimated) {
val subsamplingView = initSubsamplingImageView()
subsamplingView.visible()
subsamplingView.setImage(ImageSource.inputStream(openStream!!))
} else {
val imageView = initImageView()
imageView.visible()
imageView.setImage(openStream!!)
} }
} .subscribeOn(Schedulers.io())
// Keep the Rx stream alive to close the input stream only when unsubscribed .observeOn(AndroidSchedulers.mainThread())
.flatMap { Observable.never<Unit>() } .doOnNext { isAnimated ->
.doOnUnsubscribe { openStream?.close() } if (!isAnimated) {
.subscribe({}, {}) val subsamplingView = initSubsamplingImageView()
subsamplingView.visible()
subsamplingView.setImage(ImageSource.inputStream(openStream!!))
} else {
val imageView = initImageView()
imageView.visible()
imageView.setImage(openStream!!)
}
}
// Keep the Rx stream alive to close the input stream only when unsubscribed
.flatMap { Observable.never<Unit>() }
.doOnUnsubscribe { openStream?.close() }
.subscribe({}, {})
addSubscription(readImageHeaderSubscription) addSubscription(readImageHeaderSubscription)
} }
@ -328,7 +328,7 @@ class WebtoonPageHolder(
val size = 48.dpToPx val size = 48.dpToPx
layoutParams = FrameLayout.LayoutParams(size, size).apply { layoutParams = FrameLayout.LayoutParams(size, size).apply {
gravity = Gravity.CENTER_HORIZONTAL gravity = Gravity.CENTER_HORIZONTAL
setMargins(0, parentHeight/4, 0, 0) setMargins(0, parentHeight / 4, 0, 0)
} }
} }
progressContainer.addView(progress) progressContainer.addView(progress)
@ -389,7 +389,7 @@ class WebtoonPageHolder(
AppCompatButton(context).apply { AppCompatButton(context).apply {
layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
gravity = Gravity.CENTER_HORIZONTAL gravity = Gravity.CENTER_HORIZONTAL
setMargins(0, parentHeight/4, 0, 0) setMargins(0, parentHeight / 4, 0, 0)
} }
setText(R.string.action_retry) setText(R.string.action_retry)
setOnClickListener { setOnClickListener {
@ -411,7 +411,7 @@ class WebtoonPageHolder(
val decodeLayout = LinearLayout(context).apply { val decodeLayout = LinearLayout(context).apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, parentHeight).apply { layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, parentHeight).apply {
setMargins(0, parentHeight/6, 0, 0) setMargins(0, parentHeight / 6, 0, 0)
} }
gravity = Gravity.CENTER_HORIZONTAL gravity = Gravity.CENTER_HORIZONTAL
orientation = LinearLayout.VERTICAL orientation = LinearLayout.VERTICAL
@ -476,36 +476,36 @@ class WebtoonPageHolder(
*/ */
private fun ImageView.setImage(stream: InputStream) { private fun ImageView.setImage(stream: InputStream) {
GlideApp.with(this) GlideApp.with(this)
.load(stream) .load(stream)
.skipMemoryCache(true) .skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.transition(DrawableTransitionOptions.with(NoTransition.getFactory())) .transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
.listener(object : RequestListener<Drawable> { .listener(object : RequestListener<Drawable> {
override fun onLoadFailed( override fun onLoadFailed(
e: GlideException?, e: GlideException?,
model: Any?, model: Any?,
target: Target<Drawable>?, target: Target<Drawable>?,
isFirstResource: Boolean isFirstResource: Boolean
): Boolean { ): Boolean {
onImageDecodeError() onImageDecodeError()
return false return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
} }
onImageDecoded()
return false override fun onResourceReady(
} resource: Drawable?,
}) model: Any?,
.into(this) target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
}
onImageDecoded()
return false
}
})
.into(this)
} }
} }

View File

@ -137,13 +137,13 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
} }
animate() animate()
.apply { .apply {
newX?.let { x(it) } newX?.let { x(it) }
newY?.let { y(it) } newY?.let { y(it) }
} }
.setInterpolator(DecelerateInterpolator()) .setInterpolator(DecelerateInterpolator())
.setDuration(400) .setDuration(400)
.start() .start()
return true return true
} }

View File

@ -143,17 +143,18 @@ class WebtoonTransitionHolder(
unsubscribeStatus() unsubscribeStatus()
statusSubscription = chapter.stateObserver statusSubscription = chapter.stateObserver
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { state -> .subscribe { state ->
pagesContainer.removeAllViews() pagesContainer.removeAllViews()
when (state) { when (state) {
is ReaderChapter.State.Wait -> {} is ReaderChapter.State.Wait -> {
is ReaderChapter.State.Loading -> setLoading() }
is ReaderChapter.State.Error -> setError(state.error, transition) is ReaderChapter.State.Loading -> setLoading()
is ReaderChapter.State.Loaded -> setLoaded() is ReaderChapter.State.Error -> setError(state.error, transition)
is ReaderChapter.State.Loaded -> setLoaded()
}
pagesContainer.visibleIf { pagesContainer.childCount > 0 }
} }
pagesContainer.visibleIf { pagesContainer.childCount > 0 }
}
addSubscription(statusSubscription) addSubscription(statusSubscription)
} }

View File

@ -95,7 +95,7 @@ class WebtoonViewer(val activity: ReaderActivity) : BaseViewer {
else -> activity.toggleMenu() else -> activity.toggleMenu()
} }
} }
recycler.longTapListener = f@ { event -> recycler.longTapListener = f@{ event ->
if (activity.menuVisible || config.longTapEnabled) { if (activity.menuVisible || config.longTapEnabled) {
val child = recycler.findChildViewUnder(event.x, event.y) val child = recycler.findChildViewUnder(event.x, event.y)
if (child != null) { if (child != null) {

View File

@ -17,9 +17,12 @@ class RecentChapterItem(val chapter: Chapter, val manga: Manga, header: DateItem
var status: Int var status: Int
get() = download?.status ?: _status get() = download?.status ?: _status
set(value) { _status = value } set(value) {
_status = value
}
@Transient var download: Download? = null @Transient
var download: Download? = null
val isDownloaded: Boolean val isDownloaded: Boolean
get() = status == Download.DOWNLOADED get() = status == Download.DOWNLOADED
@ -29,7 +32,7 @@ class RecentChapterItem(val chapter: Chapter, val manga: Manga, header: DateItem
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): RecentChapterHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): RecentChapterHolder {
return RecentChapterHolder(view , adapter as RecentChaptersAdapter) return RecentChapterHolder(view, adapter as RecentChaptersAdapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,

View File

@ -38,8 +38,8 @@ class RecentChaptersPresenter(
.subscribeLatestCache(RecentChaptersController::onNextRecentChapters) .subscribeLatestCache(RecentChaptersController::onNextRecentChapters)
getChapterStatusObservable() getChapterStatusObservable()
.subscribeLatestCache(RecentChaptersController::onChapterStatusChange) { .subscribeLatestCache(RecentChaptersController::onChapterStatusChange) { _, error ->
_, error -> Timber.e(error) Timber.e(error)
} }
} }

View File

@ -18,7 +18,7 @@ import java.text.DecimalFormatSymbols
* @constructor creates an instance of the adapter. * @constructor creates an instance of the adapter.
*/ */
class RecentlyReadAdapter(controller: RecentlyReadController) class RecentlyReadAdapter(controller: RecentlyReadController)
: FlexibleAdapter<RecentlyReadItem>(null, controller, true) { : FlexibleAdapter<RecentlyReadItem>(null, controller, true) {
val sourceManager by injectLazy<SourceManager>() val sourceManager by injectLazy<SourceManager>()

View File

@ -99,8 +99,9 @@ class RecentlyReadPresenter : BasePresenter<RecentlyReadController>() {
((currChapterIndex + 1) until chapters.size) ((currChapterIndex + 1) until chapters.size)
.map { chapters[it] } .map { chapters[it] }
.firstOrNull { it.chapter_number > chapterNumber && .firstOrNull {
it.chapter_number <= chapterNumber + 1 it.chapter_number > chapterNumber &&
it.chapter_number <= chapterNumber + 1
} }
} }
else -> throw NotImplementedError("Unknown sorting method") else -> throw NotImplementedError("Unknown sorting method")

View File

@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.DialogCheckboxView import eu.kanade.tachiyomi.widget.DialogCheckboxView
class RemoveHistoryDialog<T>(bundle: Bundle? = null) : DialogController(bundle) class RemoveHistoryDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
where T : Controller, T: RemoveHistoryDialog.Listener { where T : Controller, T : RemoveHistoryDialog.Listener {
private var manga: Manga? = null private var manga: Manga? = null

View File

@ -109,10 +109,10 @@ class SettingsBackupController : SettingsController() {
onClick { onClick {
val currentDir = preferences.backupsDirectory().getOrDefault() val currentDir = preferences.backupsDirectory().getOrDefault()
try{ try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, CODE_BACKUP_DIR) startActivityForResult(intent, CODE_BACKUP_DIR)
} catch (e: ActivityNotFoundException){ } catch (e: ActivityNotFoundException) {
// Fall back to custom picker on error // Fall back to custom picker on error
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR) startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR)
} }

View File

@ -111,7 +111,7 @@ class SettingsLibraryController : SettingsController() {
key = Keys.libraryUpdateCategories key = Keys.libraryUpdateCategories
titleRes = R.string.pref_library_update_categories titleRes = R.string.pref_library_update_categories
entries = categories.map { it.name }.toTypedArray() entries = categories.map { it.name }.toTypedArray()
entryValues = categories.map { it.id.toString() }.toTypedArray() entryValues = categories.map { it.id.toString() }.toTypedArray()
preferences.libraryUpdateCategories().asObservable() preferences.libraryUpdateCategories().asObservable()
.subscribeUntilDestroy { .subscribeUntilDestroy {
val selectedCategories = it val selectedCategories = it
@ -171,7 +171,8 @@ class SettingsLibraryController : SettingsController() {
defaultValue = "-1" defaultValue = "-1"
val selectedCategory = categories.find { it.id == preferences.defaultCategory() } val selectedCategory = categories.find { it.id == preferences.defaultCategory() }
summary = selectedCategory?.name ?: context.getString(R.string.default_category_summary) summary = selectedCategory?.name
?: context.getString(R.string.default_category_summary)
onChange { newValue -> onChange { newValue ->
summary = categories.find { summary = categories.find {
it.id == (newValue as String).toInt() it.id == (newValue as String).toInt()

View File

@ -53,7 +53,8 @@ class WebViewActivity : BaseActivity() {
} }
if (bundle == null) { if (bundle == null) {
val source = sourceManager.get(intent.extras!!.getLong(SOURCE_KEY)) as? HttpSource ?: return val source = sourceManager.get(intent.extras!!.getLong(SOURCE_KEY)) as? HttpSource
?: return
val url = intent.extras!!.getString(URL_KEY) ?: return val url = intent.extras!!.getString(URL_KEY) ?: return
val headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" } val headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }

View File

@ -65,14 +65,14 @@ fun initDialog(dialogPreference: DialogPreference) {
inline fun <P : Preference> PreferenceGroup.initThenAdd(p: P, block: P.() -> Unit): P { inline fun <P : Preference> PreferenceGroup.initThenAdd(p: P, block: P.() -> Unit): P {
return p.apply { return p.apply {
block() block()
this.isIconSpaceReserved = false this.isIconSpaceReserved = false
addPreference(this) addPreference(this)
} }
} }
inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P { inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P {
return p.apply { return p.apply {
this.isIconSpaceReserved = false this.isIconSpaceReserved = false
addPreference(this) addPreference(this)
block() block()
} }
@ -88,28 +88,42 @@ inline fun Preference.onChange(crossinline block: (Any?) -> Boolean) {
var Preference.defaultValue: Any? var Preference.defaultValue: Any?
get() = null // set only get() = null // set only
set(value) { setDefaultValue(value) } set(value) {
setDefaultValue(value)
}
var Preference.titleRes: Int var Preference.titleRes: Int
get() = 0 // set only get() = 0 // set only
set(value) { setTitle(value) } set(value) {
setTitle(value)
}
var Preference.iconRes: Int var Preference.iconRes: Int
get() = 0 // set only get() = 0 // set only
set(value) { icon = VectorDrawableCompat.create(context.resources, value, context.theme) } set(value) {
icon = VectorDrawableCompat.create(context.resources, value, context.theme)
}
var Preference.summaryRes: Int var Preference.summaryRes: Int
get() = 0 // set only get() = 0 // set only
set(value) { setSummary(value) } set(value) {
setSummary(value)
}
var Preference.iconTint: Int var Preference.iconTint: Int
get() = 0 // set only get() = 0 // set only
set(value) { DrawableCompat.setTint(icon, value) } set(value) {
DrawableCompat.setTint(icon, value)
}
var ListPreference.entriesRes: Array<Int> var ListPreference.entriesRes: Array<Int>
get() = emptyArray() // set only get() = emptyArray() // set only
set(value) { entries = value.map { context.getString(it) }.toTypedArray() } set(value) {
entries = value.map { context.getString(it) }.toTypedArray()
}
var MultiSelectListPreference.entriesRes: Array<Int> var MultiSelectListPreference.entriesRes: Array<Int>
get() = emptyArray() // set only get() = emptyArray() // set only
set(value) { entries = value.map { context.getString(it) }.toTypedArray() } set(value) {
entries = value.map { context.getString(it) }.toTypedArray()
}

View File

@ -79,8 +79,8 @@ class EpubFile(file: File) : Closeable {
*/ */
private fun getPagesFromDocument(document: Document): List<String> { private fun getPagesFromDocument(document: Document): List<String> {
val pages = document.select("manifest > item") val pages = document.select("manifest > item")
.filter { "application/xhtml+xml" == it.attr("media-type") } .filter { "application/xhtml+xml" == it.attr("media-type") }
.associateBy { it.attr("id") } .associateBy { it.attr("id") }
val spine = document.select("spine > itemref").map { it.attr("idref") } val spine = document.select("spine > itemref").map { it.attr("idref") }
return spine.mapNotNull { pages[it] }.map { it.attr("href") } return spine.mapNotNull { pages[it] }.map { it.attr("href") }

View File

@ -90,8 +90,7 @@ fun Context.getFilePicker(currentDir: String): Intent {
* @param permission the permission to check. * @param permission the permission to check.
* @return true if it has permissions. * @return true if it has permissions.
*/ */
fun Context.hasPermission(permission: String) fun Context.hasPermission(permission: String) = ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
= ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
/** /**
* Returns the color for the given attribute. * Returns the color for the given attribute.

View File

@ -44,7 +44,7 @@ object ImageUtil {
if (bytes.compareWith("RIFF".toByteArray())) { if (bytes.compareWith("RIFF".toByteArray())) {
return ImageType.WEBP return ImageType.WEBP
} }
} catch(e: Exception) { } catch (e: Exception) {
} }
return null return null
} }

Some files were not shown because too many files have changed in this diff Show More