Run default Android Studio formatter on code
This commit is contained in:
parent
a1fadce7c6
commit
3ecc883944
109 changed files with 640 additions and 585 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() })
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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?) {}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>>,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>()
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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) ?: "" }
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
@ -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") }
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
Reference in a new issue