androidx migration

I DID THIS ONE MYSELF WITHOUT TAKING IT FROM THE OTHER FORKS
YEEEEEEEEEEET
This commit is contained in:
Rani Sargees 2020-01-06 03:26:31 -05:00
parent 53402459f2
commit 9b883b1a09
243 changed files with 4537 additions and 4604 deletions

View File

@ -40,7 +40,7 @@ android {
applicationId "eu.kanade.tachiyomi.az" applicationId "eu.kanade.tachiyomi.az"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 8405 versionCode 8405
versionName "v8.4.5-AZ" versionName "v8.4.5-AZ"
@ -134,22 +134,21 @@ dependencies {
implementation 'com.github.inorichi:junrar-android:634c1f5' implementation 'com.github.inorichi:junrar-android:634c1f5'
// Android support library // Android support library
final support_library_version = '28.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "com.android.support:support-v4:$support_library_version" implementation 'androidx.appcompat:appcompat:1.1.0'
implementation "com.android.support:appcompat-v7:$support_library_version" implementation 'androidx.cardview:cardview:1.0.0'
implementation "com.android.support:cardview-v7:$support_library_version" implementation 'com.google.android.material:material:1.0.0'
implementation "com.android.support:design:$support_library_version" implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "com.android.support:recyclerview-v7:$support_library_version" implementation 'androidx.preference:preference:1.1.0'
implementation "com.android.support:preference-v7:$support_library_version" implementation 'androidx.annotation:annotation:1.1.0'
implementation "com.android.support:support-annotations:$support_library_version" implementation 'androidx.browser:browser:1.2.0'
implementation "com.android.support:customtabs:$support_library_version"
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.android.support:multidex:1.0.3' implementation 'androidx.multidex:multidex:2.0.1'
// DO NOT UPGRADE TO 17.0, IT REQUIRES ANDROIDX // DO NOT UPGRADE TO 17.0, IT REQUIRES ANDROIDX
standardImplementation 'com.google.firebase:firebase-core:16.0.9' standardImplementation 'com.google.firebase:firebase-core:17.2.1'
// ReactiveX // ReactiveX
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'
@ -159,11 +158,11 @@ dependencies {
implementation 'com.github.pwittchen:reactivenetwork:0.13.0' implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
// Network client // Network client
implementation "com.squareup.okhttp3:okhttp:3.12.3" // DO NOT UPGRADE TO 3.13.X+, it requires minSdk 21 implementation "com.squareup.okhttp3:okhttp:4.2.1" // DO NOT UPGRADE TO 3.13.X+, it requires minSdk 21
implementation 'com.squareup.okio:okio:1.17.4' // TODO I think we can do 2.x, okhttp is ok with it but is there any other deps that need 1.x? implementation 'com.squareup.okio:okio:2.4.0' // I think we can do 2.x, okhttp is ok with it but is there any other deps that need 1.x?
// REST // REST
final retrofit_version = '2.6.1' final retrofit_version = '2.6.2'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version" implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
@ -185,16 +184,16 @@ dependencies {
// Job scheduling // Job scheduling
implementation 'com.evernote:android-job:1.2.5' implementation 'com.evernote:android-job:1.2.5'
// DO NOT UPGRADE TO 17.0, IT REQUIRES ANDROIDX // DO NOT UPGRADE TO 17.0, IT REQUIRES ANDROIDX
implementation 'com.google.android.gms:play-services-gcm:16.1.0' implementation 'com.google.android.gms:play-services-gcm:17.0.0'
// [EXH] Android 7 SSL Workaround // [EXH] Android 7 SSL Workaround
implementation 'com.google.android.gms:play-services-safetynet:16.0.0' implementation 'com.google.android.gms:play-services-safetynet:17.0.0'
// Changelog // Changelog
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0' implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
// Database // Database
implementation 'android.arch.persistence:db:1.1.1' implementation 'androidx.sqlite:sqlite:2.0.1'
implementation 'com.github.inorichi.storio:storio-common:8be19de@aar' implementation 'com.github.inorichi.storio:storio-common:8be19de@aar'
implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar' implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar'
implementation 'io.requery:sqlite-android:3.25.2' implementation 'io.requery:sqlite-android:3.25.2'
@ -208,13 +207,13 @@ dependencies {
implementation "com.github.inorichi.injekt:injekt-core:65b0440" implementation "com.github.inorichi.injekt:injekt-core:65b0440"
// Image library // Image library
final glide_version = '4.9.0' final glide_version = '4.10.0'
implementation "com.github.bumptech.glide:glide:$glide_version" implementation "com.github.bumptech.glide:glide:$glide_version"
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version" implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version"
// Transformations // Transformations
implementation 'jp.wasabeef:glide-transformations:3.1.1' implementation 'jp.wasabeef:glide-transformations:4.0.0'
// Logging // Logging
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
@ -225,24 +224,24 @@ dependencies {
// UI // UI
implementation 'com.dmitrymalkovich.android:material-design-dimens:1.4' implementation 'com.dmitrymalkovich.android:material-design-dimens:1.4'
implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4' implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
implementation 'eu.davidea:flexible-adapter:5.0.6' // Cannot upgrade to 5.1.0 as it uses AndroidX implementation 'eu.davidea:flexible-adapter:5.1.0' // Cannot upgrade to 5.1.0 as it uses AndroidX
implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b5' implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
implementation 'com.nononsenseapps:filepicker:2.5.2' implementation 'com.nononsenseapps:filepicker:2.5.2'
implementation 'com.github.amulyakhare:TextDrawable:558677e' implementation 'com.github.amulyakhare:TextDrawable:558677e'
implementation 'com.afollestad.material-dialogs:core:0.9.6.0' // Cannot upgrade to 2.x, AndroidX and API changes implementation 'com.afollestad.material-dialogs:core:0.9.6.0' // Cannot upgrade to 2.x, AndroidX and API changes
implementation 'me.zhanghai.android.systemuihelper:library:1.0.0' implementation 'me.zhanghai.android.systemuihelper:library:1.0.0'
implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.0.4' implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0'
implementation 'com.github.mthli:Slice:v1.3' implementation 'com.github.mthli:Slice:v1.3'
implementation 'me.gujun.android.taggroup:library:1.4@aar' implementation 'me.gujun.android.taggroup:library:1.4@aar'
implementation 'com.github.chrisbanes:PhotoView:2.1.4' // Cannot upgrade to 2.2.x+ as it uses AndroidX implementation 'com.github.chrisbanes:PhotoView:2.3.0' // Cannot upgrade to 2.2.x+ as it uses AndroidX
implementation 'com.github.inorichi:DirectionalViewPager:3acc51a' implementation 'com.github.carlosesco:DirectionalViewPager:a844dbca0a'
// Conductor // Conductor
implementation 'com.bluelinelabs:conductor:2.1.5' implementation 'com.bluelinelabs:conductor:2.1.5'
implementation("com.bluelinelabs:conductor-support:2.1.5") { implementation("com.bluelinelabs:conductor-support:2.1.5") {
exclude group: "com.android.support" exclude group: "com.android.support"
} }
implementation 'com.github.inorichi:conductor-support-preference:27.0.2' implementation 'com.github.inorichi:conductor-support-preference:78e2344'
// RxBindings // RxBindings
final rxbindings_version = '1.0.1' final rxbindings_version = '1.0.1'
@ -287,7 +286,7 @@ dependencies {
implementation 'com.lvla.android:rxjava2-interop-kt:0.2.1' implementation 'com.lvla.android:rxjava2-interop-kt:0.2.1'
// Debug network interceptor (EH) // Debug network interceptor (EH)
implementation "com.squareup.okhttp3:logging-interceptor:3.12.1" implementation "com.squareup.okhttp3:logging-interceptor:4.2.1"
// Firebase (EH) // Firebase (EH)
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
@ -309,7 +308,7 @@ dependencies {
// Humanize (EH) // Humanize (EH)
implementation 'com.github.mfornos:humanize-slim:1.2.2' implementation 'com.github.mfornos:humanize-slim:1.2.2'
implementation 'com.android.support:gridlayout-v7:28.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
final def markwon_version = '4.1.0' final def markwon_version = '4.1.0'
@ -319,6 +318,8 @@ dependencies {
implementation "io.noties.markwon:html:$markwon_version" implementation "io.noties.markwon:html:$markwon_version"
implementation "io.noties.markwon:image:$markwon_version" implementation "io.noties.markwon:image:$markwon_version"
implementation "io.noties.markwon:linkify:$markwon_version" implementation "io.noties.markwon:linkify:$markwon_version"
implementation 'com.google.guava:guava:27.0.1-android'
} }
buildscript { buildscript {

View File

@ -111,7 +111,7 @@
android:theme="@android:style/Theme.Translucent.NoTitleBar" /> android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">

View File

@ -6,7 +6,7 @@ import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.support.multidex.MultiDex import androidx.multidex.MultiDex
import com.elvishew.xlog.LogConfiguration import com.elvishew.xlog.LogConfiguration
import com.elvishew.xlog.LogLevel import com.elvishew.xlog.LogLevel
import com.elvishew.xlog.XLog import com.elvishew.xlog.XLog

View File

@ -12,10 +12,9 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.saveTo import eu.kanade.tachiyomi.util.saveTo
import okhttp3.Response import okhttp3.Response
import okio.Okio import okio.buffer
import okio.sink
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -147,7 +146,7 @@ class ChapterCache(private val context: Context) {
editor = diskCache.edit(key) ?: return editor = diskCache.edit(key) ?: return
// Write chapter urls to cache. // Write chapter urls to cache.
Okio.buffer(Okio.sink(editor.newOutputStream(0))).use { editor.newOutputStream(0).sink().buffer().use {
it.write(cachedValue.toByteArray()) it.write(cachedValue.toByteArray())
it.flush() it.flush()
} }
@ -207,12 +206,12 @@ class ChapterCache(private val context: Context) {
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key") editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
// Get OutputStream and write image with Okio. // Get OutputStream and write image with Okio.
response.body()!!.source().saveTo(editor.newOutputStream(0)) response.body!!.source().saveTo(editor.newOutputStream(0))
diskCache.flush() diskCache.flush()
editor.commit() editor.commit()
} finally { } finally {
response.body()?.close() response.body?.close()
editor?.abortUnlessCommitted() editor?.abortUnlessCommitted()
} }
} }

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.database package eu.kanade.tachiyomi.data.database
import android.arch.persistence.db.SupportSQLiteOpenHelper
import android.content.Context import android.content.Context
import androidx.sqlite.db.SupportSQLiteOpenHelper
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
import eu.kanade.tachiyomi.data.database.mappers.* import eu.kanade.tachiyomi.data.database.mappers.*
import eu.kanade.tachiyomi.data.database.models.* import eu.kanade.tachiyomi.data.database.models.*

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.database package eu.kanade.tachiyomi.data.database
import android.arch.persistence.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import android.arch.persistence.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteOpenHelper
import eu.kanade.tachiyomi.data.database.tables.* import eu.kanade.tachiyomi.data.database.tables.*
import exh.metadata.sql.tables.SearchMetadataTable import exh.metadata.sql.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable import exh.metadata.sql.tables.SearchTagTable

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.database.resolvers package eu.kanade.tachiyomi.data.database.resolvers
import android.content.ContentValues import android.content.ContentValues
import android.support.annotation.NonNull import androidx.annotation.NonNull
import com.pushtorefresh.storio.sqlite.StorIOSQLite import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResult import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.Query import com.pushtorefresh.storio.sqlite.queries.Query

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context import android.content.Context
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.support.v4.app.NotificationCompat import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.download.model.DownloadQueue

View File

@ -9,7 +9,7 @@ import android.net.NetworkInfo.State.DISCONNECTED
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import android.support.v4.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.github.pwittchen.reactivenetwork.library.Connectivity import com.github.pwittchen.reactivenetwork.library.Connectivity
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay

View File

@ -362,7 +362,7 @@ class Downloader(
.map { response -> .map { response ->
val file = tmpDir.createFile("$filename.tmp") val file = tmpDir.createFile("$filename.tmp")
try { try {
response.body()!!.source().saveTo(file.openOutputStream()) response.body!!.source().saveTo(file.openOutputStream())
val extension = getImageExtension(response, file) val extension = getImageExtension(response, file)
file.renameTo("$filename.$extension") file.renameTo("$filename.$extension")
} catch (e: Exception) { } catch (e: Exception) {
@ -394,7 +394,7 @@ 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.

View File

@ -6,7 +6,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Build import android.os.Build
import android.support.v4.app.NotificationCompat import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications

View File

@ -6,7 +6,7 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import android.support.v4.app.NotificationCompat import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
@ -25,7 +25,9 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.util.isServiceRunning
import eu.kanade.tachiyomi.util.notificationManager
import eu.kanade.tachiyomi.util.syncChaptersWithSource
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.data.preference package eu.kanade.tachiyomi.data.preference
import android.support.v7.preference.PreferenceDataStore import androidx.preference.PreferenceDataStore
class EmptyPreferenceDataStore : PreferenceDataStore() { class EmptyPreferenceDataStore : PreferenceDataStore() {

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.preference package eu.kanade.tachiyomi.data.preference
import android.content.SharedPreferences import android.content.SharedPreferences
import android.support.v7.preference.PreferenceDataStore import androidx.preference.PreferenceDataStore
class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() { class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() {

View File

@ -1,70 +1,70 @@
package eu.kanade.tachiyomi.data.track package eu.kanade.tachiyomi.data.track
import android.support.annotation.CallSuper import androidx.annotation.CallSuper
import android.support.annotation.DrawableRes import androidx.annotation.DrawableRes
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import rx.Completable import rx.Completable
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
abstract class TrackService(val id: Int) { abstract class TrackService(val id: Int) {
val preferences: PreferencesHelper by injectLazy() val preferences: PreferencesHelper by injectLazy()
val networkService: NetworkHelper by injectLazy() val networkService: NetworkHelper by injectLazy()
open val client: OkHttpClient open val client: OkHttpClient
get() = networkService.client get() = networkService.client
// Name of the manga sync service to display // Name of the manga sync service to display
abstract val name: String abstract val name: String
@DrawableRes @DrawableRes
abstract fun getLogo(): Int abstract fun getLogo(): Int
abstract fun getLogoColor(): Int abstract fun getLogoColor(): Int
abstract fun getStatusList(): List<Int> abstract fun getStatusList(): List<Int>
abstract fun getStatus(status: Int): String abstract fun getStatus(status: Int): String
abstract fun getScoreList(): List<String> abstract fun getScoreList(): List<String>
open fun indexToScore(index: Int): Float { open fun indexToScore(index: Int): Float {
return index.toFloat() return index.toFloat()
} }
abstract fun displayScore(track: Track): String abstract fun displayScore(track: Track): String
abstract fun add(track: Track): Observable<Track> abstract fun add(track: Track): Observable<Track>
abstract fun update(track: Track): Observable<Track> abstract fun update(track: Track): Observable<Track>
abstract fun bind(track: Track): Observable<Track> abstract fun bind(track: Track): Observable<Track>
abstract fun search(query: String): Observable<List<TrackSearch>> abstract fun search(query: String): Observable<List<TrackSearch>>
abstract fun refresh(track: Track): Observable<Track> abstract fun refresh(track: Track): Observable<Track>
abstract fun login(username: String, password: String): Completable abstract fun login(username: String, password: String): Completable
@CallSuper @CallSuper
open fun logout() { open fun logout() {
preferences.setTrackCredentials(this, "", "") preferences.setTrackCredentials(this, "", "")
} }
open val isLogged: Boolean open val isLogged: Boolean
get() = !getUsername().isEmpty() && get() = !getUsername().isEmpty() &&
!getPassword().isEmpty() !getPassword().isEmpty()
fun getUsername() = preferences.trackUsername(this)!! fun getUsername() = preferences.trackUsername(this)!!
fun getPassword() = preferences.trackPassword(this)!! fun getPassword() = preferences.trackPassword(this)!!
fun saveCredentials(username: String, password: String) { fun saveCredentials(username: String, password: String) {
preferences.setTrackCredentials(this, username, password) preferences.setTrackCredentials(this, username, password)
} }
} }

View File

@ -1,286 +1,286 @@
package eu.kanade.tachiyomi.data.track.anilist package eu.kanade.tachiyomi.data.track.anilist
import android.net.Uri import android.net.Uri
import com.github.salomonbrys.kotson.* import com.github.salomonbrys.kotson.*
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import rx.Observable import rx.Observable
import java.util.Calendar import java.util.*
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val parser = JsonParser() private val parser = JsonParser()
private val jsonMime = MediaType.parse("application/json; charset=utf-8") private val jsonMime = "application/json; charset=utf-8".toMediaTypeOrNull()
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
fun addLibManga(track: Track): Observable<Track> { fun addLibManga(track: Track): Observable<Track> {
val query = """ val query = """
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) { |mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) { |SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id | id
| status | status
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val variables = jsonObject( val variables = jsonObject(
"mangaId" to track.media_id, "mangaId" to track.media_id,
"progress" to track.last_chapter_read, "progress" to track.last_chapter_read,
"status" to track.toAnilistStatus() "status" to track.toAnilistStatus()
) )
val payload = jsonObject( val payload = jsonObject(
"query" to query, "query" to query,
"variables" to variables "variables" to variables
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = RequestBody.create(jsonMime, payload.toString())
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
.build() .build()
return authClient.newCall(request) return authClient.newCall(request)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
netResponse.close() netResponse.close()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }
val response = parser.parse(responseBody).obj val response = parser.parse(responseBody).obj
track.library_id = response["data"]["SaveMediaListEntry"]["id"].asLong track.library_id = response["data"]["SaveMediaListEntry"]["id"].asLong
track track
} }
} }
fun updateLibManga(track: Track): Observable<Track> { fun updateLibManga(track: Track): Observable<Track> {
val query = """ val query = """
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) { |mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) { |SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|id |id
|status |status
|progress |progress
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val variables = jsonObject( val variables = jsonObject(
"listId" to track.library_id, "listId" to track.library_id,
"progress" to track.last_chapter_read, "progress" to track.last_chapter_read,
"status" to track.toAnilistStatus(), "status" to track.toAnilistStatus(),
"score" to track.score.toInt() "score" to track.score.toInt()
) )
val payload = jsonObject( val payload = jsonObject(
"query" to query, "query" to query,
"variables" to variables "variables" to variables
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = RequestBody.create(jsonMime, payload.toString())
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
.build() .build()
return authClient.newCall(request) return authClient.newCall(request)
.asObservableSuccess() .asObservableSuccess()
.map { .map {
track track
} }
} }
fun search(search: String): Observable<List<TrackSearch>> { fun search(search: String): Observable<List<TrackSearch>> {
val query = """ val query = """
|query Search(${'$'}query: String) { |query Search(${'$'}query: String) {
|Page (perPage: 50) { |Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) { |media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|id |id
|title { |title {
|romaji |romaji
|} |}
|coverImage { |coverImage {
|large |large
|} |}
|type |type
|status |status
|chapters |chapters
|description |description
|startDate { |startDate {
|year |year
|month |month
|day |day
|} |}
|} |}
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val variables = jsonObject( val variables = jsonObject(
"query" to search "query" to search
) )
val payload = jsonObject( val payload = jsonObject(
"query" to query, "query" to query,
"variables" to variables "variables" to variables
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = RequestBody.create(jsonMime, payload.toString())
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
.build() .build()
return authClient.newCall(request) return authClient.newCall(request)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }
val response = parser.parse(responseBody).obj val response = parser.parse(responseBody).obj
val data = response["data"]!!.obj val data = response["data"]!!.obj
val page = data["Page"].obj val page = data["Page"].obj
val media = page["media"].array val media = page["media"].array
val entries = media.map { jsonToALManga(it.obj) } val entries = media.map { jsonToALManga(it.obj) }
entries.map { it.toTrack() } entries.map { it.toTrack() }
} }
} }
fun findLibManga(track: Track, userid: Int): Observable<Track?> { fun findLibManga(track: Track, userid: Int): Observable<Track?> {
val query = """ val query = """
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) { |query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page { |Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) { |mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|id |id
|status |status
|scoreRaw: score(format: POINT_100) |scoreRaw: score(format: POINT_100)
|progress |progress
|media { |media {
|id |id
|title { |title {
|romaji |romaji
|} |}
|coverImage { |coverImage {
|large |large
|} |}
|type |type
|status |status
|chapters |chapters
|description |description
|startDate { |startDate {
|year |year
|month |month
|day |day
|} |}
|} |}
|} |}
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val variables = jsonObject( val variables = jsonObject(
"id" to userid, "id" to userid,
"manga_id" to track.media_id "manga_id" to track.media_id
) )
val payload = jsonObject( val payload = jsonObject(
"query" to query, "query" to query,
"variables" to variables "variables" to variables
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = RequestBody.create(jsonMime, payload.toString())
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
.build() .build()
return authClient.newCall(request) return authClient.newCall(request)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }
val response = parser.parse(responseBody).obj val response = parser.parse(responseBody).obj
val data = response["data"]!!.obj val data = response["data"]!!.obj
val page = data["Page"].obj val page = data["Page"].obj
val media = page["mediaList"].array val media = page["mediaList"].array
val entries = media.map { jsonToALUserManga(it.obj) } val entries = media.map { jsonToALUserManga(it.obj) }
entries.firstOrNull()?.toTrack() entries.firstOrNull()?.toTrack()
} }
} }
fun getLibManga(track: Track, userid: Int): Observable<Track> { fun getLibManga(track: Track, userid: Int): Observable<Track> {
return findLibManga(track, userid) return findLibManga(track, userid)
.map { it ?: throw Exception("Could not find manga") } .map { it ?: throw Exception("Could not find manga") }
} }
fun createOAuth(token: String): OAuth { fun createOAuth(token: String): OAuth {
return OAuth(token, "Bearer", System.currentTimeMillis() + 31536000000, 31536000000) return OAuth(token, "Bearer", System.currentTimeMillis() + 31536000000, 31536000000)
} }
fun getCurrentUser(): Observable<Pair<Int, String>> { fun getCurrentUser(): Observable<Pair<Int, String>> {
val query = """ val query = """
|query User { |query User {
|Viewer { |Viewer {
|id |id
|mediaListOptions { |mediaListOptions {
|scoreFormat |scoreFormat
|} |}
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val payload = jsonObject( val payload = jsonObject(
"query" to query "query" to query
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = RequestBody.create(jsonMime, payload.toString())
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
.build() .build()
return authClient.newCall(request) return authClient.newCall(request)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }
val response = parser.parse(responseBody).obj val response = parser.parse(responseBody).obj
val data = response["data"]!!.obj val data = response["data"]!!.obj
val viewer = data["Viewer"].obj val viewer = data["Viewer"].obj
Pair(viewer["id"].asInt, viewer["mediaListOptions"]["scoreFormat"].asString) Pair(viewer["id"].asInt, viewer["mediaListOptions"]["scoreFormat"].asString)
} }
} }
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) {
0L 0L
} }
return ALManga(struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString, return ALManga(struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString,
struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].asString, struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].asString,
date, struct["chapters"].nullInt ?: 0) date, struct["chapters"].nullInt ?: 0)
} }
private fun jsonToALUserManga(struct: JsonObject): ALUserManga { private fun jsonToALUserManga(struct: JsonObject): ALUserManga {
return ALUserManga(struct["id"].asLong, struct["status"].asString, struct["scoreRaw"].asInt, struct["progress"].asInt, jsonToALManga(struct["media"].obj)) return ALUserManga(struct["id"].asLong, struct["status"].asString, struct["scoreRaw"].asInt, struct["progress"].asInt, jsonToALManga(struct["media"].obj))
} }
companion object { companion object {
private const val clientId = "385" private const val clientId = "385"
private const val clientUrl = "tachiyomi://anilist-auth" private const val clientUrl = "tachiyomi://anilist-auth"
private const val apiUrl = "https://graphql.anilist.co/" private const val apiUrl = "https://graphql.anilist.co/"
private const val baseUrl = "https://anilist.co/api/v2/" private const val baseUrl = "https://anilist.co/api/v2/"
private const val baseMangaUrl = "https://anilist.co/manga/" private const val baseMangaUrl = "https://anilist.co/manga/"
fun mangaUrl(mediaId: Int): String { fun mangaUrl(mediaId: Int): String {
return baseMangaUrl + mediaId return baseMangaUrl + mediaId
} }
fun authUrl() = Uri.parse("${baseUrl}oauth/authorize").buildUpon() fun authUrl() = Uri.parse("${baseUrl}oauth/authorize").buildUpon()
.appendQueryParameter("client_id", clientId) .appendQueryParameter("client_id", clientId)
.appendQueryParameter("response_type", "token") .appendQueryParameter("response_type", "token")
.build() .build()
} }
} }

View File

@ -84,7 +84,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
return authClient.newCall(request) return authClient.newCall(request)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }
@ -127,7 +127,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
// get comic info // get comic info
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
jsonToTrack(parser.parse(responseBody).obj) jsonToTrack(parser.parse(responseBody).obj)
} }
} }
@ -144,7 +144,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
return authClient.newCall(requestUserRead) return authClient.newCall(requestUserRead)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val resp = netResponse.body()?.string() val resp = netResponse.body?.string()
val coll = gson.fromJson(resp, Collection::class.java) val coll = gson.fromJson(resp, Collection::class.java)
track.status = coll.status?.id!! track.status = coll.status?.id!!
track.last_chapter_read = coll.ep_status!! track.last_chapter_read = coll.ep_status!!
@ -154,7 +154,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
fun accessToken(code: String): Observable<OAuth> { fun accessToken(code: String): Observable<OAuth> {
return client.newCall(accessTokenRequest(code)).asObservableSuccess().map { netResponse -> return client.newCall(accessTokenRequest(code)).asObservableSuccess().map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }

View File

@ -14,7 +14,7 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
fun addTocken(tocken: String, oidFormBody: FormBody): FormBody { fun addTocken(tocken: String, oidFormBody: FormBody): FormBody {
val newFormBody = FormBody.Builder() val newFormBody = FormBody.Builder()
for (i in 0 until oidFormBody.size()) { for (i in 0 until oidFormBody.size) {
newFormBody.add(oidFormBody.name(i), oidFormBody.value(i)) newFormBody.add(oidFormBody.name(i), oidFormBody.value(i))
} }
newFormBody.add("access_token", tocken) newFormBody.add("access_token", tocken)
@ -29,18 +29,18 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
if (currAuth.isExpired()) { if (currAuth.isExpired()) {
val response = chain.proceed(BangumiApi.refreshTokenRequest(currAuth.refresh_token!!)) val response = chain.proceed(BangumiApi.refreshTokenRequest(currAuth.refresh_token!!))
if (response.isSuccessful) { if (response.isSuccessful) {
newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java)) newAuth(gson.fromJson(response.body!!.string(), OAuth::class.java))
} else { } else {
response.close() response.close()
} }
} }
var authRequest = if (originalRequest.method() == "GET") originalRequest.newBuilder() var authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder()
.header("User-Agent", "Tachiyomi") .header("User-Agent", "Tachiyomi")
.url(originalRequest.url().newBuilder() .url(originalRequest.url.newBuilder()
.addQueryParameter("access_token", currAuth.access_token).build()) .addQueryParameter("access_token", currAuth.access_token).build())
.build() else originalRequest.newBuilder() .build() else originalRequest.newBuilder()
.post(addTocken(currAuth.access_token, originalRequest.body() as FormBody)) .post(addTocken(currAuth.access_token, originalRequest.body as FormBody))
.header("User-Agent", "Tachiyomi") .header("User-Agent", "Tachiyomi")
.build() .build()

View File

@ -22,7 +22,7 @@ class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor {
if (currAuth.isExpired()) { if (currAuth.isExpired()) {
val response = chain.proceed(KitsuApi.refreshTokenRequest(refreshToken)) val response = chain.proceed(KitsuApi.refreshTokenRequest(refreshToken))
if (response.isSuccessful) { if (response.isSuccessful) {
newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java)) newAuth(gson.fromJson(response.body!!.string(), OAuth::class.java))
} else { } else {
response.close() response.close()
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.data.track.kitsu package eu.kanade.tachiyomi.data.track.kitsu
import android.support.annotation.CallSuper import androidx.annotation.CallSuper
import com.github.salomonbrys.kotson.* import com.github.salomonbrys.kotson.*
import com.google.gson.JsonObject import com.google.gson.JsonObject
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track

View File

@ -1,164 +1,163 @@
package eu.kanade.tachiyomi.data.track.myanimelist package eu.kanade.tachiyomi.data.track.myanimelist
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import rx.Completable import rx.Completable
import rx.Observable import rx.Observable
import java.lang.Exception
class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
companion object {
companion object { const val READING = 1
const val READING = 1 const val COMPLETED = 2
const val COMPLETED = 2 const val ON_HOLD = 3
const val ON_HOLD = 3 const val DROPPED = 4
const val DROPPED = 4 const val PLAN_TO_READ = 6
const val PLAN_TO_READ = 6
const val DEFAULT_STATUS = READING
const val DEFAULT_STATUS = READING const val DEFAULT_SCORE = 0
const val DEFAULT_SCORE = 0
const val BASE_URL = "https://myanimelist.net"
const val BASE_URL = "https://myanimelist.net" const val USER_SESSION_COOKIE = "MALSESSIONID"
const val USER_SESSION_COOKIE = "MALSESSIONID" const val LOGGED_IN_COOKIE = "is_logged_in"
const val LOGGED_IN_COOKIE = "is_logged_in" }
}
private val interceptor by lazy { MyAnimeListInterceptor(this) }
private val interceptor by lazy { MyAnimeListInterceptor(this) } private val api by lazy { MyanimelistApi(client, interceptor) }
private val api by lazy { MyanimelistApi(client, interceptor) }
override val name: String
override val name: String get() = "MyAnimeList"
get() = "MyAnimeList"
override fun getLogo() = R.drawable.mal
override fun getLogo() = R.drawable.mal
override fun getLogoColor() = Color.rgb(46, 81, 162)
override fun getLogoColor() = Color.rgb(46, 81, 162)
override fun getStatus(status: Int): String = with(context) {
override fun getStatus(status: Int): String = with(context) { when (status) {
when (status) { READING -> getString(R.string.reading)
READING -> getString(R.string.reading) COMPLETED -> getString(R.string.completed)
COMPLETED -> getString(R.string.completed) ON_HOLD -> getString(R.string.on_hold)
ON_HOLD -> getString(R.string.on_hold) DROPPED -> getString(R.string.dropped)
DROPPED -> getString(R.string.dropped) PLAN_TO_READ -> getString(R.string.plan_to_read)
PLAN_TO_READ -> getString(R.string.plan_to_read) else -> ""
else -> "" }
} }
}
override fun getStatusList(): List<Int> {
override fun getStatusList(): List<Int> { return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) }
}
override fun getScoreList(): List<String> {
override fun getScoreList(): List<String> { return IntRange(0, 10).map(Int::toString)
return IntRange(0, 10).map(Int::toString) }
}
override fun displayScore(track: Track): String {
override fun displayScore(track: Track): String { return track.score.toInt().toString()
return track.score.toInt().toString() }
}
override fun add(track: Track): Observable<Track> {
override fun add(track: Track): Observable<Track> { return api.addLibManga(track)
return api.addLibManga(track) }
}
override fun update(track: Track): Observable<Track> {
override fun update(track: Track): Observable<Track> { if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { track.status = COMPLETED
track.status = COMPLETED }
}
return api.updateLibManga(track)
return api.updateLibManga(track) }
}
override fun bind(track: Track): Observable<Track> {
override fun bind(track: Track): Observable<Track> { return api.findLibManga(track)
return api.findLibManga(track) .flatMap { remoteTrack ->
.flatMap { remoteTrack -> if (remoteTrack != null) {
if (remoteTrack != null) { track.copyPersonalFrom(remoteTrack)
track.copyPersonalFrom(remoteTrack) update(track)
update(track) } else {
} else { // Set default fields if it's not found in the list
// Set default fields if it's not found in the list track.score = DEFAULT_SCORE.toFloat()
track.score = DEFAULT_SCORE.toFloat() track.status = DEFAULT_STATUS
track.status = DEFAULT_STATUS add(track)
add(track) }
} }
} }
}
override fun search(query: String): Observable<List<TrackSearch>> {
override fun search(query: String): Observable<List<TrackSearch>> { return api.search(query)
return api.search(query) }
}
override fun refresh(track: Track): Observable<Track> {
override fun refresh(track: Track): Observable<Track> { return api.getLibManga(track)
return api.getLibManga(track) .map { remoteTrack ->
.map { remoteTrack -> track.copyPersonalFrom(remoteTrack)
track.copyPersonalFrom(remoteTrack) track.total_chapters = remoteTrack.total_chapters
track.total_chapters = remoteTrack.total_chapters track
track }
} }
}
override fun login(username: String, password: String): Completable {
override fun login(username: String, password: String): Completable { logout()
logout()
return Observable.fromCallable { api.login(username, password) }
return Observable.fromCallable { api.login(username, password) } .doOnNext { csrf -> saveCSRF(csrf) }
.doOnNext { csrf -> saveCSRF(csrf) } .doOnNext { saveCredentials(username, password) }
.doOnNext { saveCredentials(username, password) } .doOnError { logout() }
.doOnError { logout() } .toCompletable()
.toCompletable() }
}
fun refreshLogin() {
fun refreshLogin() { val username = getUsername()
val username = getUsername() val password = getPassword()
val password = getPassword() logout()
logout()
try {
try { val csrf = api.login(username, password)
val csrf = api.login(username, password) saveCSRF(csrf)
saveCSRF(csrf) saveCredentials(username, password)
saveCredentials(username, password) } catch (e: Exception) {
} catch (e: Exception) { logout()
logout() throw e
throw e }
} }
}
// Attempt to login again if cookies have been cleared but credentials are still filled
// Attempt to login again if cookies have been cleared but credentials are still filled fun ensureLoggedIn() {
fun ensureLoggedIn() { if (isAuthorized) return
if (isAuthorized) return if (!isLogged) throw Exception("MAL Login Credentials not found")
if (!isLogged) throw Exception("MAL Login Credentials not found")
refreshLogin()
refreshLogin() }
}
override fun logout() {
override fun logout() { super.logout()
super.logout() preferences.trackToken(this).delete()
preferences.trackToken(this).delete() networkService.cookieManager.remove(BASE_URL.toHttpUrlOrNull()!!)
networkService.cookieManager.remove(HttpUrl.parse(BASE_URL)!!) }
}
val isAuthorized: Boolean
val isAuthorized: Boolean get() = super.isLogged &&
get() = super.isLogged && getCSRF().isNotEmpty() &&
getCSRF().isNotEmpty() && checkCookies()
checkCookies()
fun getCSRF(): String = preferences.trackToken(this).getOrDefault()
fun getCSRF(): String = preferences.trackToken(this).getOrDefault()
private fun saveCSRF(csrf: String) = preferences.trackToken(this).set(csrf)
private fun saveCSRF(csrf: String) = preferences.trackToken(this).set(csrf)
private fun checkCookies(): Boolean {
private fun checkCookies(): Boolean { var ckCount = 0
var ckCount = 0 val url = BASE_URL.toHttpUrlOrNull()!!
val url = HttpUrl.parse(BASE_URL)!! for (ck in networkService.cookieManager.get(url)) {
for (ck in networkService.cookieManager.get(url)) { if (ck.name == USER_SESSION_COOKIE || ck.name == LOGGED_IN_COOKIE)
if (ck.name() == USER_SESSION_COOKIE || ck.name() == LOGGED_IN_COOKIE) ckCount++
ckCount++ }
}
return ckCount == 2
return ckCount == 2 }
}
}
}

View File

@ -15,7 +15,7 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
val request = chain.request() val request = chain.request()
var response = chain.proceed(updateRequest(request)) var response = chain.proceed(updateRequest(request))
if (response.code() == 400){ if (response.code == 400) {
myanimelist.refreshLogin() myanimelist.refreshLogin()
response = chain.proceed(updateRequest(request)) response = chain.proceed(updateRequest(request))
} }
@ -24,7 +24,7 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
} }
private fun updateRequest(request: Request): Request { private fun updateRequest(request: Request): Request {
return request.body()?.let { return request.body?.let {
val contentType = it.contentType().toString() val contentType = it.contentType().toString()
val updatedBody = when { val updatedBody = when {
contentType.contains("x-www-form-urlencoded") -> updateFormBody(it) contentType.contains("x-www-form-urlencoded") -> updateFormBody(it)

View File

@ -10,7 +10,11 @@ import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.selectInt import eu.kanade.tachiyomi.util.selectInt
import eu.kanade.tachiyomi.util.selectText import eu.kanade.tachiyomi.util.selectText
import okhttp3.* import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.Response
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -85,7 +89,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.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) {
val trackForm = Jsoup.parse(it.consumeBody()) val trackForm = Jsoup.parse(it.consumeBody())
libTrack = Track.create(TrackManager.MYANIMELIST).apply { libTrack = Track.create(TrackManager.MYANIMELIST).apply {
@ -125,7 +129,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
val response = client.newCall(POST(url = loginUrl(), body = loginPostBody(username, password, csrf))).execute() val response = client.newCall(POST(url = loginUrl(), body = loginPostBody(username, password, csrf))).execute()
response.use { response.use {
if (response.priorResponse()?.code() != 302) throw Exception("Authentication error") if (response.priorResponse?.code != 302) throw Exception("Authentication error")
} }
} }
@ -172,15 +176,15 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
private fun Response.consumeBody(): String? { private fun Response.consumeBody(): String? {
use { use {
if (it.code() != 200) throw Exception("HTTP error ${it.code()}") if (it.code != 200) throw Exception("HTTP error ${it.code}")
return it.body()?.string() return it.body?.string()
} }
} }
private fun Response.consumeXmlBody(): String? { private fun Response.consumeXmlBody(): String? {
use { res -> use { res ->
if (res.code() != 200) throw Exception("Export list error") if (res.code != 200) throw Exception("Export list error")
BufferedReader(InputStreamReader(GZIPInputStream(res.body()?.source()?.inputStream()))).use { reader -> BufferedReader(InputStreamReader(GZIPInputStream(res.body?.source()?.inputStream()))).use { reader ->
val sb = StringBuilder() val sb = StringBuilder()
reader.forEachLine { line -> reader.forEachLine { line ->
sb.append(line) sb.append(line)
@ -262,7 +266,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.put("score", track.score) .put("score", track.score)
.put("num_read_chapters", track.last_chapter_read) .put("num_read_chapters", track.last_chapter_read)
return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body.toString()) return RequestBody.create("application/json; charset=utf-8".toMediaTypeOrNull(), body.toString())
} }
private fun Element.searchTitle() = select("strong").text()!! private fun Element.searchTitle() = select("strong").text()!!

View File

@ -14,7 +14,11 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import okhttp3.* import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -22,7 +26,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
private val gson: Gson by injectLazy() private val gson: Gson by injectLazy()
private val parser = JsonParser() private val parser = JsonParser()
private val jsonime = MediaType.parse("application/json; charset=utf-8") private val jsonime = "application/json; charset=utf-8".toMediaTypeOrNull()
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
fun addLibManga(track: Track, user_id: String): Observable<Track> { fun addLibManga(track: Track, user_id: String): Observable<Track> {
@ -63,7 +67,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
return authClient.newCall(request) return authClient.newCall(request)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }
@ -120,13 +124,13 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
return authClient.newCall(requestMangas) return authClient.newCall(requestMangas)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
parser.parse(responseBody).obj parser.parse(responseBody).obj
}.flatMap { mangas -> }.flatMap { mangas ->
authClient.newCall(request) authClient.newCall(request)
.asObservableSuccess() .asObservableSuccess()
.map { netResponse -> .map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }
@ -143,13 +147,13 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
} }
fun getCurrentUser(): Int { fun getCurrentUser(): Int {
val user = authClient.newCall(GET("$apiUrl/users/whoami")).execute().body()?.string() val user = authClient.newCall(GET("$apiUrl/users/whoami")).execute().body?.string()
return parser.parse(user).obj["id"].asInt return parser.parse(user).obj["id"].asInt
} }
fun accessToken(code: String): Observable<OAuth> { fun accessToken(code: String): Observable<OAuth> {
return client.newCall(accessTokenRequest(code)).asObservableSuccess().map { netResponse -> return client.newCall(accessTokenRequest(code)).asObservableSuccess().map { netResponse ->
val responseBody = netResponse.body()?.string().orEmpty() val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) { if (responseBody.isEmpty()) {
throw Exception("Null Response") throw Exception("Null Response")
} }

View File

@ -22,7 +22,7 @@ class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Intercept
if (currAuth.isExpired()) { if (currAuth.isExpired()) {
val response = chain.proceed(ShikimoriApi.refreshTokenRequest(refreshToken)) val response = chain.proceed(ShikimoriApi.refreshTokenRequest(refreshToken))
if (response.isSuccessful) { if (response.isSuccessful) {
newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java)) newAuth(gson.fromJson(response.body!!.string(), OAuth::class.java))
} else { } else {
response.close() response.close()
} }

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.support.v4.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.evernote.android.job.Job import com.evernote.android.job.Job
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import com.evernote.android.job.JobRequest import com.evernote.android.job.JobRequest

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.updater
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.support.v4.app.NotificationCompat import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationHandler
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver

View File

@ -71,7 +71,7 @@ class UpdaterService : IntentService(UpdaterService::class.java.name) {
val apkFile = File(externalCacheDir, "update.apk") val apkFile = File(externalCacheDir, "update.apk")
if (response.isSuccessful) { if (response.isSuccessful) {
response.body()!!.source().saveTo(apkFile) response.body!!.source().saveTo(apkFile)
} else { } else {
response.close() response.close()
throw Exception("Unsuccessful response") throw Exception("Unsuccessful response")

View File

@ -32,7 +32,7 @@ internal class ExtensionGithubApi {
} }
private fun parseResponse(response: Response): List<Extension.Available> { private fun parseResponse(response: Response): List<Extension.Available> {
val text = response.body()?.use { it.string() } ?: return emptyList() val text = response.body?.use { it.string() } ?: return emptyList()
val json = gson.fromJson<JsonArray>(text) val json = gson.fromJson<JsonArray>(text)

View File

@ -21,7 +21,7 @@ class AndroidCookieJar(context: Context) : CookieJar {
} }
} }
override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) { override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
val urlString = url.toString() val urlString = url.toString()
for (cookie in cookies) { for (cookie in cookies) {

View File

@ -1,154 +1,154 @@
package eu.kanade.tachiyomi.network 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.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebSettings import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import eu.kanade.tachiyomi.util.WebViewClientCompat import eu.kanade.tachiyomi.util.WebViewClientCompat
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import java.io.IOException import java.io.IOException
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class CloudflareInterceptor(private val context: Context) : Interceptor { class CloudflareInterceptor(private val context: Context) : Interceptor {
private val serverCheck = arrayOf("cloudflare-nginx", "cloudflare") private val serverCheck = arrayOf("cloudflare-nginx", "cloudflare")
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
/** /**
* When this is called, it initializes the WebView if it wasn't already. We use this to avoid * When this is called, it initializes the WebView if it wasn't already. We use this to avoid
* blocking the main thread too much. If used too often we could consider moving it to the * blocking the main thread too much. If used too often we could consider moving it to the
* Application class. * Application class.
*/ */
private val initWebView by lazy { private val initWebView by lazy {
if (Build.VERSION.SDK_INT >= 17) { if (Build.VERSION.SDK_INT >= 17) {
WebSettings.getDefaultUserAgent(context) WebSettings.getDefaultUserAgent(context)
} else { } else {
null null
} }
} }
@Synchronized @Synchronized
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
initWebView initWebView
val response = chain.proceed(chain.request()) val response = chain.proceed(chain.request())
// Check if Cloudflare anti-bot is on // Check if Cloudflare anti-bot is on
if (response.code() == 503 && response.header("Server") in serverCheck) { if (response.code == 503 && response.header("Server") in serverCheck) {
try { try {
response.close() response.close()
val solutionRequest = resolveWithWebView(chain.request()) val solutionRequest = resolveWithWebView(chain.request())
return chain.proceed(solutionRequest) return chain.proceed(solutionRequest)
} catch (e: Exception) { } catch (e: Exception) {
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
// we don't crash the entire app // we don't crash the entire app
throw IOException(e) throw IOException(e)
} }
} }
return response return response
} }
private fun isChallengeSolutionUrl(url: String): Boolean { private fun isChallengeSolutionUrl(url: String): Boolean {
return "chk_jschl" in url return "chk_jschl" in url
} }
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
private fun resolveWithWebView(request: Request): Request { private fun resolveWithWebView(request: Request): Request {
// We need to lock this thread until the WebView finds the challenge solution url, because // We need to lock this thread until the WebView finds the challenge solution url, because
// OkHttp doesn't support asynchronous interceptors. // OkHttp doesn't support asynchronous interceptors.
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
var webView: WebView? = null var webView: WebView? = null
var solutionUrl: String? = null var solutionUrl: String? = null
var challengeFound = false var challengeFound = false
val origRequestUrl = request.url().toString() val origRequestUrl = request.url.toString()
val headers = request.headers().toMultimap().mapValues { it.value.getOrNull(0) ?: "" } val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
handler.post { handler.post {
val view = WebView(context) val view = WebView(context)
webView = view webView = view
view.settings.javaScriptEnabled = true view.settings.javaScriptEnabled = true
view.settings.userAgentString = request.header("User-Agent") view.settings.userAgentString = request.header("User-Agent")
view.webViewClient = object : WebViewClientCompat() { view.webViewClient = object : WebViewClientCompat() {
override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean { override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean {
if (isChallengeSolutionUrl(url)) { if (isChallengeSolutionUrl(url)) {
solutionUrl = url solutionUrl = url
latch.countDown() latch.countDown()
} }
return solutionUrl != null return solutionUrl != null
} }
override fun shouldInterceptRequestCompat( override fun shouldInterceptRequestCompat(
view: WebView, view: WebView,
url: String url: String
): WebResourceResponse? { ): WebResourceResponse? {
if (solutionUrl != null) { if (solutionUrl != null) {
// Intercept any request when we have the solution. // Intercept any request when we have the solution.
return WebResourceResponse("text/plain", "UTF-8", null) return WebResourceResponse("text/plain", "UTF-8", null)
} }
return null return null
} }
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
// Http error codes are only received since M // Http error codes are only received since M
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
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()
} }
} }
override fun onReceivedErrorCompat( override fun onReceivedErrorCompat(
view: WebView, view: WebView,
errorCode: Int, errorCode: Int,
description: String?, description: String?,
failingUrl: String, failingUrl: String,
isMainFrame: Boolean isMainFrame: Boolean
) { ) {
if (isMainFrame) { if (isMainFrame) {
if (errorCode == 503) { if (errorCode == 503) {
// Found the cloudflare challenge page. // Found the cloudflare challenge page.
challengeFound = true challengeFound = true
} else { } else {
// Unlock thread, the challenge wasn't found. // Unlock thread, the challenge wasn't found.
latch.countDown() latch.countDown()
} }
} }
} }
} }
webView?.loadUrl(origRequestUrl, headers) webView?.loadUrl(origRequestUrl, headers)
} }
// Wait a reasonable amount of time to retrieve the solution. The minimum should be // Wait a reasonable amount of time to retrieve the solution. The minimum should be
// around 4 seconds but it can take more due to slow networks or server issues. // around 4 seconds but it can take more due to slow networks or server issues.
latch.await(12, TimeUnit.SECONDS) latch.await(12, TimeUnit.SECONDS)
handler.post { handler.post {
webView?.stopLoading() webView?.stopLoading()
webView?.destroy() webView?.destroy()
} }
val solution = solutionUrl ?: throw Exception("Challenge not found") val solution = solutionUrl ?: throw Exception("Challenge not found")
return Request.Builder().get() return Request.Builder().get()
.url(solution) .url(solution)
.headers(request.headers()) .headers(request.headers)
.addHeader("Referer", origRequestUrl) .addHeader("Referer", origRequestUrl)
.addHeader("Accept", "text/html,application/xhtml+xml,application/xml") .addHeader("Accept", "text/html,application/xhtml+xml,application/xml")
.addHeader("Accept-Language", "en") .addHeader("Accept-Language", "en")
.build() .build()
} }
} }

View File

@ -1,120 +1,120 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import exh.log.maybeInjectEHLogger import exh.log.maybeInjectEHLogger
import okhttp3.* import okhttp3.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.net.Socket import java.net.Socket
import java.net.UnknownHostException import java.net.UnknownHostException
import java.security.KeyManagementException import java.security.KeyManagementException
import java.security.KeyStore import java.security.KeyStore
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import javax.net.ssl.* import javax.net.ssl.*
open class NetworkHelper(context: Context) { open class NetworkHelper(context: Context) {
private val cacheDir = File(context.cacheDir, "network_cache") private val cacheDir = File(context.cacheDir, "network_cache")
private val cacheSize = 5L * 1024 * 1024 // 5 MiB private val cacheSize = 5L * 1024 * 1024 // 5 MiB
open val cookieManager = AndroidCookieJar(context) open val cookieManager = AndroidCookieJar(context)
open val client = OkHttpClient.Builder() open val client = OkHttpClient.Builder()
.cookieJar(cookieManager) .cookieJar(cookieManager)
.cache(Cache(cacheDir, cacheSize)) .cache(Cache(cacheDir, cacheSize))
.enableTLS12() .enableTLS12()
.maybeInjectEHLogger() .maybeInjectEHLogger()
.build() .build()
open val cloudflareClient = client.newBuilder() open val cloudflareClient = client.newBuilder()
.addInterceptor(CloudflareInterceptor(context)) .addInterceptor(CloudflareInterceptor(context))
.maybeInjectEHLogger() .maybeInjectEHLogger()
.build() .build()
private fun OkHttpClient.Builder.enableTLS12(): OkHttpClient.Builder { private fun OkHttpClient.Builder.enableTLS12(): OkHttpClient.Builder {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
return this return this
} }
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(null as KeyStore?) trustManagerFactory.init(null as KeyStore?)
val trustManagers = trustManagerFactory.trustManagers val trustManagers = trustManagerFactory.trustManagers
if (trustManagers.size == 1 && trustManagers[0] is X509TrustManager) { if (trustManagers.size == 1 && trustManagers[0] is X509TrustManager) {
class TLSSocketFactory @Throws(KeyManagementException::class, NoSuchAlgorithmException::class) class TLSSocketFactory @Throws(KeyManagementException::class, NoSuchAlgorithmException::class)
constructor() : SSLSocketFactory() { constructor() : SSLSocketFactory() {
private val internalSSLSocketFactory: SSLSocketFactory private val internalSSLSocketFactory: SSLSocketFactory
init { init {
val context = SSLContext.getInstance("TLS") val context = SSLContext.getInstance("TLS")
context.init(null, null, null) context.init(null, null, null)
internalSSLSocketFactory = context.socketFactory internalSSLSocketFactory = context.socketFactory
} }
override fun getDefaultCipherSuites(): Array<String> { override fun getDefaultCipherSuites(): Array<String> {
return internalSSLSocketFactory.defaultCipherSuites return internalSSLSocketFactory.defaultCipherSuites
} }
override fun getSupportedCipherSuites(): Array<String> { override fun getSupportedCipherSuites(): Array<String> {
return internalSSLSocketFactory.supportedCipherSuites return internalSSLSocketFactory.supportedCipherSuites
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun createSocket(): Socket? { override fun createSocket(): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket()) return enableTLSOnSocket(internalSSLSocketFactory.createSocket())
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket? { override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)) return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose))
} }
@Throws(IOException::class, UnknownHostException::class) @Throws(IOException::class, UnknownHostException::class)
override fun createSocket(host: String, port: Int): Socket? { override fun createSocket(host: String, port: Int): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)) return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))
} }
@Throws(IOException::class, UnknownHostException::class) @Throws(IOException::class, UnknownHostException::class)
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket? { override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)) return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort))
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun createSocket(host: InetAddress, port: Int): Socket? { override fun createSocket(host: InetAddress, port: Int): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)) return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket? { override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)) return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort))
} }
private fun enableTLSOnSocket(socket: Socket?): Socket? { private fun enableTLSOnSocket(socket: Socket?): Socket? {
if (socket != null && socket is SSLSocket) { if (socket != null && socket is SSLSocket) {
socket.enabledProtocols = socket.supportedProtocols socket.enabledProtocols = socket.supportedProtocols
} }
return socket return socket
} }
} }
sslSocketFactory(TLSSocketFactory(), trustManagers[0] as X509TrustManager) sslSocketFactory(TLSSocketFactory(), trustManagers[0] as X509TrustManager)
} }
val specCompat = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) val specCompat = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
.cipherSuites( .cipherSuites(
*ConnectionSpec.MODERN_TLS.cipherSuites().orEmpty().toTypedArray(), *ConnectionSpec.MODERN_TLS.cipherSuites.orEmpty().toTypedArray(),
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
) )
.build() .build()
val specs = listOf(specCompat, ConnectionSpec.CLEARTEXT) val specs = listOf(specCompat, ConnectionSpec.CLEARTEXT)
connectionSpecs(specs) connectionSpecs(specs)
return this return this
} }
} }

View File

@ -1,81 +1,81 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import exh.util.withRootCause import exh.util.withRootCause
import okhttp3.Call import okhttp3.Call
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import rx.Producer import rx.Producer
import rx.Subscription import rx.Subscription
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
fun Call.asObservableWithAsyncStacktrace(): Observable<Pair<Exception, Response>> { fun Call.asObservableWithAsyncStacktrace(): Observable<Pair<Exception, Response>> {
// Record stacktrace at creation time for easier debugging // Record stacktrace at creation time for easier debugging
// asObservable is involved in a lot of crashes so this is worth the performance hit // asObservable is involved in a lot of crashes so this is worth the performance hit
val asyncStackTrace = Exception("Async stacktrace") val asyncStackTrace = Exception("Async stacktrace")
return Observable.unsafeCreate { subscriber -> return Observable.unsafeCreate { subscriber ->
// Since Call is a one-shot type, clone it for each new subscriber. // Since Call is a one-shot type, clone it for each new subscriber.
val call = clone() val call = clone()
// Wrap the call in a helper which handles both unsubscription and backpressure. // Wrap the call in a helper which handles both unsubscription and backpressure.
val requestArbiter = object : AtomicBoolean(), Producer, Subscription { val requestArbiter = object : AtomicBoolean(), Producer, Subscription {
val executed = AtomicBoolean(false) val executed = AtomicBoolean(false)
override fun request(n: Long) { override fun request(n: Long) {
if (n == 0L || !compareAndSet(false, true)) return if (n == 0L || !compareAndSet(false, true)) return
try { try {
val response = call.execute() val response = call.execute()
executed.set(true) executed.set(true)
if (!subscriber.isUnsubscribed) { if (!subscriber.isUnsubscribed) {
subscriber.onNext(asyncStackTrace to response) subscriber.onNext(asyncStackTrace to response)
subscriber.onCompleted() subscriber.onCompleted()
} }
} catch (error: Throwable) { } catch (error: Throwable) {
if (!subscriber.isUnsubscribed) { if (!subscriber.isUnsubscribed) {
subscriber.onError(error.withRootCause(asyncStackTrace)) subscriber.onError(error.withRootCause(asyncStackTrace))
} }
} }
} }
override fun unsubscribe() { override fun unsubscribe() {
if(!executed.get()) if(!executed.get())
call.cancel() call.cancel()
} }
override fun isUnsubscribed(): Boolean { override fun isUnsubscribed(): Boolean {
return call.isCanceled return call.isCanceled()
} }
} }
subscriber.add(requestArbiter) subscriber.add(requestArbiter)
subscriber.setProducer(requestArbiter) subscriber.setProducer(requestArbiter)
} }
} }
fun Call.asObservable() = asObservableWithAsyncStacktrace().map { it.second } fun Call.asObservable() = asObservableWithAsyncStacktrace().map { it.second }
fun Call.asObservableSuccess(): Observable<Response> { fun Call.asObservableSuccess(): Observable<Response> {
return asObservableWithAsyncStacktrace().map { (asyncStacktrace, response) -> return asObservableWithAsyncStacktrace().map { (asyncStacktrace, response) ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
response.close() response.close()
throw Exception("HTTP error ${response.code()}", asyncStacktrace) throw Exception("HTTP error ${response.code}", asyncStacktrace)
} else response } else response
} }
} }
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call { fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
val progressClient = newBuilder() val progressClient = newBuilder()
.cache(null) .cache(null)
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request()) val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder() originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body()!!, listener)) .body(ProgressResponseBody(originalResponse.body!!, listener))
.build() .build()
} }
.build() .build()
return progressClient.newCall(request) return progressClient.newCall(request)
} }

View File

@ -1,40 +1,40 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okio.* import okio.*
import java.io.IOException import java.io.IOException
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
private val bufferedSource: BufferedSource by lazy { private val bufferedSource: BufferedSource by lazy {
Okio.buffer(source(responseBody.source())) source(responseBody.source()).buffer()
} }
override fun contentType(): MediaType { override fun contentType(): MediaType {
return responseBody.contentType()!! return responseBody.contentType()!!
} }
override fun contentLength(): Long { override fun contentLength(): Long {
return responseBody.contentLength() return responseBody.contentLength()
} }
override fun source(): BufferedSource { override fun source(): BufferedSource {
return bufferedSource return bufferedSource
} }
private fun source(source: Source): Source { private fun source(source: Source): Source {
return object : ForwardingSource(source) { return object : ForwardingSource(source) {
internal var totalBytesRead = 0L internal var totalBytesRead = 0L
@Throws(IOException::class) @Throws(IOException::class)
override fun read(sink: Buffer, byteCount: Long): Long { override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount) val bytesRead = super.read(sink, byteCount)
// read() returns the number of bytes read, or -1 if this source is exhausted. // read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += if (bytesRead != -1L) bytesRead else 0 totalBytesRead += if (bytesRead != -1L) bytesRead else 0
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L) progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
return bytesRead return bytesRead
} }
} }
} }
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.source package eu.kanade.tachiyomi.source
import android.support.v7.preference.PreferenceScreen import androidx.preference.PreferenceScreen
interface ConfigurableSource : Source { interface ConfigurableSource : Source {

View File

@ -1,395 +1,397 @@
package eu.kanade.tachiyomi.source.online package eu.kanade.tachiyomi.source.online
import android.app.Application import android.app.Application
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.network.* import eu.kanade.tachiyomi.network.*
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.* import eu.kanade.tachiyomi.source.model.*
import exh.patch.injectPatches import exh.patch.injectPatches
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import okhttp3.* import okhttp3.Headers
import rx.Observable import okhttp3.OkHttpClient
import uy.kohesive.injekt.Injekt import okhttp3.Request
import uy.kohesive.injekt.api.get import okhttp3.Response
import java.lang.Exception import rx.Observable
import java.net.URI import uy.kohesive.injekt.Injekt
import java.net.URISyntaxException import uy.kohesive.injekt.api.get
import java.security.MessageDigest import java.net.URI
import java.net.URISyntaxException
/** import java.security.MessageDigest
* A simple implementation for sources from a website.
*/ /**
abstract class HttpSource : CatalogueSource { * A simple implementation for sources from a website.
*/
/** abstract class HttpSource : CatalogueSource {
* Network service.
*/ /**
protected val network: NetworkHelper by lazy { * Network service.
val original = Injekt.get<NetworkHelper>() */
object : NetworkHelper(Injekt.get<Application>()) { protected val network: NetworkHelper by lazy {
override val client: OkHttpClient? val original = Injekt.get<NetworkHelper>()
get() = delegate?.networkHttpClient ?: original.client object : NetworkHelper(Injekt.get<Application>()) {
.newBuilder() override val client: OkHttpClient
.injectPatches { id } get() = delegate?.networkHttpClient ?: original.client
.build() .newBuilder()
.injectPatches { id }
override val cloudflareClient: OkHttpClient? .build()
get() = delegate?.networkCloudflareClient ?: original.cloudflareClient
.newBuilder() override val cloudflareClient: OkHttpClient
.injectPatches { id } get() = delegate?.networkCloudflareClient ?: original.cloudflareClient
.build() .newBuilder()
.injectPatches { id }
override val cookieManager: AndroidCookieJar .build()
get() = original.cookieManager
} override val cookieManager: AndroidCookieJar
} get() = original.cookieManager
}
// /** }
// * Preferences that a source may need.
// */ // /**
// val preferences: SharedPreferences by lazy { // * Preferences that a source may need.
// Injekt.get<Application>().getSharedPreferences("source_$id", Context.MODE_PRIVATE) // */
// } // val preferences: SharedPreferences by lazy {
// Injekt.get<Application>().getSharedPreferences("source_$id", Context.MODE_PRIVATE)
/** // }
* Base url of the website without the trailing slash, like: http://mysite.com
*/ /**
abstract val baseUrl: String * Base url of the website without the trailing slash, like: http://mysite.com
*/
/** abstract val baseUrl: String
* Version id used to generate the source id. If the site completely changes and urls are
* incompatible, you may increase this value and it'll be considered as a new source. /**
*/ * Version id used to generate the source id. If the site completely changes and urls are
open val versionId = 1 * incompatible, you may increase this value and it'll be considered as a new source.
*/
/** open val versionId = 1
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
* of the MD5 of the string: sourcename/language/versionId /**
* Note the generated id sets the sign bit to 0. * Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
*/ * of the MD5 of the string: sourcename/language/versionId
override val id by lazy { * Note the generated id sets the sign bit to 0.
val key = "${name.toLowerCase()}/$lang/$versionId" */
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) override val id by lazy {
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE val key = "${name.toLowerCase()}/$lang/$versionId"
} val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
/** }
* Headers used for requests.
*/ /**
val headers: Headers by lazy { headersBuilder().build() } * Headers used for requests.
*/
/** val headers: Headers by lazy { headersBuilder().build() }
* Default network client for doing requests.
*/ /**
open val client: OkHttpClient * Default network client for doing requests.
get() = delegate?.baseHttpClient ?: network.client */
open val client: OkHttpClient
/** get() = delegate?.baseHttpClient ?: network.client
* Headers builder for requests. Implementations can override this method for custom headers.
*/ /**
open protected fun headersBuilder() = Headers.Builder().apply { * Headers builder for requests. Implementations can override this method for custom headers.
add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") */
} open protected fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)")
/** }
* Visible name of the source.
*/ /**
override fun toString() = "$name (${lang.toUpperCase()})" * Visible name of the source.
*/
/** override fun toString() = "$name (${lang.toUpperCase()})"
* Returns an observable containing a page with a list of manga. Normally it's not needed to
* override this method. /**
* * Returns an observable containing a page with a list of manga. Normally it's not needed to
* @param page the page number to retrieve. * override this method.
*/ *
override fun fetchPopularManga(page: Int): Observable<MangasPage> { * @param page the page number to retrieve.
return client.newCall(popularMangaRequest(page)) */
.asObservableSuccess() override fun fetchPopularManga(page: Int): Observable<MangasPage> {
.map { response -> return client.newCall(popularMangaRequest(page))
popularMangaParse(response) .asObservableSuccess()
} .map { response ->
} popularMangaParse(response)
}
/** }
* Returns the request for the popular manga given the page.
* /**
* @param page the page number to retrieve. * Returns the request for the popular manga given the page.
*/ *
abstract protected fun popularMangaRequest(page: Int): Request * @param page the page number to retrieve.
*/
/** abstract protected fun popularMangaRequest(page: Int): Request
* Parses the response from the site and returns a [MangasPage] object.
* /**
* @param response the response from the site. * Parses the response from the site and returns a [MangasPage] object.
*/ *
abstract protected fun popularMangaParse(response: Response): MangasPage * @param response the response from the site.
*/
/** abstract protected fun popularMangaParse(response: Response): MangasPage
* Returns an observable containing a page with a list of manga. Normally it's not needed to
* override this method. /**
* * Returns an observable containing a page with a list of manga. Normally it's not needed to
* @param page the page number to retrieve. * override this method.
* @param query the search query. *
* @param filters the list of filters to apply. * @param page the page number to retrieve.
*/ * @param query the search query.
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { * @param filters the list of filters to apply.
return client.newCall(searchMangaRequest(page, query, filters)) */
.asObservableSuccess() override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
.map { response -> return client.newCall(searchMangaRequest(page, query, filters))
searchMangaParse(response) .asObservableSuccess()
} .map { response ->
} searchMangaParse(response)
}
/** }
* Returns the request for the search manga given the page.
* /**
* @param page the page number to retrieve. * Returns the request for the search manga given the page.
* @param query the search query. *
* @param filters the list of filters to apply. * @param page the page number to retrieve.
*/ * @param query the search query.
abstract protected fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request * @param filters the list of filters to apply.
*/
/** abstract protected fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request
* Parses the response from the site and returns a [MangasPage] object.
* /**
* @param response the response from the site. * Parses the response from the site and returns a [MangasPage] object.
*/ *
abstract protected fun searchMangaParse(response: Response): MangasPage * @param response the response from the site.
*/
/** abstract protected fun searchMangaParse(response: Response): MangasPage
* Returns an observable containing a page with a list of latest manga updates.
* /**
* @param page the page number to retrieve. * Returns an observable containing a page with a list of latest manga updates.
*/ *
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { * @param page the page number to retrieve.
return client.newCall(latestUpdatesRequest(page)) */
.asObservableSuccess() override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
.map { response -> return client.newCall(latestUpdatesRequest(page))
latestUpdatesParse(response) .asObservableSuccess()
} .map { response ->
} latestUpdatesParse(response)
}
/** }
* Returns the request for latest manga given the page.
* /**
* @param page the page number to retrieve. * Returns the request for latest manga given the page.
*/ *
abstract protected fun latestUpdatesRequest(page: Int): Request * @param page the page number to retrieve.
*/
/** abstract protected fun latestUpdatesRequest(page: Int): Request
* Parses the response from the site and returns a [MangasPage] object.
* /**
* @param response the response from the site. * Parses the response from the site and returns a [MangasPage] object.
*/ *
abstract protected fun latestUpdatesParse(response: Response): MangasPage * @param response the response from the site.
*/
/** abstract protected fun latestUpdatesParse(response: Response): MangasPage
* Returns an observable with the updated details for a manga. Normally it's not needed to
* override this method. /**
* * Returns an observable with the updated details for a manga. Normally it's not needed to
* @param manga the manga to be updated. * override this method.
*/ *
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { * @param manga the manga to be updated.
return client.newCall(mangaDetailsRequest(manga)) */
.asObservableSuccess() override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
.map { response -> return client.newCall(mangaDetailsRequest(manga))
mangaDetailsParse(response).apply { initialized = true } .asObservableSuccess()
} .map { response ->
} mangaDetailsParse(response).apply { initialized = true }
}
/** }
* Returns the request for the details of a manga. Override only if it's needed to change the
* url, send different headers or request method like POST. /**
* * Returns the request for the details of a manga. Override only if it's needed to change the
* @param manga the manga to be updated. * url, send different headers or request method like POST.
*/ *
open fun mangaDetailsRequest(manga: SManga): Request { * @param manga the manga to be updated.
return GET(baseUrl + manga.url, headers) */
} open fun mangaDetailsRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers)
/** }
* Parses the response from the site and returns the details of a manga.
* /**
* @param response the response from the site. * Parses the response from the site and returns the details of a manga.
*/ *
abstract protected fun mangaDetailsParse(response: Response): SManga * @param response the response from the site.
*/
/** abstract protected fun mangaDetailsParse(response: Response): SManga
* Returns an observable with the updated chapter list for a manga. Normally it's not needed to
* override this method. If a manga is licensed an empty chapter list observable is returned /**
* * Returns an observable with the updated chapter list for a manga. Normally it's not needed to
* @param manga the manga to look for chapters. * override this method. If a manga is licensed an empty chapter list observable is returned
*/ *
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { * @param manga the manga to look for chapters.
if (manga.status != SManga.LICENSED) { */
return client.newCall(chapterListRequest(manga)) override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
.asObservableSuccess() if (manga.status != SManga.LICENSED) {
.map { response -> return client.newCall(chapterListRequest(manga))
chapterListParse(response) .asObservableSuccess()
} .map { response ->
} else { chapterListParse(response)
return Observable.error(Exception("Licensed - No chapters to show")) }
} } else {
} return Observable.error(Exception("Licensed - No chapters to show"))
}
/** }
* Returns the request for updating the chapter list. Override only if it's needed to override
* the url, send different headers or request method like POST. /**
* * Returns the request for updating the chapter list. Override only if it's needed to override
* @param manga the manga to look for chapters. * the url, send different headers or request method like POST.
*/ *
open protected fun chapterListRequest(manga: SManga): Request { * @param manga the manga to look for chapters.
return GET(baseUrl + manga.url, headers) */
} open protected fun chapterListRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers)
/** }
* Parses the response from the site and returns a list of chapters.
* /**
* @param response the response from the site. * Parses the response from the site and returns a list of chapters.
*/ *
abstract protected fun chapterListParse(response: Response): List<SChapter> * @param response the response from the site.
*/
/** abstract protected fun chapterListParse(response: Response): List<SChapter>
* Returns an observable with the page list for a chapter.
* /**
* @param chapter the chapter whose page list has to be fetched. * Returns an observable with the page list for a chapter.
*/ *
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { * @param chapter the chapter whose page list has to be fetched.
return client.newCall(pageListRequest(chapter)) */
.asObservableSuccess() override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
.map { response -> return client.newCall(pageListRequest(chapter))
pageListParse(response) .asObservableSuccess()
} .map { response ->
} pageListParse(response)
}
/** }
* Returns the request for getting the page list. Override only if it's needed to override the
* url, send different headers or request method like POST. /**
* * Returns the request for getting the page list. Override only if it's needed to override the
* @param chapter the chapter whose page list has to be fetched. * url, send different headers or request method like POST.
*/ *
open protected fun pageListRequest(chapter: SChapter): Request { * @param chapter the chapter whose page list has to be fetched.
return GET(baseUrl + chapter.url, headers) */
} open protected fun pageListRequest(chapter: SChapter): Request {
return GET(baseUrl + chapter.url, headers)
/** }
* Parses the response from the site and returns a list of pages.
* /**
* @param response the response from the site. * Parses the response from the site and returns a list of pages.
*/ *
abstract protected fun pageListParse(response: Response): List<Page> * @param response the response from the site.
*/
/** abstract protected fun pageListParse(response: Response): List<Page>
* Returns an observable with the page containing the source url of the image. If there's any
* error, it will return null instead of throwing an exception. /**
* * Returns an observable with the page containing the source url of the image. If there's any
* @param page the page whose source image has to be fetched. * error, it will return null instead of throwing an exception.
*/ *
open fun fetchImageUrl(page: Page): Observable<String> { * @param page the page whose source image has to be fetched.
return client.newCall(imageUrlRequest(page)) */
.asObservableSuccess() open fun fetchImageUrl(page: Page): Observable<String> {
.map { imageUrlParse(it) } return client.newCall(imageUrlRequest(page))
} .asObservableSuccess()
.map { imageUrlParse(it) }
/** }
* Returns the request for getting the url to the source image. Override only if it's needed to
* override the url, send different headers or request method like POST. /**
* * Returns the request for getting the url to the source image. Override only if it's needed to
* @param page the chapter whose page list has to be fetched * override the url, send different headers or request method like POST.
*/ *
open protected fun imageUrlRequest(page: Page): Request { * @param page the chapter whose page list has to be fetched
return GET(page.url, headers) */
} open protected fun imageUrlRequest(page: Page): Request {
return GET(page.url, headers)
/** }
* Parses the response from the site and returns the absolute url to the source image.
* /**
* @param response the response from the site. * Parses the response from the site and returns the absolute url to the source image.
*/ *
abstract protected fun imageUrlParse(response: Response): String * @param response the response from the site.
*/
/** abstract protected fun imageUrlParse(response: Response): String
* Returns an observable with the response of the source image.
* /**
* @param page the page whose source image has to be downloaded. * Returns an observable with the response of the source image.
*/ *
open fun fetchImage(page: Page): Observable<Response> { * @param page the page whose source image has to be downloaded.
return client.newCallWithProgress(imageRequest(page), page) */
.asObservableSuccess() open fun fetchImage(page: Page): Observable<Response> {
} return client.newCallWithProgress(imageRequest(page), page)
.asObservableSuccess()
/** }
* Returns the request for getting the source image. Override only if it's needed to override
* the url, send different headers or request method like POST. /**
* * Returns the request for getting the source image. Override only if it's needed to override
* @param page the chapter whose page list has to be fetched * the url, send different headers or request method like POST.
*/ *
open protected fun imageRequest(page: Page): Request { * @param page the chapter whose page list has to be fetched
return GET(page.imageUrl!!, headers) */
} open protected fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers)
/** }
* Assigns the url of the chapter without the scheme and domain. It saves some redundancy from
* database and the urls could still work after a domain change. /**
* * Assigns the url of the chapter without the scheme and domain. It saves some redundancy from
* @param url the full url to the chapter. * database and the urls could still work after a domain change.
*/ *
fun SChapter.setUrlWithoutDomain(url: String) { * @param url the full url to the chapter.
this.url = getUrlWithoutDomain(url) */
} fun SChapter.setUrlWithoutDomain(url: String) {
this.url = getUrlWithoutDomain(url)
/** }
* Assigns the url of the manga without the scheme and domain. It saves some redundancy from
* database and the urls could still work after a domain change. /**
* * Assigns the url of the manga without the scheme and domain. It saves some redundancy from
* @param url the full url to the manga. * database and the urls could still work after a domain change.
*/ *
fun SManga.setUrlWithoutDomain(url: String) { * @param url the full url to the manga.
this.url = getUrlWithoutDomain(url) */
} fun SManga.setUrlWithoutDomain(url: String) {
this.url = getUrlWithoutDomain(url)
/** }
* Returns the url of the given string without the scheme and domain.
* /**
* @param orig the full url. * Returns the url of the given string without the scheme and domain.
*/ *
private fun getUrlWithoutDomain(orig: String): String { * @param orig the full url.
try { */
val uri = URI(orig) private fun getUrlWithoutDomain(orig: String): String {
var out = uri.path try {
if (uri.query != null) val uri = URI(orig)
out += "?" + uri.query var out = uri.path
if (uri.fragment != null) if (uri.query != null)
out += "#" + uri.fragment out += "?" + uri.query
return out if (uri.fragment != null)
} catch (e: URISyntaxException) { out += "#" + uri.fragment
return orig return out
} } catch (e: URISyntaxException) {
} return orig
}
/** }
* Called before inserting a new chapter into database. Use it if you need to override chapter
* fields, like the title or the chapter number. Do not change anything to [manga]. /**
* * Called before inserting a new chapter into database. Use it if you need to override chapter
* @param chapter the chapter to be added. * fields, like the title or the chapter number. Do not change anything to [manga].
* @param manga the manga of the chapter. *
*/ * @param chapter the chapter to be added.
open fun prepareNewChapter(chapter: SChapter, manga: SManga) { * @param manga the manga of the chapter.
} */
open fun prepareNewChapter(chapter: SChapter, manga: SManga) {
/** }
* Returns the list of filters for the source.
*/ /**
override fun getFilterList() = FilterList() * Returns the list of filters for the source.
*/
// EXH --> override fun getFilterList() = FilterList()
private var delegate: DelegatedHttpSource? = null
get() = if(Injekt.get<PreferencesHelper>().eh_delegateSources().getOrDefault()) // EXH -->
field private var delegate: DelegatedHttpSource? = null
else null get() = if(Injekt.get<PreferencesHelper>().eh_delegateSources().getOrDefault())
fun bindDelegate(delegate: DelegatedHttpSource) { field
this.delegate = delegate else null
} fun bindDelegate(delegate: DelegatedHttpSource) {
// EXH <-- this.delegate = delegate
} }
// EXH <--
}

View File

@ -18,35 +18,36 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateHelper import exh.eh.EHentaiUpdateHelper
import exh.eh.EHentaiUpdateWorkerConstants
import exh.eh.GalleryEntry
import exh.metadata.EX_DATE_FORMAT import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.EH_GENRE_NAMESPACE import exh.metadata.metadata.EHentaiSearchMetadata.Companion.EH_GENRE_NAMESPACE
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_LIGHT import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_LIGHT
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_NORMAL import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_NORMAL
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag
import exh.metadata.nullIfBlank import exh.metadata.nullIfBlank
import exh.metadata.parseHumanReadableByteCount import exh.metadata.parseHumanReadableByteCount
import exh.debug.DebugToggles
import exh.ui.login.LoginController import exh.ui.login.LoginController
import exh.util.UriFilter import exh.util.UriFilter
import exh.util.UriGroup import exh.util.UriGroup
import exh.util.ignore import exh.util.ignore
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import kotlinx.coroutines.runBlocking
import okhttp3.* import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.nodes.TextNode
import rx.Observable import rx.Observable
import rx.Single
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder import java.net.URLEncoder
import java.util.* import java.util.*
import exh.metadata.metadata.base.RaisedTag
import exh.eh.EHentaiUpdateWorkerConstants
import exh.eh.GalleryEntry
import kotlinx.coroutines.runBlocking
import org.jsoup.nodes.TextNode
import rx.Single
import java.lang.RuntimeException
// TODO Consider gallery updating when doing tabbed browsing // TODO Consider gallery updating when doing tabbed browsing
class EHentai(override val id: Long, class EHentai(override val id: Long,
@ -108,11 +109,11 @@ class EHentai(override val id: Long,
}) })
} }
val parsedLocation = HttpUrl.parse(doc.location()) val parsedLocation = doc.location().toHttpUrlOrNull()
//Add to page if required //Add to page if required
val hasNextPage = if(parsedLocation == null val hasNextPage = if(parsedLocation == null
|| !parsedLocation.queryParameterNames().contains(REVERSE_PARAM)) { || !parsedLocation.queryParameterNames.contains(REVERSE_PARAM)) {
select("a[onclick=return false]").last()?.let { select("a[onclick=return false]").last()?.let {
it.text() == ">" it.text() == ">"
} ?: false } ?: false
@ -151,7 +152,7 @@ class EHentai(override val id: Long,
throttleFunc() throttleFunc()
val resp = client.newCall(exGet(baseUrl + url)).execute() val resp = client.newCall(exGet(baseUrl + url)).execute()
if (!resp.isSuccessful) error("HTTP error (${resp.code()})!") if (!resp.isSuccessful) error("HTTP error (${resp.code})!")
doc = resp.asJsoup() doc = resp.asJsoup()
val parentLink = doc!!.select("#gdd .gdt1").find { el -> val parentLink = doc!!.select("#gdd .gdt1").find { el ->
@ -344,10 +345,10 @@ class EHentai(override val id: Long,
} else { } else {
response.close() response.close()
if(response.code() == 404) { if (response.code == 404) {
throw GalleryNotFoundException(stacktrace) throw GalleryNotFoundException(stacktrace)
} else { } else {
throw Exception("HTTP error ${response.code()}", stacktrace) throw Exception("HTTP error ${response.code}", stacktrace)
} }
} }
} }
@ -707,7 +708,7 @@ class EHentai(override val id: Long,
val outJson = JsonParser().parse(client.newCall(Request.Builder() val outJson = JsonParser().parse(client.newCall(Request.Builder()
.url(EH_API_BASE) .url(EH_API_BASE)
.post(RequestBody.create(JSON, json.toString())) .post(RequestBody.create(JSON, json.toString()))
.build()).execute().body()!!.string()).obj .build()).execute().body!!.string()).obj
val obj = outJson["tokenlist"].array.first() val obj = outJson["tokenlist"].array.first()
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/" return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
@ -720,7 +721,7 @@ class EHentai(override val id: Long,
private const val REVERSE_PARAM = "TEH_REVERSE" private const val REVERSE_PARAM = "TEH_REVERSE"
private const val EH_API_BASE = "https://api.e-hentai.org/api.php" private const val EH_API_BASE = "https://api.e-hentai.org/api.php"
private val JSON = MediaType.parse("application/json; charset=utf-8")!! private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!!
private val FAVORITES_BORDER_HEX_COLORS = listOf( private val FAVORITES_BORDER_HEX_COLORS = listOf(
"000", "000",

View File

@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.GalleryAddEvent
import exh.HITOMI_SOURCE_ID import exh.HITOMI_SOURCE_ID
import exh.hitomi.HitomiNozomi import exh.hitomi.HitomiNozomi
import exh.metadata.metadata.HitomiSearchMetadata import exh.metadata.metadata.HitomiSearchMetadata
@ -265,7 +264,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
val range = response.header("Content-Range")!! val range = response.header("Content-Range")!!
val total = range.substringAfter('/').toLong() val total = range.substringAfter('/').toLong()
val end = range.substringBefore('/').substringAfter('-').toLong() val end = range.substringBefore('/').substringAfter('-').toLong()
val body = response.body()!! val body = response.body!!
return parseNozomiPage(body.bytes()) return parseNozomiPage(body.bytes())
.map { .map {
MangasPage(it, end < total - 1) MangasPage(it, end < total - 1)
@ -360,8 +359,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val hlId = response.request().url().pathSegments().last().removeSuffix(".js").toLong() val hlId = response.request.url.pathSegments.last().removeSuffix(".js").toLong()
val str = response.body()!!.string() val str = response.body!!.string()
val json = jsonParser.parse(str.removePrefix("var galleryinfo =")) val json = jsonParser.parse(str.removePrefix("var galleryinfo ="))
return json.array.mapIndexed { index, jsonElement -> return json.array.mapIndexed { index, jsonElement ->
Page( Page(
@ -385,7 +384,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
override fun imageRequest(page: Page): Request { override fun imageRequest(page: Page): Request {
val request = super.imageRequest(page) val request = super.imageRequest(page)
val hlId = request.url().pathSegments().let { val hlId = request.url.pathSegments.let {
it[it.lastIndex - 1] it[it.lastIndex - 1]
} }
return request.newBuilder() return request.newBuilder()

View File

@ -3,8 +3,6 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.github.salomonbrys.kotson.* import com.github.salomonbrys.kotson.*
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonParser import com.google.gson.JsonParser
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -15,12 +13,11 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.GalleryAddEvent
import exh.NHENTAI_SOURCE_ID import exh.NHENTAI_SOURCE_ID
import exh.metadata.metadata.NHentaiSearchMetadata import exh.metadata.metadata.NHentaiSearchMetadata
import exh.metadata.metadata.NHentaiSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.NHentaiSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.util.* import exh.util.urlImportFetchSearchManga
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
@ -153,17 +150,17 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
} }
} }
val hasNextPage = if(!response.request().url().queryParameterNames().contains(REVERSE_PARAM)) { val hasNextPage = if (!response.request.url.queryParameterNames.contains(REVERSE_PARAM)) {
doc.selectFirst(".next") != null doc.selectFirst(".next") != null
} else { } else {
response.request().url().queryParameter(REVERSE_PARAM)!!.toBoolean() response.request.url.queryParameter(REVERSE_PARAM)!!.toBoolean()
} }
return MangasPage(mangas, hasNextPage) return MangasPage(mangas, hasNextPage)
} }
override fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) { override fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) {
val json = GALLERY_JSON_REGEX.find(input.body()!!.string())!!.groupValues[1] val json = GALLERY_JSON_REGEX.find(input.body!!.string())!!.groupValues[1]
val obj = jsonParser.parse(json).asJsonObject val obj = jsonParser.parse(json).asJsonObject
with(metadata) { with(metadata) {

View File

@ -17,9 +17,13 @@ import exh.util.CachedField
import exh.util.NakedTrie import exh.util.NakedTrie
import exh.util.await import exh.util.await
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.rx2.asSingle import kotlinx.coroutines.rx2.asSingle
import kotlinx.coroutines.withContext
import okhttp3.* import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -64,7 +68,7 @@ class EightMuses: HttpSource(),
.asObservableSuccess() .asObservableSuccess()
.toSingle() .toSingle()
.await(Schedulers.io()) .await(Schedulers.io())
.body()!!.string() .body!!.string()
val parsed = Jsoup.parse(result) val parsed = Jsoup.parse(result)
@ -115,11 +119,11 @@ class EightMuses: HttpSource(),
*/ */
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val urlBuilder = if(!query.isBlank()) { val urlBuilder = if(!query.isBlank()) {
HttpUrl.parse("$baseUrl/search")!! "$baseUrl/search".toHttpUrlOrNull()!!
.newBuilder() .newBuilder()
.addQueryParameter("q", query) .addQueryParameter("q", query)
} else { } else {
HttpUrl.parse("$baseUrl/comics")!! "$baseUrl/comics".toHttpUrlOrNull()!!
.newBuilder() .newBuilder()
} }

View File

@ -28,7 +28,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.rx2.asSingle import kotlinx.coroutines.rx2.asSingle
import okhttp3.* import okhttp3.CookieJar
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
@ -371,7 +374,7 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
*/ */
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val doc = response.asJsoup() val doc = response.asJsoup()
val basePath = listOf("data") + response.request().url().pathSegments() val basePath = listOf("data") + response.request.url.pathSegments
val scripts = doc.getElementsByTag("script").map { it.data() } val scripts = doc.getElementsByTag("script").map { it.data() }
for(script in scripts) { for(script in scripts) {
val totalPages = TOTAL_PAGES_REGEX.find(script)?.groupValues?.getOrNull(1)?.toIntOrNull() ?: continue val totalPages = TOTAL_PAGES_REGEX.find(script)?.groupValues?.getOrNull(1)?.toIntOrNull() ?: continue

View File

@ -15,7 +15,7 @@ import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUA
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import rx.Observable import rx.Observable
@ -72,7 +72,7 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
RaisedTag("artist", it, TAG_TYPE_VIRTUAL) RaisedTag("artist", it, TAG_TYPE_VIRTUAL)
} }
readerId = HttpUrl.parse(input.select("[title=Read]").attr("href"))!!.pathSegments()[2] readerId = input.select("[title=Read]").attr("href").toHttpUrlOrNull()!!.pathSegments[2]
} }
} }

View File

@ -16,25 +16,23 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import exh.GalleryAddEvent
import exh.TSUMINO_SOURCE_ID import exh.TSUMINO_SOURCE_ID
import exh.ui.captcha.ActionCompletionVerifier
import exh.ui.captcha.BrowserActionActivity
import exh.metadata.metadata.TsuminoSearchMetadata import exh.metadata.metadata.TsuminoSearchMetadata
import exh.metadata.metadata.TsuminoSearchMetadata.Companion.BASE_URL import exh.metadata.metadata.TsuminoSearchMetadata.Companion.BASE_URL
import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.ui.captcha.ActionCompletionVerifier
import exh.ui.captcha.BrowserActionActivity
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import okhttp3.* import okhttp3.*
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.io.IOException
class Tsumino(private val context: Context): ParsedHttpSource(), class Tsumino(private val context: Context): ParsedHttpSource(),
LewdSource<TsuminoSearchMetadata, Document>, LewdSource<TsuminoSearchMetadata, Document>,
@ -127,7 +125,7 @@ class Tsumino(private val context: Context): ParsedHttpSource(),
} }
fun genericMangaParse(response: Response): MangasPage { fun genericMangaParse(response: Response): MangasPage {
val json = jsonParser.parse(response.body()!!.string()!!).asJsonObject val json = jsonParser.parse(response.body!!.string()!!).asJsonObject
val hasNextPage = json["pageNumber"].int < json["pageCount"].int val hasNextPage = json["pageNumber"].int < json["pageCount"].int
val manga = json["data"].array.map { val manga = json["data"].array.map {

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.ui.base.activity package eu.kanade.tachiyomi.ui.base.activity
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import eu.kanade.tachiyomi.util.LocaleHelper import eu.kanade.tachiyomi.util.LocaleHelper
abstract class BaseActivity : AppCompatActivity() { abstract class BaseActivity : AppCompatActivity() {

View File

@ -1,11 +1,11 @@
package eu.kanade.tachiyomi.ui.base.controller package eu.kanade.tachiyomi.ui.base.controller
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.base.controller
import android.content.pm.PackageManager.PERMISSION_GRANTED import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build import android.os.Build
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction

View File

@ -3,8 +3,6 @@ package eu.kanade.tachiyomi.ui.base.controller;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -14,6 +12,9 @@ import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler; import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/** /**
* A controller that displays a dialog window, floating on top of its activity's window. * A controller that displays a dialog window, floating on top of its activity's window.
* This is a wrapper over {@link Dialog} object like {@link android.app.DialogFragment}. * This is a wrapper over {@link Dialog} object like {@link android.app.DialogFragment}.

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.base.controller package eu.kanade.tachiyomi.ui.base.controller
import android.os.Bundle import android.os.Bundle
import android.support.annotation.CallSuper
import android.view.View import android.view.View
import androidx.annotation.CallSuper
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription

View File

@ -1,11 +1,10 @@
package eu.kanade.tachiyomi.ui.base.controller package eu.kanade.tachiyomi.ui.base.controller
import android.support.v4.widget.DrawerLayout
import android.view.ViewGroup import android.view.ViewGroup
interface SecondaryDrawerController { interface SecondaryDrawerController {
fun createSecondaryDrawer(drawer: DrawerLayout): ViewGroup? fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup?
fun cleanupSecondaryDrawer(drawer: DrawerLayout) fun cleanupSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout)
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.ui.base.controller package eu.kanade.tachiyomi.ui.base.controller
import android.support.design.widget.TabLayout import com.google.android.material.tabs.TabLayout
interface TabbedController { interface TabbedController {

View File

@ -1,10 +1,9 @@
package eu.kanade.tachiyomi.ui.base.holder package eu.kanade.tachiyomi.ui.base.holder
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view), LayoutContainer { abstract class BaseViewHolder(view: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(view), LayoutContainer {
override val containerView: View? override val containerView: View?
get() = itemView get() = itemView

View File

@ -1,61 +1,61 @@
package eu.kanade.tachiyomi.ui.base.presenter; package eu.kanade.tachiyomi.ui.base.presenter;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import androidx.annotation.Nullable;
import nucleus.factory.PresenterFactory; import nucleus.factory.PresenterFactory;
import nucleus.presenter.Presenter; import nucleus.presenter.Presenter;
public class NucleusConductorDelegate<P extends Presenter> { public class NucleusConductorDelegate<P extends Presenter> {
@Nullable private P presenter; @Nullable private P presenter;
@Nullable private Bundle bundle; @Nullable private Bundle bundle;
private PresenterFactory<P> factory; private PresenterFactory<P> factory;
public NucleusConductorDelegate(PresenterFactory<P> creator) { public NucleusConductorDelegate(PresenterFactory<P> creator) {
this.factory = creator; this.factory = creator;
} }
public P getPresenter() { public P getPresenter() {
if (presenter == null) { if (presenter == null) {
presenter = factory.createPresenter(); presenter = factory.createPresenter();
presenter.create(bundle); presenter.create(bundle);
bundle = null; bundle = null;
} }
return presenter; return presenter;
} }
Bundle onSaveInstanceState() { Bundle onSaveInstanceState() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
// getPresenter(); // Workaround a crash related to saving instance state with child routers // getPresenter(); // Workaround a crash related to saving instance state with child routers
if (presenter != null) { if (presenter != null) {
presenter.save(bundle); presenter.save(bundle);
} }
return bundle; return bundle;
} }
void onRestoreInstanceState(Bundle presenterState) { void onRestoreInstanceState(Bundle presenterState) {
bundle = presenterState; bundle = presenterState;
} }
void onTakeView(Object view) { void onTakeView(Object view) {
getPresenter(); getPresenter();
if (presenter != null) { if (presenter != null) {
//noinspection unchecked //noinspection unchecked
presenter.takeView(view); presenter.takeView(view);
} }
} }
void onDropView() { void onDropView() {
if (presenter != null) { if (presenter != null) {
presenter.dropView(); presenter.dropView();
} }
} }
void onDestroy() { void onDestroy() {
if (presenter != null) { if (presenter != null) {
presenter.destroy(); presenter.destroy();
} }
} }
} }

View File

@ -1,44 +1,45 @@
package eu.kanade.tachiyomi.ui.base.presenter; package eu.kanade.tachiyomi.ui.base.presenter;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.view.View;
import android.view.View;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.Controller;
import androidx.annotation.NonNull;
public class NucleusConductorLifecycleListener extends Controller.LifecycleListener {
public class NucleusConductorLifecycleListener extends Controller.LifecycleListener {
private static final String PRESENTER_STATE_KEY = "presenter_state";
private static final String PRESENTER_STATE_KEY = "presenter_state";
private NucleusConductorDelegate delegate;
private NucleusConductorDelegate delegate;
public NucleusConductorLifecycleListener(NucleusConductorDelegate delegate) {
this.delegate = delegate; public NucleusConductorLifecycleListener(NucleusConductorDelegate delegate) {
} this.delegate = delegate;
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) { @Override
delegate.onTakeView(controller); public void postCreateView(@NonNull Controller controller, @NonNull View view) {
} delegate.onTakeView(controller);
}
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) { @Override
delegate.onDropView(); public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
} delegate.onDropView();
}
@Override
public void preDestroy(@NonNull Controller controller) { @Override
delegate.onDestroy(); public void preDestroy(@NonNull Controller controller) {
} delegate.onDestroy();
}
@Override
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) { @Override
outState.putBundle(PRESENTER_STATE_KEY, delegate.onSaveInstanceState()); public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) {
} outState.putBundle(PRESENTER_STATE_KEY, delegate.onSaveInstanceState());
}
@Override
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) { @Override
delegate.onRestoreInstanceState(savedInstanceState.getBundle(PRESENTER_STATE_KEY)); public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) {
} delegate.onRestoreInstanceState(savedInstanceState.getBundle(PRESENTER_STATE_KEY));
}
}
}

View File

@ -3,9 +3,8 @@ package eu.kanade.tachiyomi.ui.catalogue
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import androidx.appcompat.widget.SearchView
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
@ -111,7 +110,7 @@ class CatalogueController(bundle: Bundle? = null) : NucleusController<CatalogueP
adapter = CatalogueAdapter(this) adapter = CatalogueAdapter(this)
// Create recycler and set adapter. // Create recycler and set adapter.
recycler.layoutManager = LinearLayoutManager(view.context) recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
recycler.adapter = adapter recycler.adapter = adapter
recycler.addItemDecoration(SourceDividerItemDecoration(view.context)) recycler.addItemDecoration(SourceDividerItemDecoration(view.context))

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.AbstractHeaderItem
@ -24,14 +23,14 @@ data class LangItem(val code: String) : AbstractHeaderItem<LangHolder>() {
/** /**
* Creates a new view holder for this item. * Creates a new view holder for this item.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LangHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): LangHolder {
return LangHolder(view, adapter) return LangHolder(view, adapter)
} }
/** /**
* Binds this item to the given view holder. * Binds this item to the given view holder.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: LangHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: LangHolder,
position: Int, payloads: List<Any?>?) { position: Int, payloads: List<Any?>?) {
holder.bind(this) holder.bind(this)

View File

@ -4,10 +4,9 @@ import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() { class SourceDividerItemDecoration(context: Context) : androidx.recyclerview.widget.RecyclerView.ItemDecoration() {
private val divider: Drawable private val divider: Drawable
@ -17,14 +16,14 @@ class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoratio
a.recycle() a.recycle()
} }
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { override fun onDraw(c: Canvas, parent: androidx.recyclerview.widget.RecyclerView, state: androidx.recyclerview.widget.RecyclerView.State) {
val childCount = parent.childCount val childCount = parent.childCount
for (i in 0 until childCount - 1) { for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i) val child = parent.getChildAt(i)
val holder = parent.getChildViewHolder(child) val holder = parent.getChildViewHolder(child)
if (holder is SourceHolder && if (holder is SourceHolder &&
parent.getChildViewHolder(parent.getChildAt(i + 1)) is SourceHolder) { parent.getChildViewHolder(parent.getChildAt(i + 1)) is SourceHolder) {
val params = child.layoutParams as RecyclerView.LayoutParams val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight val bottom = top + divider.intrinsicHeight
val left = parent.paddingLeft + holder.margin val left = parent.paddingLeft + holder.margin
@ -36,8 +35,8 @@ class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoratio
} }
} }
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, override fun getItemOffsets(outRect: Rect, view: View, parent: androidx.recyclerview.widget.RecyclerView,
state: RecyclerView.State) { state: androidx.recyclerview.widget.RecyclerView.State) {
outRect.set(0, 0, 0, divider.intrinsicHeight) outRect.set(0, 0, 0, divider.intrinsicHeight)
} }

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.AbstractSectionableItem
@ -27,14 +26,14 @@ data class SourceItem(val source: CatalogueSource, val header: LangItem? = null,
/** /**
* Creates a new view holder for this item. * Creates a new view holder for this item.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): SourceHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): SourceHolder {
return SourceHolder(view, adapter as CatalogueAdapter, showButtons) return SourceHolder(view, adapter as CatalogueAdapter, showButtons)
} }
/** /**
* Binds this item to the given view holder. * Binds this item to the given view holder.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: SourceHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: SourceHolder,
position: Int, payloads: List<Any?>?) { position: Int, payloads: List<Any?>?) {
holder.bind(this) holder.bind(this)

View File

@ -2,13 +2,15 @@ package eu.kanade.tachiyomi.ui.catalogue.browse
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v4.widget.DrawerLayout
import android.support.v7.widget.*
import android.view.* import android.view.*
import androidx.appcompat.widget.SearchView
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.elvishew.xlog.XLog import com.elvishew.xlog.XLog
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -35,7 +37,6 @@ import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.subscriptions.Subscriptions import rx.subscriptions.Subscriptions
import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -85,7 +86,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
/** /**
* Recycler view with the list of results. * Recycler view with the list of results.
*/ */
private var recycler: RecyclerView? = null private var recycler: androidx.recyclerview.widget.RecyclerView? = null
/** /**
* Subscription for the search view. * Subscription for the search view.
@ -142,13 +143,13 @@ open class BrowseCatalogueController(bundle: Bundle) :
super.onDestroyView(view) super.onDestroyView(view)
} }
override fun createSecondaryDrawer(drawer: DrawerLayout): ViewGroup? { override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup? {
// Inflate and prepare drawer // Inflate and prepare drawer
val navView = drawer.inflate(R.layout.catalogue_drawer) as CatalogueNavigationView val navView = drawer.inflate(R.layout.catalogue_drawer) as CatalogueNavigationView //TODO whatever this is
this.navView = navView this.navView = navView
navView.setFilters(presenter.filterItems) navView.setFilters(presenter.filterItems)
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.END) drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
// EXH --> // EXH -->
navView.setSavedSearches(presenter.loadSearches()) navView.setSavedSearches(presenter.loadSearches())
@ -196,7 +197,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
showProgressBar() showProgressBar()
adapter?.clear() adapter?.clear()
drawer.closeDrawer(Gravity.END) drawer.closeDrawer(GravityCompat.END)
presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters) presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters)
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
} }
@ -238,7 +239,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
val allDefault = presenter.sourceFilters == presenter.source.getFilterList() val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
showProgressBar() showProgressBar()
adapter?.clear() adapter?.clear()
drawer.closeDrawer(Gravity.END) drawer.closeDrawer(GravityCompat.END)
presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters) presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters)
} }
@ -248,31 +249,31 @@ open class BrowseCatalogueController(bundle: Bundle) :
presenter.sourceFilters = newFilters presenter.sourceFilters = newFilters
navView.setFilters(presenter.filterItems) navView.setFilters(presenter.filterItems)
} }
return navView return navView as ViewGroup //TODO fix this bullshit
} }
override fun cleanupSecondaryDrawer(drawer: DrawerLayout) { override fun cleanupSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout) {
navView = null navView = null
} }
private fun setupRecycler(view: View) { private fun setupRecycler(view: View) {
numColumnsSubscription?.unsubscribe() numColumnsSubscription?.unsubscribe()
var oldPosition = RecyclerView.NO_POSITION var oldPosition = androidx.recyclerview.widget.RecyclerView.NO_POSITION
val oldRecycler = catalogue_view?.getChildAt(1) val oldRecycler = catalogue_view?.getChildAt(1)
if (oldRecycler is RecyclerView) { if (oldRecycler is androidx.recyclerview.widget.RecyclerView) {
oldPosition = (oldRecycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() oldPosition = (oldRecycler.layoutManager as androidx.recyclerview.widget.LinearLayoutManager).findFirstVisibleItemPosition()
oldRecycler.adapter = null oldRecycler.adapter = null
catalogue_view?.removeView(oldRecycler) catalogue_view?.removeView(oldRecycler)
} }
val recycler = if (presenter.isListMode) { val recycler = if (presenter.isListMode) {
RecyclerView(view.context).apply { androidx.recyclerview.widget.RecyclerView(view.context).apply {
id = R.id.recycler id = R.id.recycler
layoutManager = LinearLayoutManager(context) layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) layoutParams = androidx.recyclerview.widget.RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(context, androidx.recyclerview.widget.DividerItemDecoration.VERTICAL))
} }
} else { } else {
(catalogue_view.inflate(R.layout.catalogue_recycler_autofit) as AutofitRecyclerView).apply { (catalogue_view.inflate(R.layout.catalogue_recycler_autofit) as AutofitRecyclerView).apply {
@ -282,7 +283,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
// Set again the adapter to recalculate the covers height // Set again the adapter to recalculate the covers height
.subscribe { adapter = this@BrowseCatalogueController.adapter } .subscribe { adapter = this@BrowseCatalogueController.adapter }
(layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { (layoutManager as androidx.recyclerview.widget.GridLayoutManager).spanSizeLookup = object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
return when (adapter?.getItemViewType(position)) { return when (adapter?.getItemViewType(position)) {
R.layout.catalogue_grid_item, null -> 1 R.layout.catalogue_grid_item, null -> 1
@ -297,7 +298,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
catalogue_view.addView(recycler, 1) catalogue_view.addView(recycler, 1)
if (oldPosition != RecyclerView.NO_POSITION) { if (oldPosition != androidx.recyclerview.widget.RecyclerView.NO_POSITION) {
recycler.layoutManager?.scrollToPosition(oldPosition) recycler.layoutManager?.scrollToPosition(oldPosition)
} }
this.recycler = recycler this.recycler = recycler
@ -369,7 +370,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_display_mode -> swapDisplayMode() R.id.action_display_mode -> swapDisplayMode()
R.id.action_set_filter -> navView?.let { activity?.drawer?.openDrawer(Gravity.END) } R.id.action_set_filter -> navView?.let { activity?.drawer?.openDrawer(GravityCompat.END) }
R.id.action_open_in_browser -> openInBrowser() R.id.action_open_in_browser -> openInBrowser()
R.id.action_open_in_web_view -> openInWebView() R.id.action_open_in_web_view -> openInWebView()
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.catalogue.browse package eu.kanade.tachiyomi.ui.catalogue.browse
import android.support.v7.widget.RecyclerView
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@ -25,7 +24,7 @@ class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference<Bo
R.layout.catalogue_grid_item R.layout.catalogue_grid_item
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): CatalogueHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): CatalogueHolder {
val parent = adapter.recyclerView val parent = adapter.recyclerView
return if (parent is AutofitRecyclerView) { return if (parent is AutofitRecyclerView) {
view.apply { view.apply {
@ -40,7 +39,7 @@ class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference<Bo
} }
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
holder: CatalogueHolder, holder: CatalogueHolder,
position: Int, position: Int,
payloads: List<Any?>?) { payloads: List<Any?>?) {

View File

@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.catalogue.browse
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
@ -10,13 +12,12 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.dpToPx import eu.kanade.tachiyomi.util.dpToPx
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.SimpleNavigationView import eu.kanade.tachiyomi.widget.SimpleNavigationView
import kotlinx.android.synthetic.main.catalogue_drawer_content.view.*
import android.util.TypedValue
import android.view.View
import exh.EXHSavedSearch import exh.EXHSavedSearch
import kotlinx.android.synthetic.main.catalogue_drawer_content.view.*
class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
: SimpleNavigationView(context, attrs) { : SimpleNavigationView(context, attrs) {
@ -44,10 +45,10 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs:
init { init {
recycler.adapter = adapter recycler.adapter = adapter
recycler.setHasFixedSize(true) recycler.setHasFixedSize(true)
val view = inflate(eu.kanade.tachiyomi.R.layout.catalogue_drawer_content) val view = inflate(R.layout.catalogue_drawer_content)
((view as ViewGroup).getChildAt(1) as ViewGroup).addView(recycler) ((view as ViewGroup).getChildAt(1) as ViewGroup).addView(recycler)
addView(view) addView(view)
title.text = context?.getString(eu.kanade.tachiyomi.R.string.source_search_options) title.text = context.getString(R.string.source_search_options)
save_search_btn.setOnClickListener { onSaveClicked() } save_search_btn.setOnClickListener { onSaveClicked() }
search_btn.setOnClickListener { onSearchClicked() } search_btn.setOnClickListener { onSearchClicked() }
reset_btn.setOnClickListener { onResetClicked() } reset_btn.setOnClickListener { onResetClicked() }

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.catalogue.browse package eu.kanade.tachiyomi.ui.catalogue.browse
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
@ -19,11 +18,11 @@ class ProgressItem : AbstractFlexibleItem<ProgressItem.Holder>() {
return R.layout.catalogue_progress_item return R.layout.catalogue_progress_item
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>) {
holder.progressBar.visibility = View.GONE holder.progressBar.visibility = View.GONE
holder.progressMessage.visibility = View.GONE holder.progressMessage.visibility = View.GONE

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.CheckBox import android.widget.CheckBox
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -16,11 +15,11 @@ open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem<Chec
return R.layout.navigation_view_checkbox return R.layout.navigation_view_checkbox
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val view = holder.check val view = holder.check
view.text = filter.name view.text = filter.name
view.isChecked = filter.state view.isChecked = filter.state

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
@ -33,11 +32,11 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<Grou
return 101 return 101
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
holder.title.text = filter.name holder.title.text = filter.name
holder.icon.setVectorCompat(if (isExpanded) holder.icon.setVectorCompat(if (isExpanded)

View File

@ -1,10 +1,9 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.support.design.R
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import com.google.android.material.R
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -18,11 +17,11 @@ class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem<HeaderItem.Hold
return R.layout.design_navigation_item_subheader return R.layout.design_navigation_item_subheader
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val view = holder.itemView as TextView val view = holder.itemView as TextView
view.text = filter.name view.text = filter.name
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
@ -23,11 +22,11 @@ class HelpDialogItem(val filter: Filter.HelpDialog) : AbstractHeaderItem<HelpDia
return R.layout.navigation_view_help_dialog return R.layout.navigation_view_help_dialog
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val view = holder.button as TextView val view = holder.button as TextView
view.text = filter.name view.text = filter.name
view.setOnClickListener { view.setOnClickListener {

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Spinner import android.widget.Spinner
@ -19,11 +18,11 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<Selec
return R.layout.navigation_view_spinner return R.layout.navigation_view_spinner
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
holder.text.text = filter.name + ": " holder.text.text = filter.name + ": "
val spinner = holder.spinner val spinner = holder.spinner

View File

@ -1,9 +1,8 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.support.design.R
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import com.google.android.material.R
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -17,11 +16,11 @@ class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem<Separator
return R.layout.design_navigation_item_separator return R.layout.design_navigation_item_separator
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
} }

View File

@ -1,54 +1,53 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.v7.widget.RecyclerView import android.view.View
import android.view.View import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.ISectionable
import eu.davidea.flexibleadapter.items.ISectionable import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.setVectorCompat
import eu.kanade.tachiyomi.util.setVectorCompat
class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() {
class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() {
init {
init { isExpanded = false
isExpanded = false }
}
override fun getLayoutRes(): Int {
override fun getLayoutRes(): Int { return R.layout.navigation_view_group
return R.layout.navigation_view_group }
}
override fun getItemViewType(): Int {
override fun getItemViewType(): Int { return 100
return 100 }
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { return Holder(view, adapter)
return Holder(view, adapter) }
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { holder.title.text = filter.name
holder.title.text = filter.name
holder.icon.setVectorCompat(if (isExpanded)
holder.icon.setVectorCompat(if (isExpanded) R.drawable.ic_expand_more_white_24dp
R.drawable.ic_expand_more_white_24dp else
else R.drawable.ic_chevron_right_white_24dp)
R.drawable.ic_chevron_right_white_24dp)
holder.itemView.setOnClickListener(holder)
holder.itemView.setOnClickListener(holder)
}
}
override fun equals(other: Any?): Boolean {
override fun equals(other: Any?): Boolean { if (this === other) return true
if (this === other) return true if (javaClass != other?.javaClass) return false
if (javaClass != other?.javaClass) return false return filter == (other as SortGroup).filter
return filter == (other as SortGroup).filter }
}
override fun hashCode(): Int {
override fun hashCode(): Int { return filter.hashCode()
return filter.hashCode() }
}
class Holder(view: View, adapter: FlexibleAdapter<*>) : GroupItem.Holder(view, adapter)
class Holder(view: View, adapter: FlexibleAdapter<*>) : GroupItem.Holder(view, adapter)
} }

View File

@ -1,10 +1,9 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.graphics.drawable.VectorDrawableCompat
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.CheckedTextView import android.widget.CheckedTextView
import androidx.core.content.ContextCompat
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -23,11 +22,11 @@ class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem
return 102 return 102
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val view = holder.text val view = holder.text
view.text = name view.text = name
val filter = group.filter val filter = group.filter

View File

@ -1,9 +1,8 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.design.widget.TextInputLayout
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import com.google.android.material.textfield.TextInputLayout
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -23,17 +22,17 @@ open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Hol
return R.layout.navigation_view_text return R.layout.navigation_view_text
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
holder.wrapper.hint = filter.name holder.wrapper.hint = filter.name
holder.edit.setText(filter.state) holder.edit.setText(filter.state)
holder.edit.addTextChangedListener(textWatcher) holder.edit.addTextChangedListener(textWatcher)
} }
override fun unbindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int) { override fun unbindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int) {
holder.edit.removeTextChangedListener(textWatcher) holder.edit.removeTextChangedListener(textWatcher)
} }

View File

@ -1,10 +1,9 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.design.R
import android.support.graphics.drawable.VectorDrawableCompat
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.widget.CheckedTextView import android.widget.CheckedTextView
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import com.google.android.material.R
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -24,11 +23,11 @@ open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriS
return 103 return 103
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return Holder(view, adapter) return Holder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val view = holder.text val view = holder.text
view.text = filter.name view.text = filter.name

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.catalogue.global_search
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.support.v7.widget.RecyclerView
import android.util.SparseArray import android.util.SparseArray
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
@ -25,12 +24,12 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) :
*/ */
private var bundle = Bundle() private var bundle = Bundle()
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any?>) { override fun onBindViewHolder(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, position: Int, payloads: List<Any?>) {
super.onBindViewHolder(holder, position, payloads) super.onBindViewHolder(holder, position, payloads)
restoreHolderState(holder) restoreHolderState(holder)
} }
override fun onViewRecycled(holder: RecyclerView.ViewHolder) { override fun onViewRecycled(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder) {
super.onViewRecycled(holder) super.onViewRecycled(holder)
saveHolderState(holder, bundle) saveHolderState(holder, bundle)
} }
@ -53,7 +52,7 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) :
* @param holder The holder to save. * @param holder The holder to save.
* @param outState The bundle where the state is saved. * @param outState The bundle where the state is saved.
*/ */
private fun saveHolderState(holder: RecyclerView.ViewHolder, outState: Bundle) { private fun saveHolderState(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, outState: Bundle) {
val key = "holder_${holder.adapterPosition}" val key = "holder_${holder.adapterPosition}"
val holderState = SparseArray<Parcelable>() val holderState = SparseArray<Parcelable>()
holder.itemView.saveHierarchyState(holderState) holder.itemView.saveHierarchyState(holderState)
@ -65,7 +64,7 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) :
* *
* @param holder The holder to restore. * @param holder The holder to restore.
*/ */
private fun restoreHolderState(holder: RecyclerView.ViewHolder) { private fun restoreHolderState(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder) {
val key = "holder_${holder.adapterPosition}" val key = "holder_${holder.adapterPosition}"
val holderState = bundle.getSparseParcelableArray<Parcelable>(key) val holderState = bundle.getSparseParcelableArray<Parcelable>(key)
if (holderState != null) { if (holderState != null) {

View File

@ -1,37 +1,36 @@
package eu.kanade.tachiyomi.ui.catalogue.global_search package eu.kanade.tachiyomi.ui.catalogue.global_search
import android.support.v7.widget.RecyclerView import android.view.View
import android.view.View import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Manga
class CatalogueSearchCardItem(val manga: Manga) : AbstractFlexibleItem<CatalogueSearchCardHolder>() {
class CatalogueSearchCardItem(val manga: Manga) : AbstractFlexibleItem<CatalogueSearchCardHolder>() {
override fun getLayoutRes(): Int {
override fun getLayoutRes(): Int { return R.layout.catalogue_global_search_controller_card_item
return R.layout.catalogue_global_search_controller_card_item }
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): CatalogueSearchCardHolder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): CatalogueSearchCardHolder { return CatalogueSearchCardHolder(view, adapter as CatalogueSearchCardAdapter)
return CatalogueSearchCardHolder(view, adapter as CatalogueSearchCardAdapter) }
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: CatalogueSearchCardHolder,
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: CatalogueSearchCardHolder, position: Int, payloads: List<Any?>?) {
position: Int, payloads: List<Any?>?) { holder.bind(manga)
holder.bind(manga) }
}
override fun equals(other: Any?): Boolean {
override fun equals(other: Any?): Boolean { if (other is CatalogueSearchCardItem) {
if (other is CatalogueSearchCardItem) { return manga.id == other.manga.id
return manga.id == other.manga.id }
} return false
return false }
}
override fun hashCode(): Int {
override fun hashCode(): Int { return manga.id?.toInt() ?: 0
return manga.id?.toInt() ?: 0 }
}
} }

View File

@ -1,9 +1,8 @@
package eu.kanade.tachiyomi.ui.catalogue.global_search package eu.kanade.tachiyomi.ui.catalogue.global_search
import android.os.Bundle import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import androidx.appcompat.widget.SearchView
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -139,7 +138,7 @@ open class CatalogueSearchController(
adapter = CatalogueSearchAdapter(this) adapter = CatalogueSearchAdapter(this)
// Create recycler and set adapter. // Create recycler and set adapter.
recycler.layoutManager = LinearLayoutManager(view.context) recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
recycler.adapter = adapter recycler.adapter = adapter
} }

View File

@ -1,16 +1,12 @@
package eu.kanade.tachiyomi.ui.catalogue.global_search package eu.kanade.tachiyomi.ui.catalogue.global_search
import android.support.v7.widget.LinearLayoutManager
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.gone import eu.kanade.tachiyomi.util.gone
import eu.kanade.tachiyomi.util.setVectorCompat
import eu.kanade.tachiyomi.util.visible import eu.kanade.tachiyomi.util.visible
import kotlinx.android.synthetic.main.catalogue_global_search_controller_card.* import kotlinx.android.synthetic.main.catalogue_global_search_controller_card.*
import kotlinx.android.synthetic.main.catalogue_global_search_controller_card.view.*
/** /**
* Holder that binds the [CatalogueSearchItem] containing catalogue cards. * Holder that binds the [CatalogueSearchItem] containing catalogue cards.
@ -30,7 +26,7 @@ class CatalogueSearchHolder(view: View, val adapter: CatalogueSearchAdapter) :
init { init {
// Set layout horizontal. // Set layout horizontal.
recycler.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.HORIZONTAL, false) recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, false)
recycler.adapter = mangaAdapter recycler.adapter = mangaAdapter
more.setOnClickListener { more.setOnClickListener {

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.catalogue.global_search package eu.kanade.tachiyomi.ui.catalogue.global_search
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -32,14 +31,14 @@ class CatalogueSearchItem(val source: CatalogueSource, val results: List<Catalog
* *
* @return holder of view. * @return holder of view.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): CatalogueSearchHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): CatalogueSearchHolder {
return CatalogueSearchHolder(view, adapter as CatalogueSearchAdapter) return CatalogueSearchHolder(view, adapter as CatalogueSearchAdapter)
} }
/** /**
* Bind item to view. * Bind item to view.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: CatalogueSearchHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: CatalogueSearchHolder,
position: Int, payloads: List<Any?>?) { position: Int, payloads: List<Any?>?) {
holder.bind(this) holder.bind(this)
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.catalogue.latest package eu.kanade.tachiyomi.ui.catalogue.latest
import android.os.Bundle import android.os.Bundle
import android.support.v4.widget.DrawerLayout
import android.view.Menu import android.view.Menu
import android.view.ViewGroup import android.view.ViewGroup
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -28,11 +27,11 @@ class LatestUpdatesController(bundle: Bundle) : BrowseCatalogueController(bundle
menu.findItem(R.id.action_set_filter).isVisible = false menu.findItem(R.id.action_set_filter).isVisible = false
} }
override fun createSecondaryDrawer(drawer: DrawerLayout): ViewGroup? { override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup? {
return null return null
} }
override fun cleanupSecondaryDrawer(drawer: DrawerLayout) { override fun cleanupSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout) {
} }

View File

@ -1,11 +1,10 @@
package eu.kanade.tachiyomi.ui.category package eu.kanade.tachiyomi.ui.category
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.view.ActionMode
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.* import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding.view.clicks import com.jakewharton.rxbinding.view.clicks
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
@ -74,7 +73,7 @@ class CategoryController : NucleusController<CategoryPresenter>(),
super.onViewCreated(view) super.onViewCreated(view)
adapter = CategoryAdapter(this@CategoryController) adapter = CategoryAdapter(this@CategoryController)
recycler.layoutManager = LinearLayoutManager(view.context) recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
recycler.setHasFixedSize(true) recycler.setHasFixedSize(true)
recycler.adapter = adapter recycler.adapter = adapter
adapter?.isHandleDragEnabled = true adapter?.isHandleDragEnabled = true
@ -207,7 +206,7 @@ class CategoryController : NucleusController<CategoryPresenter>(),
*/ */
override fun onItemClick(view: View, position: Int): Boolean { override fun onItemClick(view: View, position: Int): Boolean {
// Check if action mode is initialized and selected item exist. // Check if action mode is initialized and selected item exist.
if (actionMode != null && position != RecyclerView.NO_POSITION) { if (actionMode != null && position != androidx.recyclerview.widget.RecyclerView.NO_POSITION) {
toggleSelection(position) toggleSelection(position)
return true return true
} else { } else {

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.category package eu.kanade.tachiyomi.ui.category
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -31,7 +30,7 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
* @param view The view of this item. * @param view The view of this item.
* @param adapter The adapter of this item. * @param adapter The adapter of this item.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): CategoryHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): CategoryHolder {
return CategoryHolder(view, adapter as CategoryAdapter) return CategoryHolder(view, adapter as CategoryAdapter)
} }
@ -43,7 +42,7 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
* @param position The position of this item in the adapter. * @param position The position of this item in the adapter.
* @param payloads List of partial changes. * @param payloads List of partial changes.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
holder: CategoryHolder, holder: CategoryHolder,
position: Int, position: Int,
payloads: List<Any?>?) { payloads: List<Any?>?) {

View File

@ -1,260 +1,259 @@
package eu.kanade.tachiyomi.ui.download package eu.kanade.tachiyomi.ui.download
import android.support.v7.widget.LinearLayoutManager import android.view.*
import android.view.* import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import kotlinx.android.synthetic.main.download_controller.*
import kotlinx.android.synthetic.main.download_controller.* import rx.Observable
import rx.Observable import rx.Subscription
import rx.Subscription import rx.android.schedulers.AndroidSchedulers
import rx.android.schedulers.AndroidSchedulers import java.util.*
import java.util.* import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit
/**
/** * Controller that shows the currently active downloads.
* Controller that shows the currently active downloads. * Uses R.layout.fragment_download_queue.
* Uses R.layout.fragment_download_queue. */
*/ class DownloadController : NucleusController<DownloadPresenter>(),
class DownloadController : NucleusController<DownloadPresenter>(), DownloadAdapter.OnItemReleaseListener {
DownloadAdapter.OnItemReleaseListener {
/**
/** * Adapter containing the active downloads.
* Adapter containing the active downloads. */
*/ private var adapter: DownloadAdapter? = null
private var adapter: DownloadAdapter? = null
/**
/** * Map of subscriptions for active downloads.
* Map of subscriptions for active downloads. */
*/ private val progressSubscriptions by lazy { HashMap<Download, Subscription>() }
private val progressSubscriptions by lazy { HashMap<Download, Subscription>() }
/**
/** * Whether the download queue is running or not.
* Whether the download queue is running or not. */
*/ private var isRunning: Boolean = false
private var isRunning: Boolean = false
init {
init { setHasOptionsMenu(true)
setHasOptionsMenu(true) }
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.download_controller, container, false)
return inflater.inflate(R.layout.download_controller, container, false) }
}
override fun createPresenter(): DownloadPresenter {
override fun createPresenter(): DownloadPresenter { return DownloadPresenter()
return DownloadPresenter() }
}
override fun getTitle(): String? {
override fun getTitle(): String? { return resources?.getString(R.string.label_download_queue)
return resources?.getString(R.string.label_download_queue) }
}
override fun onViewCreated(view: View) {
override fun onViewCreated(view: View) { super.onViewCreated(view)
super.onViewCreated(view)
// Check if download queue is empty and update information accordingly.
// Check if download queue is empty and update information accordingly. setInformationView()
setInformationView()
// Initialize adapter.
// Initialize adapter. adapter = DownloadAdapter(this@DownloadController)
adapter = DownloadAdapter(this@DownloadController) recycler.adapter = adapter
recycler.adapter = adapter adapter?.isHandleDragEnabled = true
adapter?.isHandleDragEnabled = true
// Set the layout manager for the recycler and fixed size.
// Set the layout manager for the recycler and fixed size. recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
recycler.layoutManager = LinearLayoutManager(view.context) recycler.setHasFixedSize(true)
recycler.setHasFixedSize(true)
// Suscribe to changes
// Suscribe to changes DownloadService.runningRelay
DownloadService.runningRelay .observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .subscribeUntilDestroy { onQueueStatusChange(it) }
.subscribeUntilDestroy { onQueueStatusChange(it) }
presenter.getDownloadStatusObservable()
presenter.getDownloadStatusObservable() .observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .subscribeUntilDestroy { onStatusChange(it) }
.subscribeUntilDestroy { onStatusChange(it) }
presenter.getDownloadProgressObservable()
presenter.getDownloadProgressObservable() .observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .subscribeUntilDestroy { onUpdateDownloadedPages(it) }
.subscribeUntilDestroy { onUpdateDownloadedPages(it) } }
}
override fun onDestroyView(view: View) {
override fun onDestroyView(view: View) { for (subscription in progressSubscriptions.values) {
for (subscription in progressSubscriptions.values) { subscription.unsubscribe()
subscription.unsubscribe() }
} progressSubscriptions.clear()
progressSubscriptions.clear() adapter = null
adapter = null super.onDestroyView(view)
super.onDestroyView(view) }
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.download_queue, menu)
inflater.inflate(R.menu.download_queue, menu) }
}
override fun onPrepareOptionsMenu(menu: Menu) {
override fun onPrepareOptionsMenu(menu: Menu) { // Set start button visibility.
// Set start button visibility. menu.findItem(R.id.start_queue).isVisible = !isRunning && !presenter.downloadQueue.isEmpty()
menu.findItem(R.id.start_queue).isVisible = !isRunning && !presenter.downloadQueue.isEmpty()
// Set pause button visibility.
// Set pause button visibility. menu.findItem(R.id.pause_queue).isVisible = isRunning
menu.findItem(R.id.pause_queue).isVisible = isRunning
// Set clear button visibility.
// Set clear button visibility. menu.findItem(R.id.clear_queue).isVisible = !presenter.downloadQueue.isEmpty()
menu.findItem(R.id.clear_queue).isVisible = !presenter.downloadQueue.isEmpty() }
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
override fun onOptionsItemSelected(item: MenuItem): Boolean { val context = applicationContext ?: return false
val context = applicationContext ?: return false when (item.itemId) {
when (item.itemId) { R.id.start_queue -> DownloadService.start(context)
R.id.start_queue -> DownloadService.start(context) R.id.pause_queue -> {
R.id.pause_queue -> { DownloadService.stop(context)
DownloadService.stop(context) presenter.pauseDownloads()
presenter.pauseDownloads() }
} R.id.clear_queue -> {
R.id.clear_queue -> { DownloadService.stop(context)
DownloadService.stop(context) presenter.clearQueue()
presenter.clearQueue() }
} else -> return super.onOptionsItemSelected(item)
else -> return super.onOptionsItemSelected(item) }
} return true
return true }
}
/**
/** * Called when the status of a download changes.
* Called when the status of a download changes. *
* * @param download the download whose status has changed.
* @param download the download whose status has changed. */
*/ private fun onStatusChange(download: Download) {
private fun onStatusChange(download: Download) { when (download.status) {
when (download.status) { Download.DOWNLOADING -> {
Download.DOWNLOADING -> { observeProgress(download)
observeProgress(download) // Initial update of the downloaded pages
// Initial update of the downloaded pages onUpdateDownloadedPages(download)
onUpdateDownloadedPages(download) }
} Download.DOWNLOADED -> {
Download.DOWNLOADED -> { unsubscribeProgress(download)
unsubscribeProgress(download) onUpdateProgress(download)
onUpdateProgress(download) onUpdateDownloadedPages(download)
onUpdateDownloadedPages(download) }
} Download.ERROR -> unsubscribeProgress(download)
Download.ERROR -> unsubscribeProgress(download) }
} }
}
/**
/** * Observe the progress of a download and notify the view.
* Observe the progress of a download and notify the view. *
* * @param download the download to observe its progress.
* @param download the download to observe its progress. */
*/ private fun observeProgress(download: Download) {
private fun observeProgress(download: Download) { val subscription = Observable.interval(50, TimeUnit.MILLISECONDS)
val subscription = Observable.interval(50, TimeUnit.MILLISECONDS) // Get the sum of percentages for all the pages.
// Get the sum of percentages for all the pages. .flatMap {
.flatMap { Observable.from(download.pages)
Observable.from(download.pages) .map(Page::progress)
.map(Page::progress) .reduce { x, y -> x + y }
.reduce { x, y -> x + y } }
} // Keep only the latest emission to avoid backpressure.
// Keep only the latest emission to avoid backpressure. .onBackpressureLatest()
.onBackpressureLatest() .observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .subscribe { progress ->
.subscribe { progress -> // Update the view only if the progress has changed.
// Update the view only if the progress has changed. if (download.totalProgress != progress) {
if (download.totalProgress != progress) { download.totalProgress = progress
download.totalProgress = progress onUpdateProgress(download)
onUpdateProgress(download) }
} }
}
// Avoid leaking subscriptions
// Avoid leaking subscriptions progressSubscriptions.remove(download)?.unsubscribe()
progressSubscriptions.remove(download)?.unsubscribe()
progressSubscriptions[download] = subscription
progressSubscriptions[download] = subscription }
}
/**
/** * Unsubscribes the given download from the progress subscriptions.
* Unsubscribes the given download from the progress subscriptions. *
* * @param download the download to unsubscribe.
* @param download the download to unsubscribe. */
*/ private fun unsubscribeProgress(download: Download) {
private fun unsubscribeProgress(download: Download) { progressSubscriptions.remove(download)?.unsubscribe()
progressSubscriptions.remove(download)?.unsubscribe() }
}
/**
/** * Called when the queue's status has changed. Updates the visibility of the buttons.
* Called when the queue's status has changed. Updates the visibility of the buttons. *
* * @param running whether the queue is now running or not.
* @param running whether the queue is now running or not. */
*/ private fun onQueueStatusChange(running: Boolean) {
private fun onQueueStatusChange(running: Boolean) { isRunning = running
isRunning = running activity?.invalidateOptionsMenu()
activity?.invalidateOptionsMenu()
// Check if download queue is empty and update information accordingly.
// Check if download queue is empty and update information accordingly. setInformationView()
setInformationView() }
}
/**
/** * Called from the presenter to assign the downloads for the adapter.
* Called from the presenter to assign the downloads for the adapter. *
* * @param downloads the downloads from the queue.
* @param downloads the downloads from the queue. */
*/ fun onNextDownloads(downloads: List<DownloadItem>) {
fun onNextDownloads(downloads: List<DownloadItem>) { activity?.invalidateOptionsMenu()
activity?.invalidateOptionsMenu() setInformationView()
setInformationView() adapter?.updateDataSet(downloads)
adapter?.updateDataSet(downloads) }
}
/**
/** * Called when the progress of a download changes.
* Called when the progress of a download changes. *
* * @param download the download whose progress has changed.
* @param download the download whose progress has changed. */
*/ fun onUpdateProgress(download: Download) {
fun onUpdateProgress(download: Download) { getHolder(download)?.notifyProgress()
getHolder(download)?.notifyProgress() }
}
/**
/** * Called when a page of a download is downloaded.
* Called when a page of a download is downloaded. *
* * @param download the download whose page has been downloaded.
* @param download the download whose page has been downloaded. */
*/ fun onUpdateDownloadedPages(download: Download) {
fun onUpdateDownloadedPages(download: Download) { getHolder(download)?.notifyDownloadedPages()
getHolder(download)?.notifyDownloadedPages() }
}
/**
/** * Returns the holder for the given download.
* Returns the holder for the given download. *
* * @param download the download to find.
* @param download the download to find. * @return the holder of the download or null if it's not bound.
* @return the holder of the download or null if it's not bound. */
*/ private fun getHolder(download: Download): DownloadHolder? {
private fun getHolder(download: Download): DownloadHolder? { return recycler?.findViewHolderForItemId(download.chapter.id!!) as? DownloadHolder
return recycler?.findViewHolderForItemId(download.chapter.id!!) as? DownloadHolder }
}
/**
/** * Set information view when queue is empty
* Set information view when queue is empty */
*/ private fun setInformationView() {
private fun setInformationView() { if (presenter.downloadQueue.isEmpty()) {
if (presenter.downloadQueue.isEmpty()) { empty_view?.show(R.drawable.ic_file_download_black_128dp,
empty_view?.show(R.drawable.ic_file_download_black_128dp, R.string.information_no_downloads)
R.string.information_no_downloads) } else {
} else { empty_view?.hide()
empty_view?.hide() }
} }
}
/**
/** * Called when an item is released from a drag.
* Called when an item is released from a drag. *
* * @param position The position of the released item.
* @param position The position of the released item. */
*/ override fun onItemReleased(position: Int) {
override fun onItemReleased(position: Int) { val adapter = adapter ?: return
val adapter = adapter ?: return val downloads = (0 until adapter.itemCount).mapNotNull { adapter.getItem(it)?.download }
val downloads = (0 until adapter.itemCount).mapNotNull { adapter.getItem(it)?.download } presenter.reorder(downloads)
presenter.reorder(downloads) }
}
}
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.download package eu.kanade.tachiyomi.ui.download
import android.view.View import android.view.View
import android.support.v7.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -28,7 +27,7 @@ class DownloadItem(val download: Download) : AbstractFlexibleItem<DownloadHolder
* @param view The view of this item. * @param view The view of this item.
* @param adapter The adapter of this item. * @param adapter The adapter of this item.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView
.ViewHolder>>): DownloadHolder { .ViewHolder>>): DownloadHolder {
return DownloadHolder(view, adapter as DownloadAdapter) return DownloadHolder(view, adapter as DownloadAdapter)
} }
@ -41,8 +40,8 @@ class DownloadItem(val download: Download) : AbstractFlexibleItem<DownloadHolder
* @param position The position of this item in the adapter. * @param position The position of this item in the adapter.
* @param payloads List of partial changes. * @param payloads List of partial changes.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
holder: DownloadHolder, position: Int, payloads: MutableList<Any>) { holder: DownloadHolder, position: Int, payloads: MutableList<Any>) {
holder.bind(download) holder.bind(download)
} }

View File

@ -1,12 +1,7 @@
package eu.kanade.tachiyomi.ui.extension package eu.kanade.tachiyomi.ui.extension
import android.support.v7.widget.LinearLayoutManager import android.view.*
import android.support.v7.widget.SearchView import androidx.appcompat.widget.SearchView
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import com.jakewharton.rxbinding.support.v4.widget.refreshes import com.jakewharton.rxbinding.support.v4.widget.refreshes
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -63,7 +58,7 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
// Initialize adapter, scroll listener and recycler views // Initialize adapter, scroll listener and recycler views
adapter = ExtensionAdapter(this) adapter = ExtensionAdapter(this)
// Create recycler and set adapter. // Create recycler and set adapter.
ext_recycler.layoutManager = LinearLayoutManager(view.context) ext_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
ext_recycler.adapter = adapter ext_recycler.adapter = adapter
ext_recycler.addItemDecoration(ExtensionDividerItemDecoration(view.context)) ext_recycler.addItemDecoration(ExtensionDividerItemDecoration(view.context))
} }

View File

@ -3,16 +3,15 @@ package eu.kanade.tachiyomi.ui.extension
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v7.preference.*
import android.support.v7.preference.internal.AbstractMultiSelectListPreference
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.DividerItemDecoration.VERTICAL
import android.support.v7.widget.LinearLayoutManager
import android.util.TypedValue import android.util.TypedValue
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.preference.*
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager
import com.elvishew.xlog.XLog import com.elvishew.xlog.XLog
import com.jakewharton.rxbinding.view.clicks import com.jakewharton.rxbinding.view.clicks
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -80,7 +79,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
val manager = PreferenceManager(themedContext) val manager = PreferenceManager(themedContext)
manager.preferenceDataStore = EmptyPreferenceDataStore() manager.preferenceDataStore = EmptyPreferenceDataStore()
manager.onDisplayPreferenceDialogListener = this manager.onDisplayPreferenceDialogListener = this
val screen = manager.createPreferenceScreen(themedContext) val screen = manager.createPreferenceScreen(context)
preferenceScreen = screen preferenceScreen = screen
val multiSource = extension.sources.size > 1 val multiSource = extension.sources.size > 1
@ -151,10 +150,12 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
val newScreen = screen.preferenceManager.createPreferenceScreen(context) val newScreen = screen.preferenceManager.createPreferenceScreen(context)
source.setupPreferenceScreen(newScreen) source.setupPreferenceScreen(newScreen)
for (i in 0 until newScreen.preferenceCount) { // Reparent the preferences
val pref = newScreen.getPreference(i) while (newScreen.preferenceCount != 0) {
val pref = newScreen.getPreference(0)
pref.preferenceDataStore = dataStore pref.preferenceDataStore = dataStore
pref.order = Int.MAX_VALUE // reset to default order pref.order = Int.MAX_VALUE // reset to default order
newScreen.removePreference(pref)
screen.addPreference(pref) screen.addPreference(pref)
} }
} }
@ -180,7 +181,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
.newInstance(preference.getKey()) .newInstance(preference.getKey())
is ListPreference -> ListPreferenceDialogController is ListPreference -> ListPreferenceDialogController
.newInstance(preference.getKey()) .newInstance(preference.getKey())
is AbstractMultiSelectListPreference -> MultiSelectListPreferenceDialogController is MultiSelectListPreference -> MultiSelectListPreferenceDialogController
.newInstance(preference.getKey()) .newInstance(preference.getKey())
else -> throw IllegalArgumentException("Tried to display dialog for unknown " + else -> throw IllegalArgumentException("Tried to display dialog for unknown " +
"preference type. Did you forget to override onDisplayPreferenceDialog()?") "preference type. Did you forget to override onDisplayPreferenceDialog()?")
@ -189,8 +190,8 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
f.showDialog(router) f.showDialog(router)
} }
override fun findPreference(key: CharSequence?): Preference { override fun <T : Preference> findPreference(key: CharSequence): T? {
return preferenceScreen!!.getPreference(lastOpenPreferencePosition!!) return preferenceScreen!!.findPreference(key)
} }
override fun loginDialogClosed(source: LoginSource) { override fun loginDialogClosed(source: LoginSource) {

View File

@ -4,10 +4,9 @@ import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() { class ExtensionDividerItemDecoration(context: Context) : androidx.recyclerview.widget.RecyclerView.ItemDecoration() {
private val divider: Drawable private val divider: Drawable
@ -17,14 +16,14 @@ class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecora
a.recycle() a.recycle()
} }
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { override fun onDraw(c: Canvas, parent: androidx.recyclerview.widget.RecyclerView, state: androidx.recyclerview.widget.RecyclerView.State) {
val childCount = parent.childCount val childCount = parent.childCount
for (i in 0 until childCount - 1) { for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i) val child = parent.getChildAt(i)
val holder = parent.getChildViewHolder(child) val holder = parent.getChildViewHolder(child)
if (holder is ExtensionHolder && if (holder is ExtensionHolder &&
parent.getChildViewHolder(parent.getChildAt(i + 1)) is ExtensionHolder) { parent.getChildViewHolder(parent.getChildAt(i + 1)) is ExtensionHolder) {
val params = child.layoutParams as RecyclerView.LayoutParams val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight val bottom = top + divider.intrinsicHeight
val left = parent.paddingLeft + holder.margin val left = parent.paddingLeft + holder.margin
@ -36,8 +35,8 @@ class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecora
} }
} }
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, override fun getItemOffsets(outRect: Rect, view: View, parent: androidx.recyclerview.widget.RecyclerView,
state: RecyclerView.State) { state: androidx.recyclerview.widget.RecyclerView.State) {
outRect.set(0, 0, 0, divider.intrinsicHeight) outRect.set(0, 0, 0, divider.intrinsicHeight)
} }

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.extension package eu.kanade.tachiyomi.ui.extension
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.AbstractHeaderItem
@ -25,14 +24,14 @@ data class ExtensionGroupItem(val name: String, val size: Int) : AbstractHeaderI
/** /**
* Creates a new view holder for this item. * Creates a new view holder for this item.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): ExtensionGroupHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): ExtensionGroupHolder {
return ExtensionGroupHolder(view, adapter) return ExtensionGroupHolder(view, adapter)
} }
/** /**
* Binds this item to the given view holder. * Binds this item to the given view holder.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: ExtensionGroupHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: ExtensionGroupHolder,
position: Int, payloads: List<Any?>?) { position: Int, payloads: List<Any?>?) {
holder.bind(this) holder.bind(this)

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.extension package eu.kanade.tachiyomi.ui.extension
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.AbstractSectionableItem
@ -31,14 +30,14 @@ data class ExtensionItem(val extension: Extension,
/** /**
* Creates a new view holder for this item. * Creates a new view holder for this item.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): ExtensionHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): ExtensionHolder {
return ExtensionHolder(view, adapter as ExtensionAdapter) return ExtensionHolder(view, adapter as ExtensionAdapter)
} }
/** /**
* Binds this item to the given view holder. * Binds this item to the given view holder.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: ExtensionHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: ExtensionHolder,
position: Int, payloads: List<Any?>?) { position: Int, payloads: List<Any?>?) {
if (payloads == null || payloads.isEmpty()) { if (payloads == null || payloads.isEmpty()) {

View File

@ -1,8 +1,6 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.content.Context import android.content.Context
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
@ -53,7 +51,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
/** /**
* Recycler view of the list of manga. * Recycler view of the list of manga.
*/ */
private lateinit var recycler: RecyclerView private lateinit var recycler: androidx.recyclerview.widget.RecyclerView
/** /**
* Adapter to hold the manga in this category. * Adapter to hold the manga in this category.
@ -80,8 +78,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
this.controller = controller this.controller = controller
recycler = if (preferences.libraryAsList().getOrDefault()) { recycler = if (preferences.libraryAsList().getOrDefault()) {
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { (swipe_refresh.inflate(R.layout.library_list_recycler) as androidx.recyclerview.widget.RecyclerView).apply {
layoutManager = LinearLayoutManager(context) layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
} }
} else { } else {
(swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { (swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
@ -95,10 +93,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
recycler.adapter = adapter recycler.adapter = adapter
swipe_refresh.addView(recycler) swipe_refresh.addView(recycler)
recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { recycler.addOnScrollListener(object : androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recycler: RecyclerView, newState: Int) { override fun onScrollStateChanged(recycler: androidx.recyclerview.widget.RecyclerView, newState: Int) {
// Disable swipe refresh when view is not at the top // Disable swipe refresh when view is not at the top
val firstPos = (recycler.layoutManager as LinearLayoutManager) val firstPos = (recycler.layoutManager as androidx.recyclerview.widget.LinearLayoutManager)
.findFirstCompletelyVisibleItemPosition() .findFirstCompletelyVisibleItemPosition()
swipe_refresh.isEnabled = firstPos <= 0 swipe_refresh.isEnabled = firstPos <= 0
} }

View File

@ -5,17 +5,17 @@ import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.TabLayout
import android.support.v4.graphics.drawable.DrawableCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.app.AppCompatActivity
import android.support.v7.view.ActionMode
import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.core.graphics.drawable.DrawableCompat
import androidx.drawerlayout.widget.DrawerLayout
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxbinding.support.v4.view.pageSelections import com.jakewharton.rxbinding.support.v4.view.pageSelections
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
@ -120,7 +120,7 @@ class LibraryController(
/** /**
* Drawer listener to allow swipe only for closing the drawer. * Drawer listener to allow swipe only for closing the drawer.
*/ */
private var drawerListener: DrawerLayout.DrawerListener? = null private var drawerListener: androidx.drawerlayout.widget.DrawerLayout.DrawerListener? = null
private var tabsVisibilityRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false) private var tabsVisibilityRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false)
@ -202,10 +202,10 @@ class LibraryController(
super.onDestroyView(view) super.onDestroyView(view)
} }
override fun createSecondaryDrawer(drawer: DrawerLayout): ViewGroup { override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup {
val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
navView = view navView = view
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.END) drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.END)
navView?.onGroupClicked = { group -> navView?.onGroupClicked = { group ->
when (group) { when (group) {
@ -219,7 +219,7 @@ class LibraryController(
return view return view
} }
override fun cleanupSecondaryDrawer(drawer: DrawerLayout) { override fun cleanupSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout) {
navView = null navView = null
} }

View File

@ -1,109 +1,108 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.support.v7.widget.RecyclerView import android.view.Gravity
import android.view.Gravity import android.view.View
import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout
import android.widget.FrameLayout import com.f2prateek.rx.preferences.Preference
import com.f2prateek.rx.preferences.Preference import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.IFilterable
import eu.davidea.flexibleadapter.items.IFilterable import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference<Boolean>) :
class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference<Boolean>) : AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> {
AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> { var downloadCount = -1
var downloadCount = -1
override fun getLayoutRes(): Int {
override fun getLayoutRes(): Int { return if (libraryAsList.getOrDefault())
return if (libraryAsList.getOrDefault()) R.layout.catalogue_list_item
R.layout.catalogue_list_item else
else R.layout.catalogue_grid_item
R.layout.catalogue_grid_item }
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): LibraryHolder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder { val parent = adapter.recyclerView
val parent = adapter.recyclerView return if (parent is AutofitRecyclerView) {
return if (parent is AutofitRecyclerView) { view.apply {
view.apply { val coverHeight = parent.itemWidth / 3 * 4
val coverHeight = parent.itemWidth / 3 * 4 card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) gradient.layoutParams = FrameLayout.LayoutParams(
gradient.layoutParams = FrameLayout.LayoutParams( MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM)
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM) }
} LibraryGridHolder(view, adapter)
LibraryGridHolder(view, adapter) } else {
} else { LibraryListHolder(view, adapter)
LibraryListHolder(view, adapter) }
} }
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: LibraryHolder,
holder: LibraryHolder, position: Int,
position: Int, payloads: List<Any?>?) {
payloads: List<Any?>?) {
holder.onSetValues(this)
holder.onSetValues(this) }
}
/**
/** * Filters a manga depending on a query.
* Filters a manga depending on a query. *
* * @param constraint the query to apply.
* @param constraint the query to apply. * @return true if the manga should be included, false otherwise.
* @return true if the manga should be included, false otherwise. */
*/ 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) || if (constraint.contains(" ") || constraint.contains("\"")) {
if (constraint.contains(" ") || constraint.contains("\"")) { val genres = manga.genre?.split(", ")?.map {
val genres = manga.genre?.split(", ")?.map { it.drop(it.indexOfFirst{it==':'}+1).toLowerCase().trim() //tachiEH tag namespaces
it.drop(it.indexOfFirst{it==':'}+1).toLowerCase().trim() //tachiEH tag namespaces }
} var clean_constraint = ""
var clean_constraint = "" var ignorespace = false
var ignorespace = false for (i in constraint.trim().toLowerCase()) {
for (i in constraint.trim().toLowerCase()) { if (i==' ') {
if (i==' ') { if (!ignorespace) {
if (!ignorespace) { clean_constraint = clean_constraint + ","
clean_constraint = clean_constraint + "," } else {
} else { clean_constraint = clean_constraint + " "
clean_constraint = clean_constraint + " " }
} } else if (i=='"') {
} else if (i=='"') { ignorespace = !ignorespace
ignorespace = !ignorespace } else {
} else { clean_constraint = clean_constraint + Character.toString(i)
clean_constraint = clean_constraint + Character.toString(i) }
} }
} clean_constraint.split(",").all { containsGenre(it.trim(), genres) }
clean_constraint.split(",").all { containsGenre(it.trim(), genres) } }
} else containsGenre(constraint, manga.genre?.split(", ")?.map {
else containsGenre(constraint, manga.genre?.split(", ")?.map { it.drop(it.indexOfFirst{it==':'}+1).toLowerCase().trim() //tachiEH tag namespaces
it.drop(it.indexOfFirst{it==':'}+1).toLowerCase().trim() //tachiEH tag namespaces })
}) }
}
private fun containsGenre(tag: String, genres: List<String>?): Boolean {
private fun containsGenre(tag: String, genres: List<String>?): Boolean { return if (tag.startsWith("-"))
return if (tag.startsWith("-")) genres?.find {
genres?.find { it.trim().toLowerCase() == tag.substringAfter("-").toLowerCase()
it.trim().toLowerCase() == tag.substringAfter("-").toLowerCase() } == null
} == null else
else genres?.find {
genres?.find { it.trim().toLowerCase() == tag.toLowerCase()
it.trim().toLowerCase() == tag.toLowerCase() } != null
} != null }
}
override fun equals(other: Any?): Boolean {
override fun equals(other: Any?): Boolean { if (other is LibraryItem) {
if (other is LibraryItem) { return manga.id == other.manga.id
return manga.id == other.manga.id }
} return false
return false }
}
override fun hashCode(): Int {
override fun hashCode(): Int { return manga.id!!.hashCode()
return manga.id!!.hashCode() }
} }
}

View File

@ -3,19 +3,21 @@ package eu.kanade.tachiyomi.ui.main
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.app.ActivityManager import android.app.ActivityManager
import android.app.SearchManager import android.app.SearchManager
import android.app.usage.UsageStatsManager
import android.app.Service import android.app.Service
import android.app.usage.UsageStatsManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Looper import android.os.Looper
import android.support.v4.view.GravityCompat import android.text.TextUtils
import android.support.v4.widget.DrawerLayout import android.view.View
import android.support.v7.graphics.drawable.DrawerArrowDrawable
import android.support.v7.widget.Toolbar
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
import androidx.appcompat.widget.Toolbar
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import com.bluelinelabs.conductor.* import com.bluelinelabs.conductor.*
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -32,21 +34,19 @@ import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.openInBrowser import eu.kanade.tachiyomi.util.openInBrowser
import eu.kanade.tachiyomi.util.vibrate
import exh.EXHMigrations
import exh.eh.EHentaiUpdateWorker
import exh.uconfig.WarnConfigureDialogController import exh.uconfig.WarnConfigureDialogController
import exh.ui.batchadd.BatchAddController import exh.ui.batchadd.BatchAddController
import exh.ui.lock.LockChangeHandler import exh.ui.lock.LockChangeHandler
import exh.ui.lock.LockController import exh.ui.lock.LockController
import exh.ui.lock.lockEnabled import exh.ui.lock.lockEnabled
import exh.ui.lock.notifyLockSecurity import exh.ui.lock.notifyLockSecurity
import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.injectLazy
import android.text.TextUtils
import android.view.View
import eu.kanade.tachiyomi.util.vibrate
import exh.EXHMigrations
import exh.eh.EHentaiUpdateWorker
import exh.ui.migration.MetadataFetchDialog import exh.ui.migration.MetadataFetchDialog
import kotlinx.android.synthetic.main.main_activity.*
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -357,9 +357,9 @@ class MainActivity : BaseActivity() {
val showHamburger = router.backstackSize == 1 val showHamburger = router.backstackSize == 1
if (showHamburger) { if (showHamburger) {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED)
} else { } else {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
} }
// --> EH // --> EH

View File

@ -1,9 +1,9 @@
package eu.kanade.tachiyomi.ui.main package eu.kanade.tachiyomi.ui.main
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.support.design.widget.TabLayout
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import com.google.android.material.tabs.TabLayout
class TabsAnimator(val tabs: TabLayout) { class TabsAnimator(val tabs: TabLayout) {

View File

@ -1,222 +1,222 @@
package eu.kanade.tachiyomi.ui.manga package eu.kanade.tachiyomi.ui.manga
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.TabLayout import android.view.LayoutInflater
import android.support.graphics.drawable.VectorDrawableCompat import android.view.View
import android.view.LayoutInflater import android.view.ViewGroup
import android.view.View import android.widget.LinearLayout
import android.view.ViewGroup import android.widget.TextView
import android.widget.LinearLayout import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import android.widget.TextView import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.support.RouterPagerAdapter
import com.bluelinelabs.conductor.support.RouterPagerAdapter import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.RxController import eu.kanade.tachiyomi.ui.base.controller.RxController
import eu.kanade.tachiyomi.ui.base.controller.TabbedController import eu.kanade.tachiyomi.ui.base.controller.TabbedController
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersController import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersController
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController
import eu.kanade.tachiyomi.ui.manga.track.TrackController import eu.kanade.tachiyomi.ui.manga.track.TrackController
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.manga_controller.* import kotlinx.android.synthetic.main.manga_controller.*
import rx.Subscription import rx.Subscription
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.util.*
class MangaController : RxController, TabbedController { class MangaController : RxController, TabbedController {
constructor(manga: Manga?, constructor(manga: Manga?,
fromCatalogue: Boolean = false, fromCatalogue: Boolean = false,
smartSearchConfig: CatalogueController.SmartSearchConfig? = null, smartSearchConfig: CatalogueController.SmartSearchConfig? = null,
update: Boolean = false) : super(Bundle().apply { update: Boolean = false) : super(Bundle().apply {
putLong(MANGA_EXTRA, manga?.id ?: 0) putLong(MANGA_EXTRA, manga?.id ?: 0)
putBoolean(FROM_CATALOGUE_EXTRA, fromCatalogue) putBoolean(FROM_CATALOGUE_EXTRA, fromCatalogue)
putParcelable(SMART_SEARCH_CONFIG_EXTRA, smartSearchConfig) putParcelable(SMART_SEARCH_CONFIG_EXTRA, smartSearchConfig)
putBoolean(UPDATE_EXTRA, update) putBoolean(UPDATE_EXTRA, update)
}) { }) {
this.manga = manga this.manga = manga
if (manga != null) { if (manga != null) {
source = Injekt.get<SourceManager>().getOrStub(manga.source) source = Injekt.get<SourceManager>().getOrStub(manga.source)
} }
} }
// EXH --> // EXH -->
constructor(redirect: ChaptersPresenter.EXHRedirect) : super(Bundle().apply { constructor(redirect: ChaptersPresenter.EXHRedirect) : super(Bundle().apply {
putLong(MANGA_EXTRA, redirect.manga.id!!) putLong(MANGA_EXTRA, redirect.manga.id!!)
putBoolean(UPDATE_EXTRA, redirect.update) putBoolean(UPDATE_EXTRA, redirect.update)
}) { }) {
this.manga = redirect.manga this.manga = redirect.manga
if (manga != null) { if (manga != null) {
source = Injekt.get<SourceManager>().getOrStub(redirect.manga.source) source = Injekt.get<SourceManager>().getOrStub(redirect.manga.source)
} }
} }
// EXH <-- // EXH <--
constructor(mangaId: Long) : this( constructor(mangaId: Long) : this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking()) Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking())
@Suppress("unused") @Suppress("unused")
constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA)) constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA))
var manga: Manga? = null var manga: Manga? = null
private set private set
var source: Source? = null var source: Source? = null
private set private set
private var adapter: MangaDetailAdapter? = null private var adapter: MangaDetailAdapter? = null
val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false) val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false)
var update = args.getBoolean(UPDATE_EXTRA, false) var update = args.getBoolean(UPDATE_EXTRA, false)
// EXH --> // EXH -->
val smartSearchConfig: CatalogueController.SmartSearchConfig? = args.getParcelable(SMART_SEARCH_CONFIG_EXTRA) val smartSearchConfig: CatalogueController.SmartSearchConfig? = args.getParcelable(SMART_SEARCH_CONFIG_EXTRA)
// EXH <-- // EXH <--
val lastUpdateRelay: BehaviorRelay<Date> = BehaviorRelay.create() val lastUpdateRelay: BehaviorRelay<Date> = BehaviorRelay.create()
val chapterCountRelay: BehaviorRelay<Float> = BehaviorRelay.create() val chapterCountRelay: BehaviorRelay<Float> = BehaviorRelay.create()
val mangaFavoriteRelay: PublishRelay<Boolean> = PublishRelay.create() val mangaFavoriteRelay: PublishRelay<Boolean> = PublishRelay.create()
private val trackingIconRelay: BehaviorRelay<Boolean> = BehaviorRelay.create() private val trackingIconRelay: BehaviorRelay<Boolean> = BehaviorRelay.create()
private var trackingIconSubscription: Subscription? = null private var trackingIconSubscription: Subscription? = null
override fun getTitle(): String? { override fun getTitle(): String? {
return manga?.title return manga?.title
} }
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.manga_controller, container, false) return inflater.inflate(R.layout.manga_controller, container, false)
} }
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
if (manga == null || source == null) return if (manga == null || source == null) return
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
adapter = MangaDetailAdapter() adapter = MangaDetailAdapter()
manga_pager.offscreenPageLimit = 3 manga_pager.offscreenPageLimit = 3
manga_pager.adapter = adapter manga_pager.adapter = adapter
if (!fromCatalogue) if (!fromCatalogue)
manga_pager.currentItem = CHAPTERS_CONTROLLER manga_pager.currentItem = CHAPTERS_CONTROLLER
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
super.onDestroyView(view) super.onDestroyView(view)
adapter = null adapter = null
} }
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type) super.onChangeStarted(handler, type)
if (type.isEnter) { if (type.isEnter) {
activity?.tabs?.setupWithViewPager(manga_pager) activity?.tabs?.setupWithViewPager(manga_pager)
trackingIconSubscription = trackingIconRelay.subscribe { setTrackingIconInternal(it) } trackingIconSubscription = trackingIconRelay.subscribe { setTrackingIconInternal(it) }
} }
} }
override fun onChangeEnded(handler: ControllerChangeHandler, type: ControllerChangeType) { override fun onChangeEnded(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeEnded(handler, type) super.onChangeEnded(handler, type)
if (manga == null || source == null) { if (manga == null || source == null) {
activity?.toast(R.string.manga_not_in_db) activity?.toast(R.string.manga_not_in_db)
router.popController(this) router.popController(this)
} }
} }
override fun configureTabs(tabs: TabLayout) { override fun configureTabs(tabs: TabLayout) {
with(tabs) { with(tabs) {
tabGravity = TabLayout.GRAVITY_FILL tabGravity = TabLayout.GRAVITY_FILL
tabMode = TabLayout.MODE_FIXED tabMode = TabLayout.MODE_FIXED
} }
} }
override fun cleanupTabs(tabs: TabLayout) { override fun cleanupTabs(tabs: TabLayout) {
trackingIconSubscription?.unsubscribe() trackingIconSubscription?.unsubscribe()
setTrackingIconInternal(false) setTrackingIconInternal(false)
} }
fun setTrackingIcon(visible: Boolean) { fun setTrackingIcon(visible: Boolean) {
trackingIconRelay.call(visible) trackingIconRelay.call(visible)
} }
private fun setTrackingIconInternal(visible: Boolean) { private fun setTrackingIconInternal(visible: Boolean) {
val tab = activity?.tabs?.getTabAt(TRACK_CONTROLLER) ?: return val tab = activity?.tabs?.getTabAt(TRACK_CONTROLLER) ?: return
val drawable = if (visible) val drawable = if (visible)
VectorDrawableCompat.create(resources!!, R.drawable.ic_done_white_18dp, null) VectorDrawableCompat.create(resources!!, R.drawable.ic_done_white_18dp, null)
else null else null
val view = tabField.get(tab) as LinearLayout val view = tabField.get(tab) as LinearLayout
val textView = view.getChildAt(1) as TextView val textView = view.getChildAt(1) as TextView
textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null) textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null)
textView.compoundDrawablePadding = if (visible) 4 else 0 textView.compoundDrawablePadding = if (visible) 4 else 0
} }
private inner class MangaDetailAdapter : RouterPagerAdapter(this@MangaController) { private inner class MangaDetailAdapter : RouterPagerAdapter(this@MangaController) {
private val tabCount = if (Injekt.get<TrackManager>().hasLoggedServices()) 3 else 2 private val tabCount = if (Injekt.get<TrackManager>().hasLoggedServices()) 3 else 2
private val tabTitles = listOf( private val tabTitles = listOf(
R.string.manga_detail_tab, R.string.manga_detail_tab,
R.string.manga_chapters_tab, R.string.manga_chapters_tab,
R.string.manga_tracking_tab) R.string.manga_tracking_tab)
.map { resources!!.getString(it) } .map { resources!!.getString(it) }
override fun getCount(): Int { override fun getCount(): Int {
return tabCount return tabCount
} }
override fun configureRouter(router: Router, position: Int) { override fun configureRouter(router: Router, position: Int) {
if (!router.hasRootController()) { if (!router.hasRootController()) {
val controller = when (position) { val controller = when (position) {
INFO_CONTROLLER -> MangaInfoController() INFO_CONTROLLER -> MangaInfoController()
CHAPTERS_CONTROLLER -> ChaptersController() CHAPTERS_CONTROLLER -> ChaptersController()
TRACK_CONTROLLER -> TrackController() TRACK_CONTROLLER -> TrackController()
else -> error("Wrong position $position") else -> error("Wrong position $position")
} }
router.setRoot(RouterTransaction.with(controller)) router.setRoot(RouterTransaction.with(controller))
} }
} }
override fun getPageTitle(position: Int): CharSequence { override fun getPageTitle(position: Int): CharSequence {
return tabTitles[position] return tabTitles[position]
} }
} }
companion object { companion object {
// EXH --> // EXH -->
const val UPDATE_EXTRA = "update" const val UPDATE_EXTRA = "update"
const val SMART_SEARCH_CONFIG_EXTRA = "smartSearchConfig" const val SMART_SEARCH_CONFIG_EXTRA = "smartSearchConfig"
// EXH <-- // EXH <--
const val FROM_CATALOGUE_EXTRA = "from_catalogue" const val FROM_CATALOGUE_EXTRA = "from_catalogue"
const val MANGA_EXTRA = "manga" const val MANGA_EXTRA = "manga"
const val INFO_CONTROLLER = 0 const val INFO_CONTROLLER = 0
const val CHAPTERS_CONTROLLER = 1 const val CHAPTERS_CONTROLLER = 1
const val TRACK_CONTROLLER = 2 const val TRACK_CONTROLLER = 2
private val tabField = TabLayout.Tab::class.java.getDeclaredField("view") private val tabField = TabLayout.Tab::class.java.getDeclaredField("view")
.apply { isAccessible = true } .apply { isAccessible = true }
} }
} }

View File

@ -1,55 +1,54 @@
package eu.kanade.tachiyomi.ui.manga.chapter package eu.kanade.tachiyomi.ui.manga.chapter
import android.support.v7.widget.RecyclerView import android.view.View
import android.view.View import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.Download
class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem<ChapterHolder>(),
class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem<ChapterHolder>(), Chapter by chapter {
Chapter by chapter {
private var _status: Int = 0
private var _status: Int = 0
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
override fun getLayoutRes(): Int {
override fun getLayoutRes(): Int { return R.layout.chapters_item
return R.layout.chapters_item }
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): ChapterHolder {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): ChapterHolder { return ChapterHolder(view, adapter as ChaptersAdapter)
return ChapterHolder(view, adapter as ChaptersAdapter) }
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: ChapterHolder,
holder: ChapterHolder, position: Int,
position: Int, payloads: List<Any?>?) {
payloads: List<Any?>?) {
holder.bind(this, manga)
holder.bind(this, manga) }
}
override fun equals(other: Any?): Boolean {
override fun equals(other: Any?): Boolean { if (this === other) return true
if (this === other) return true if (other is ChapterItem) {
if (other is ChapterItem) { return chapter.id!! == other.chapter.id!!
return chapter.id!! == other.chapter.id!! }
} return false
return false }
}
override fun hashCode(): Int {
override fun hashCode(): Int { return chapter.id!!.hashCode()
return chapter.id!!.hashCode() }
}
} }

View File

@ -10,11 +10,11 @@ import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.content.pm.ShortcutInfoCompat
import android.support.v4.content.pm.ShortcutManagerCompat
import android.support.v4.graphics.drawable.IconCompat
import android.view.* import android.view.*
import android.widget.Toast import android.widget.Toast
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
@ -403,13 +403,13 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
try { try {
// --> EH // --> EH
val urlString = source.mangaDetailsRequest(presenter.manga).url().toString() val urlString = source.mangaDetailsRequest(presenter.manga).url.toString()
if(preferences.eh_incogWebview().getOrDefault()) { if(preferences.eh_incogWebview().getOrDefault()) {
activity?.startActivity(Intent(activity, WebViewActivity::class.java).apply { activity?.startActivity(Intent(activity, WebViewActivity::class.java).apply {
putExtra(WebViewActivity.KEY_URL, urlString) putExtra(WebViewActivity.KEY_URL, urlString)
}) })
} else { } else {
context.openInBrowser(source.mangaDetailsRequest(presenter.manga).url().toString()) context.openInBrowser(source.mangaDetailsRequest(presenter.manga).url.toString())
} }
// <-- EH // <-- EH
} catch (e: Exception) { } catch (e: Exception) {
@ -421,7 +421,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
val source = presenter.source as? HttpSource ?: return val source = presenter.source as? HttpSource ?: return
val url = try { val url = try {
source.mangaDetailsRequest(presenter.manga).url().toString() source.mangaDetailsRequest(presenter.manga).url.toString()
} catch (e: Exception) { } catch (e: Exception) {
return return
} }
@ -438,7 +438,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
val source = presenter.source as? HttpSource ?: return val source = presenter.source as? HttpSource ?: return
try { try {
val url = source.mangaDetailsRequest(presenter.manga).url().toString() val url = source.mangaDetailsRequest(presenter.manga).url.toString()
val intent = Intent(Intent.ACTION_SEND).apply { val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain" type = "text/plain"
putExtra(Intent.EXTRA_TEXT, url) putExtra(Intent.EXTRA_TEXT, url)

View File

@ -1,45 +1,44 @@
package eu.kanade.tachiyomi.ui.manga.track package eu.kanade.tachiyomi.ui.manga.track
import android.support.v7.widget.RecyclerView import android.view.ViewGroup
import android.view.ViewGroup import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.inflate
class TrackAdapter(controller: TrackController) : androidx.recyclerview.widget.RecyclerView.Adapter<TrackHolder>() {
class TrackAdapter(controller: TrackController) : RecyclerView.Adapter<TrackHolder>() {
var items = emptyList<TrackItem>()
var items = emptyList<TrackItem>() set(value) {
set(value) { if (field !== value) {
if (field !== value) { field = value
field = value notifyDataSetChanged()
notifyDataSetChanged() }
} }
}
val rowClickListener: OnClickListener = controller
val rowClickListener: OnClickListener = controller
fun getItem(index: Int): TrackItem? {
fun getItem(index: Int): TrackItem? { return items.getOrNull(index)
return items.getOrNull(index) }
}
override fun getItemCount(): Int {
override fun getItemCount(): Int { return items.size
return items.size }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackHolder {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackHolder { val view = parent.inflate(R.layout.track_item)
val view = parent.inflate(R.layout.track_item) return TrackHolder(view, this)
return TrackHolder(view, this) }
}
override fun onBindViewHolder(holder: TrackHolder, position: Int) {
override fun onBindViewHolder(holder: TrackHolder, position: Int) { holder.bind(items[position])
holder.bind(items[position]) }
}
interface OnClickListener {
interface OnClickListener { fun onLogoClick(position: Int)
fun onLogoClick(position: Int) fun onTitleClick(position: Int)
fun onTitleClick(position: Int) fun onStatusClick(position: Int)
fun onStatusClick(position: Int) fun onChaptersClick(position: Int)
fun onChaptersClick(position: Int) fun onScoreClick(position: Int)
fun onScoreClick(position: Int) }
}
}
}

View File

@ -1,142 +1,141 @@
package eu.kanade.tachiyomi.ui.manga.track package eu.kanade.tachiyomi.ui.manga.track
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater
import android.view.LayoutInflater import android.view.View
import android.view.View import android.view.ViewGroup
import android.view.ViewGroup import com.jakewharton.rxbinding.support.v4.widget.refreshes
import com.jakewharton.rxbinding.support.v4.widget.refreshes import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.util.toast import kotlinx.android.synthetic.main.track_controller.*
import kotlinx.android.synthetic.main.track_controller.* import timber.log.Timber
import timber.log.Timber
class TrackController : NucleusController<TrackPresenter>(),
class TrackController : NucleusController<TrackPresenter>(), TrackAdapter.OnClickListener,
TrackAdapter.OnClickListener, SetTrackStatusDialog.Listener,
SetTrackStatusDialog.Listener, SetTrackChaptersDialog.Listener,
SetTrackChaptersDialog.Listener, SetTrackScoreDialog.Listener {
SetTrackScoreDialog.Listener {
private var adapter: TrackAdapter? = null
private var adapter: TrackAdapter? = null
init {
init { // There's no menu, but this avoids a bug when coming from the catalogue, where the menu
// There's no menu, but this avoids a bug when coming from the catalogue, where the menu // disappears if the searchview is expanded
// disappears if the searchview is expanded setHasOptionsMenu(true)
setHasOptionsMenu(true) }
}
override fun createPresenter(): TrackPresenter {
override fun createPresenter(): TrackPresenter { return TrackPresenter((parentController as MangaController).manga!!)
return TrackPresenter((parentController as MangaController).manga!!) }
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.track_controller, container, false)
return inflater.inflate(R.layout.track_controller, container, false) }
}
override fun onViewCreated(view: View) {
override fun onViewCreated(view: View) { super.onViewCreated(view)
super.onViewCreated(view)
adapter = TrackAdapter(this)
adapter = TrackAdapter(this) with(view) {
with(view) { track_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
track_recycler.layoutManager = LinearLayoutManager(context) track_recycler.adapter = adapter
track_recycler.adapter = adapter swipe_refresh.isEnabled = false
swipe_refresh.isEnabled = false swipe_refresh.refreshes().subscribeUntilDestroy { presenter.refresh() }
swipe_refresh.refreshes().subscribeUntilDestroy { presenter.refresh() } }
} }
}
override fun onDestroyView(view: View) {
override fun onDestroyView(view: View) { adapter = null
adapter = null super.onDestroyView(view)
super.onDestroyView(view) }
}
fun onNextTrackings(trackings: List<TrackItem>) {
fun onNextTrackings(trackings: List<TrackItem>) { val atLeastOneLink = trackings.any { it.track != null }
val atLeastOneLink = trackings.any { it.track != null } adapter?.items = trackings
adapter?.items = trackings swipe_refresh?.isEnabled = atLeastOneLink
swipe_refresh?.isEnabled = atLeastOneLink (parentController as? MangaController)?.setTrackingIcon(atLeastOneLink)
(parentController as? MangaController)?.setTrackingIcon(atLeastOneLink) }
}
fun onSearchResults(results: List<TrackSearch>) {
fun onSearchResults(results: List<TrackSearch>) { getSearchDialog()?.onSearchResults(results)
getSearchDialog()?.onSearchResults(results) }
}
@Suppress("UNUSED_PARAMETER")
@Suppress("UNUSED_PARAMETER") fun onSearchResultsError(error: Throwable) {
fun onSearchResultsError(error: Throwable) { Timber.e(error)
Timber.e(error) getSearchDialog()?.onSearchResultsError()
getSearchDialog()?.onSearchResultsError() }
}
private fun getSearchDialog(): TrackSearchDialog? {
private fun getSearchDialog(): TrackSearchDialog? { return router.getControllerWithTag(TAG_SEARCH_CONTROLLER) as? TrackSearchDialog
return router.getControllerWithTag(TAG_SEARCH_CONTROLLER) as? TrackSearchDialog }
}
fun onRefreshDone() {
fun onRefreshDone() { swipe_refresh?.isRefreshing = false
swipe_refresh?.isRefreshing = false }
}
fun onRefreshError(error: Throwable) {
fun onRefreshError(error: Throwable) { swipe_refresh?.isRefreshing = false
swipe_refresh?.isRefreshing = false activity?.toast(error.message)
activity?.toast(error.message) }
}
override fun onLogoClick(position: Int) {
override fun onLogoClick(position: Int) { val track = adapter?.getItem(position)?.track ?: return
val track = adapter?.getItem(position)?.track ?: return
if (track.tracking_url.isNullOrBlank()) {
if (track.tracking_url.isNullOrBlank()) { activity?.toast(R.string.url_not_set)
activity?.toast(R.string.url_not_set) } else {
} else { activity?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(track.tracking_url)))
activity?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(track.tracking_url))) }
} }
}
override fun onTitleClick(position: Int) {
override fun onTitleClick(position: Int) { val item = adapter?.getItem(position) ?: return
val item = adapter?.getItem(position) ?: return TrackSearchDialog(this, item.service).showDialog(router, TAG_SEARCH_CONTROLLER)
TrackSearchDialog(this, item.service).showDialog(router, TAG_SEARCH_CONTROLLER) }
}
override fun onStatusClick(position: Int) {
override fun onStatusClick(position: Int) { val item = adapter?.getItem(position) ?: return
val item = adapter?.getItem(position) ?: return if (item.track == null) return
if (item.track == null) return
SetTrackStatusDialog(this, item).showDialog(router)
SetTrackStatusDialog(this, item).showDialog(router) }
}
override fun onChaptersClick(position: Int) {
override fun onChaptersClick(position: Int) { val item = adapter?.getItem(position) ?: return
val item = adapter?.getItem(position) ?: return if (item.track == null) return
if (item.track == null) return
SetTrackChaptersDialog(this, item).showDialog(router)
SetTrackChaptersDialog(this, item).showDialog(router) }
}
override fun onScoreClick(position: Int) {
override fun onScoreClick(position: Int) { val item = adapter?.getItem(position) ?: return
val item = adapter?.getItem(position) ?: return if (item.track == null) return
if (item.track == null) return
SetTrackScoreDialog(this, item).showDialog(router)
SetTrackScoreDialog(this, item).showDialog(router) }
}
override fun setStatus(item: TrackItem, selection: Int) {
override fun setStatus(item: TrackItem, selection: Int) { presenter.setStatus(item, selection)
presenter.setStatus(item, selection) swipe_refresh?.isRefreshing = true
swipe_refresh?.isRefreshing = true }
}
override fun setScore(item: TrackItem, score: Int) {
override fun setScore(item: TrackItem, score: Int) { presenter.setScore(item, score)
presenter.setScore(item, score) swipe_refresh?.isRefreshing = true
swipe_refresh?.isRefreshing = true }
}
override fun setChaptersRead(item: TrackItem, chaptersRead: Int) {
override fun setChaptersRead(item: TrackItem, chaptersRead: Int) { presenter.setLastChapterRead(item, chaptersRead)
presenter.setLastChapterRead(item, chaptersRead) swipe_refresh?.isRefreshing = true
swipe_refresh?.isRefreshing = true }
}
private companion object {
private companion object { const val TAG_SEARCH_CONTROLLER = "track_search_controller"
const val TAG_SEARCH_CONTROLLER = "track_search_controller" }
}
} }

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.migration package eu.kanade.tachiyomi.ui.migration
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -14,11 +13,11 @@ class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>() {
return R.layout.catalogue_list_item return R.layout.catalogue_list_item
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MangaHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): MangaHolder {
return MangaHolder(view, adapter) return MangaHolder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>,
holder: MangaHolder, holder: MangaHolder,
position: Int, position: Int,
payloads: List<Any?>?) { payloads: List<Any?>?) {

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.migration
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -52,7 +51,7 @@ class MigrationController : NucleusController<MigrationPresenter>(),
super.onViewCreated(view) super.onViewCreated(view)
adapter = FlexibleAdapter(null, this) adapter = FlexibleAdapter(null, this)
migration_recycler.layoutManager = LinearLayoutManager(view.context) migration_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
migration_recycler.adapter = adapter migration_recycler.adapter = adapter
} }

View File

@ -1,13 +1,12 @@
package eu.kanade.tachiyomi.ui.migration package eu.kanade.tachiyomi.ui.migration
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import kotlinx.android.synthetic.main.catalogue_main_controller_card.title import kotlinx.android.synthetic.main.catalogue_main_controller_card.*
/** /**
* Item that contains the selection header. * Item that contains the selection header.
@ -24,14 +23,14 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
/** /**
* Creates a new view holder for this item. * Creates a new view holder for this item.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): Holder {
return SelectionHeader.Holder(view, adapter) return SelectionHeader.Holder(view, adapter)
} }
/** /**
* Binds this item to the given view holder. * Binds this item to the given view holder.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, holder: Holder,
position: Int, payloads: List<Any?>?) { position: Int, payloads: List<Any?>?) {
// Intentionally empty // Intentionally empty
} }

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