Merge pull request #1 from Jays2Kings/truly-to-androidx

v9.0.0
This commit is contained in:
Jays2Kings 2019-12-02 16:30:09 -08:00 committed by GitHub
commit 4ac4923d15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
375 changed files with 5217 additions and 1332 deletions

View File

@ -29,17 +29,17 @@ ext {
} }
android { android {
compileSdkVersion 27 compileSdkVersion 29
buildToolsVersion '28.0.3' buildToolsVersion '29.0.2'
publishNonDefault true publishNonDefault true
defaultConfig { defaultConfig {
applicationId "eu.kanade.tachiyomi" applicationId "eu.kanade.tachiyomi"
minSdkVersion 16 minSdkVersion 21
targetSdkVersion 27 targetSdkVersion 29
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 41 versionCode 42
versionName "0.8.4" versionName "0.9.0"
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\"" buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\"" buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@ -60,6 +60,9 @@ android {
versionNameSuffix "-${getCommitCount()}" versionNameSuffix "-${getCommitCount()}"
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
} }
release {
applicationIdSuffix = '.j2k'
}
} }
flavorDimensions "default" flavorDimensions "default"
@ -73,7 +76,6 @@ android {
dimension "default" dimension "default"
} }
dev { dev {
minSdkVersion 21
resConfigs "en", "xxhdpi" resConfigs "en", "xxhdpi"
dimension "default" dimension "default"
} }
@ -92,6 +94,15 @@ android {
checkReleaseBuilds false checkReleaseBuilds false
} }
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
} }
dependencies { dependencies {
@ -101,35 +112,35 @@ 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 = '27.0.2' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation "com.android.support:support-v4:$support_library_version" implementation 'androidx.cardview:cardview:1.0.0'
implementation "com.android.support:appcompat-v7:$support_library_version" implementation 'com.google.android.material:material:1.0.0'
implementation "com.android.support:cardview-v7:$support_library_version" implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation "com.android.support:design:$support_library_version" implementation 'androidx.preference:preference:1.1.0'
implementation "com.android.support:recyclerview-v7:$support_library_version" implementation 'androidx.annotation:annotation:1.1.0'
implementation "com.android.support:preference-v7:$support_library_version" implementation 'androidx.browser:browser:1.0.0'
implementation "com.android.support:support-annotations:$support_library_version"
implementation "com.android.support:customtabs:$support_library_version"
implementation 'com.android.support.constraint:constraint-layout:1.1.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.android.support:multidex:1.0.3' implementation 'androidx.multidex:multidex:2.0.1'
standardImplementation 'com.google.firebase:firebase-core:11.8.0' 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'
implementation 'io.reactivex:rxjava:1.3.6' implementation 'io.reactivex:rxjava:1.3.8'
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0' implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2' implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
implementation 'com.github.pwittchen:reactivenetwork:0.7.0' implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
// Network client // Network client
implementation "com.squareup.okhttp3:okhttp:3.10.0" final okhttp_version = "4.2.1"
implementation 'com.squareup.okio:okio:1.14.0' implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
implementation 'com.squareup.okio:okio:2.4.0'
// REST // REST
final retrofit_version = '2.3.0' 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"
@ -146,17 +157,17 @@ dependencies {
implementation 'com.github.inorichi:unifile:e9ee588' implementation 'com.github.inorichi:unifile:e9ee588'
// HTML parser // HTML parser
implementation 'org.jsoup:jsoup:1.10.2' implementation 'org.jsoup:jsoup:1.12.1'
// Job scheduling // Job scheduling
implementation 'com.evernote:android-job:1.2.5' implementation 'com.evernote:android-job:1.2.5'
implementation 'com.google.android.gms:play-services-gcm:11.8.0' implementation 'com.google.android.gms:play-services-gcm: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.0.0' 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'
@ -170,16 +181,16 @@ 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.6.1' 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.6.1' implementation 'com.jakewharton.timber:timber:4.7.1'
// Crash reports // Crash reports
implementation 'ch.acra:acra:4.9.2' implementation 'ch.acra:acra:4.9.2'
@ -190,24 +201,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.0-rc4' implementation 'eu.davidea:flexible-adapter:5.1.0'
implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b1' 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' implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
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.2' implementation 'com.github.mthli:Slice:v1.2'
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.3' implementation 'com.github.chrisbanes:PhotoView:2.3.0'
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 project(":j2k-preference")
// RxBindings // RxBindings
final rxbindings_version = '1.0.1' final rxbindings_version = '1.0.1'
@ -228,18 +239,19 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
final coroutines_version = '0.22.2' final coroutines_version = '1.3.2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
} }
buildscript { buildscript {
ext.kotlin_version = '1.2.71' ext.kotlin_version = '1.3.50'
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
} }
} }
@ -247,12 +259,6 @@ repositories {
mavenCentral() mavenCentral()
} }
kotlin {
experimental {
coroutines 'enable'
}
}
androidExtensions { androidExtensions {
experimental = true experimental = true
} }

View File

@ -9,7 +9,10 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:name=".App" android:name=".App"
@ -20,7 +23,8 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name" android:label="@string/app_name"
android:largeHeap="true" android:largeHeap="true"
android:theme="@style/Theme.Tachiyomi"> android:theme="@style/Theme.Tachiyomi"
android:networkSecurityConfig="@xml/network_security_config">
<activity <activity
android:name=".ui.main.MainActivity" android:name=".ui.main.MainActivity"
android:launchMode="singleTask"> android:launchMode="singleTask">
@ -43,6 +47,9 @@
</activity> </activity>
<activity <activity
android:name=".ui.reader.ReaderActivity" /> android:name=".ui.reader.ReaderActivity" />
<activity
android:name=".ui.manga.info.WebViewActivity"
android:configChanges="uiMode|orientation|screenSize"/>
<activity <activity
android:name=".widget.CustomLayoutPickerActivity" android:name=".widget.CustomLayoutPickerActivity"
android:label="@string/app_name" android:label="@string/app_name"
@ -95,7 +102,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

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.support.multidex.MultiDex import androidx.multidex.MultiDex
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob

View File

@ -39,7 +39,7 @@ object Migrations {
if (oldDir.exists()) { if (oldDir.exists()) {
val destDir = context.getExternalFilesDir("covers") val destDir = context.getExternalFilesDir("covers")
if (destDir != null) { if (destDir != null) {
oldDir.listFiles().forEach { oldDir.listFiles()?.forEach {
it.renameTo(File(destDir, it.name)) it.renameTo(File(destDir, it.name))
} }
} }

View File

@ -57,6 +57,7 @@ class BackupCreateService : IntentService(NAME) {
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
val flags = intent.getIntExtra(EXTRA_FLAGS, 0) val flags = intent.getIntExtra(EXTRA_FLAGS, 0)
// Create backup // Create backup
if (uri != null)
backupManager.createBackup(uri, flags, false) backupManager.createBackup(uri, flags, false)
} }

View File

@ -160,9 +160,9 @@ class BackupRestoreService : Service() {
* @return the start value of the command. * @return the start value of the command.
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) return Service.START_NOT_STICKY if (intent == null) return START_NOT_STICKY
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
// Unsubscribe from any previous subscription if needed. // Unsubscribe from any previous subscription if needed.
subscription?.unsubscribe() subscription?.unsubscribe()
@ -175,7 +175,7 @@ class BackupRestoreService : Service() {
.subscribeOn(Schedulers.from(executor)) .subscribeOn(Schedulers.from(executor))
.subscribe() .subscribe()
return Service.START_NOT_STICKY return START_NOT_STICKY
} }
/** /**
@ -189,7 +189,7 @@ class BackupRestoreService : Service() {
return Observable.just(Unit) return Observable.just(Unit)
.map { .map {
val reader = JsonReader(contentResolver.openInputStream(uri).bufferedReader()) val reader = JsonReader(contentResolver.openInputStream(uri)!!.bufferedReader())
val json = JsonParser().parse(reader).asJsonObject val json = JsonParser().parse(reader).asJsonObject
// Get parser version // Get parser version

View File

@ -11,6 +11,8 @@ 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.Okio
import okio.buffer
import okio.sink
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
@ -126,7 +128,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()
} }
@ -186,12 +188,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,11 @@
package eu.kanade.tachiyomi.data.cache package eu.kanade.tachiyomi.data.cache
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.launchUI
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -60,8 +64,7 @@ class CoverCache(private val context: Context) {
return false return false
// Remove file. // Remove file.
val file = getCoverFile(thumbnailUrl!!) val file = getCoverFile(thumbnailUrl)
return file.exists() && file.delete() return file.exists() && file.delete()
} }
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.data.database package eu.kanade.tachiyomi.data.database
import android.arch.persistence.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteOpenHelper
import android.content.Context import android.content.Context
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.*

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 android.content.Context import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
@ -18,7 +18,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/** /**
* Version of the database. * Version of the database.
*/ */
const val DATABASE_VERSION = 8 const val DATABASE_VERSION = 9
} }
override fun onCreate(db: SupportSQLiteDatabase) = with(db) { override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@ -67,6 +67,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
db.execSQL(MangaTable.createLibraryIndexQuery) db.execSQL(MangaTable.createLibraryIndexQuery)
db.execSQL(ChapterTable.createUnreadChaptersIndexQuery) db.execSQL(ChapterTable.createUnreadChaptersIndexQuery)
} }
if (oldVersion < 9) {
db.execSQL(MangaTable.addHideTitle)
}
} }
override fun onConfigure(db: SupportSQLiteDatabase) { override fun onConfigure(db: SupportSQLiteDatabase) {

View File

@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_HIDE_TITLE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE
@ -61,6 +62,7 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
put(COL_LAST_UPDATE, obj.last_update) put(COL_LAST_UPDATE, obj.last_update)
put(COL_INITIALIZED, obj.initialized) put(COL_INITIALIZED, obj.initialized)
put(COL_VIEWER, obj.viewer) put(COL_VIEWER, obj.viewer)
put(COL_HIDE_TITLE, obj.hide_title)
put(COL_CHAPTER_FLAGS, obj.chapter_flags) put(COL_CHAPTER_FLAGS, obj.chapter_flags)
} }
} }
@ -82,6 +84,7 @@ interface BaseMangaGetResolver {
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1 initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER)) viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS)) chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
hide_title = cursor.getInt(cursor.getColumnIndex(COL_HIDE_TITLE)) == 1
} }
} }

View File

@ -16,6 +16,8 @@ interface Manga : SManga {
var chapter_flags: Int var chapter_flags: Int
var hide_title: Boolean
fun setChapterOrder(order: Int) { fun setChapterOrder(order: Int) {
setFlags(order, SORT_MASK) setFlags(order, SORT_MASK)
} }

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.source.model.SManga
open class MangaImpl : Manga { open class MangaImpl : Manga {
override var id: Long? = null override var id: Long? = null
@ -32,6 +34,14 @@ open class MangaImpl : Manga {
override var chapter_flags: Int = 0 override var chapter_flags: Int = 0
override var hide_title: Boolean = false
override fun copyFrom(other: SManga) {
if (other is MangaImpl && (other as MangaImpl)::title.isInitialized && other.title != title)
title = other.title
super.copyFrom(other)
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other == null || javaClass != other.javaClass) return false if (other == null || javaClass != other.javaClass) return false

View File

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

View File

@ -22,10 +22,10 @@ interface HistoryQueries : DbProvider {
* Returns history of recent manga containing last read chapter * Returns history of recent manga containing last read chapter
* @param date recent date range * @param date recent date range
*/ */
fun getRecentManga(date: Date) = db.get() fun getRecentManga(date: Date, offset: Int = 0) = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder() .withQuery(RawQuery.builder()
.query(getRecentMangasQuery()) .query(getRecentMangasQuery(offset))
.args(date.time) .args(date.time)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build()) .build())

View File

@ -82,6 +82,11 @@ interface MangaQueries : DbProvider {
.withPutResolver(MangaViewerPutResolver()) .withPutResolver(MangaViewerPutResolver())
.prepare() .prepare()
fun updateMangaHideTitle(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaHideTitlePutResolver())
.prepare()
fun updateMangaTitle(manga: Manga) = db.put() fun updateMangaTitle(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaTitlePutResolver()) .withPutResolver(MangaTitlePutResolver())

View File

@ -47,7 +47,7 @@ fun getRecentsQuery() = """
* and are read after the given time period * and are read after the given time period
* @return return limit is 25 * @return return limit is 25
*/ */
fun getRecentMangasQuery() = """ fun getRecentMangasQuery(offset: Int = 0) = """
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
@ -62,7 +62,7 @@ fun getRecentMangasQuery() = """
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID} ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
ORDER BY max_last_read.${History.COL_LAST_READ} DESC ORDER BY max_last_read.${History.COL_LAST_READ} DESC
LIMIT 25 LIMIT 25 OFFSET $offset
""" """
fun getHistoryByMangaId() = """ fun getHistoryByMangaId() = """

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

@ -0,0 +1,33 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.content.ContentValues
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaHideTitlePutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_HIDE_TITLE, manga.hide_title)
}
}

View File

@ -38,6 +38,8 @@ object MangaTable {
const val COL_CATEGORY = "category" const val COL_CATEGORY = "category"
const val COL_HIDE_TITLE = "hideTitle"
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() = """CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_ID INTEGER NOT NULL PRIMARY KEY,
@ -54,6 +56,7 @@ object MangaTable {
$COL_LAST_UPDATE LONG, $COL_LAST_UPDATE LONG,
$COL_INITIALIZED BOOLEAN NOT NULL, $COL_INITIALIZED BOOLEAN NOT NULL,
$COL_VIEWER INTEGER NOT NULL, $COL_VIEWER INTEGER NOT NULL,
$COL_HIDE_TITLE INTEGER NOT NULL,
$COL_CHAPTER_FLAGS INTEGER NOT NULL $COL_CHAPTER_FLAGS INTEGER NOT NULL
)""" )"""
@ -63,4 +66,7 @@ object MangaTable {
val createLibraryIndexQuery: String val createLibraryIndexQuery: String
get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " + get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " +
"WHERE $COL_FAVORITE = 1" "WHERE $COL_FAVORITE = 1"
val addHideTitle: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_HIDE_TITLE INTEGER DEFAULT 0"
} }

View File

@ -9,6 +9,9 @@ import eu.kanade.tachiyomi.data.download.model.DownloadQueue
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.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.launchNow
import eu.kanade.tachiyomi.util.launchUI
import kotlinx.coroutines.delay
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -181,6 +184,7 @@ class DownloadManager(context: Context) {
* @param source the source of the manga. * @param source the source of the manga.
*/ */
fun deleteManga(manga: Manga, source: Source) { fun deleteManga(manga: Manga, source: Source) {
downloader.clearQueue(manga, true)
queue.remove(manga) queue.remove(manga)
provider.findMangaDir(manga, source)?.delete() provider.findMangaDir(manga, source)?.delete()
cache.removeManga(manga) cache.removeManga(manga)

View File

@ -2,7 +2,8 @@ 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 androidx.core.content.ContextCompat
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
@ -168,7 +169,8 @@ internal class DownloadNotifier(private val context: Context) {
setSmallIcon(android.R.drawable.stat_sys_download_done) setSmallIcon(android.R.drawable.stat_sys_download_done)
setAutoCancel(true) setAutoCancel(true)
clearActions() clearActions()
setContentIntent(NotificationReceiver.openChapterPendingBroadcast(context, download.manga, download.chapter)) setContentIntent(NotificationReceiver.openChapterPendingActivity(context, download
.manga, download.chapter))
setProgress(0, 0, false) setProgress(0, 0, false)
} }
@ -214,9 +216,11 @@ internal class DownloadNotifier(private val context: Context) {
setContentTitle(chapter ?: context.getString(R.string.download_notifier_downloader_title)) setContentTitle(chapter ?: context.getString(R.string.download_notifier_downloader_title))
setContentText(error ?: context.getString(R.string.download_notifier_unkown_error)) setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
setSmallIcon(android.R.drawable.stat_sys_warning) setSmallIcon(android.R.drawable.stat_sys_warning)
setCategory(NotificationCompat.CATEGORY_ERROR)
clearActions() clearActions()
setAutoCancel(false) setAutoCancel(true)
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
color = ContextCompat.getColor(context, R.color.colorAccentLight)
setProgress(0, 0, false) setProgress(0, 0, false)
} }
notification.show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR) notification.show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)

View File

@ -90,6 +90,22 @@ class DownloadPendingDeleter(context: Context) {
} }
} }
/**
* Returns the list of chapters to be deleted grouped by its manga.
*
* Note: the returned list of manga and chapters only contain basic information needed by the
* downloader, so don't use them for anything else.
*/
@Synchronized
fun getPendingChapters(manga: Manga): List<Chapter>? {
val entries = decodeAll()
prefs.edit().clear().apply()
lastAddedEntry = null
val entry = entries.find { it.manga.id == manga.id }
return entry?.chapters?.map { it.toModel() }
}
/** /**
* Decodes all the chapters from preferences. * Decodes all the chapters from preferences.
*/ */

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

@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.util.*
import kotlinx.coroutines.experimental.async import kotlinx.coroutines.async
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -157,6 +157,26 @@ class Downloader(
notifier.dismiss() notifier.dismiss()
} }
/**
* Removes everything from the queue for a certain manga
*
* @param isNotification value that determines if status is set (needed for view updates)
*/
fun clearQueue(manga: Manga, isNotification: Boolean = false) {
//Needed to update the chapter view
if (isNotification) {
queue
.filter { it.status == Download.QUEUE && it.manga.id == manga.id }
.forEach { it.status = Download.NOT_DOWNLOADED }
}
queue.remove(manga)
if (queue.isEmpty()) {
DownloadService.stop(context)
stop()
}
notifier.dismiss()
}
/** /**
* Prepares the subscriptions to start downloading. * Prepares the subscriptions to start downloading.
*/ */
@ -351,7 +371,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) {
@ -374,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

@ -22,7 +22,7 @@ class LibraryUpdateJob : Job() {
val preferences = Injekt.get<PreferencesHelper>() val preferences = Injekt.get<PreferencesHelper>()
val interval = prefInterval ?: preferences.libraryUpdateInterval().getOrDefault() val interval = prefInterval ?: preferences.libraryUpdateInterval().getOrDefault()
if (interval > 0) { if (interval > 0) {
val restrictions = preferences.libraryUpdateRestriction() val restrictions = preferences.libraryUpdateRestriction()!!
val acRestriction = "ac" in restrictions val acRestriction = "ac" in restrictions
val wifiRestriction = if ("wifi" in restrictions) val wifiRestriction = if ("wifi" in restrictions)
JobRequest.NetworkType.UNMETERED JobRequest.NetworkType.UNMETERED

View File

@ -9,7 +9,9 @@ import android.graphics.BitmapFactory
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 androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
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
@ -18,6 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@ -29,14 +32,18 @@ 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.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.util.chop
import eu.kanade.tachiyomi.util.isServiceRunning
import eu.kanade.tachiyomi.util.notification
import eu.kanade.tachiyomi.util.notificationManager
import eu.kanade.tachiyomi.util.syncChaptersWithSource
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
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.* import java.util.ArrayList
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
/** /**
@ -88,6 +95,7 @@ class LibraryUpdateService(
.setLargeIcon(notificationBitmap) .setLargeIcon(notificationBitmap)
.setOngoing(true) .setOngoing(true)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setColor(ContextCompat.getColor(this, R.color.colorAccentLight))
.addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent) .addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent)
} }
@ -270,7 +278,7 @@ class LibraryUpdateService(
// Initialize the variables holding the progress of the updates. // Initialize the variables holding the progress of the updates.
val count = AtomicInteger(0) val count = AtomicInteger(0)
// List containing new updates // List containing new updates
val newUpdates = ArrayList<Manga>() val newUpdates = ArrayList<Pair<Manga, Array<Chapter>>>()
// list containing failed updates // list containing failed updates
val failedUpdates = ArrayList<Manga>() val failedUpdates = ArrayList<Manga>()
// List containing categories that get included in downloads. // List containing categories that get included in downloads.
@ -303,7 +311,8 @@ class LibraryUpdateService(
} }
} }
// Convert to the manga that contains new chapters. // Convert to the manga that contains new chapters.
.map { manga } .map { Pair(manga, (it.first.sortedByDescending { ch -> ch
.source_order }.toTypedArray())) }
} }
// Add manga with new chapters to the list. // Add manga with new chapters to the list.
.doOnNext { manga -> .doOnNext { manga ->
@ -325,6 +334,7 @@ class LibraryUpdateService(
cancelProgressNotification() cancelProgressNotification()
} }
.map { manga -> manga.first }
} }
fun downloadChapters(manga: Manga, chapters: List<Chapter>) { fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
@ -438,40 +448,65 @@ class LibraryUpdateService(
* *
* @param updates a list of manga with new updates. * @param updates a list of manga with new updates.
*/ */
private fun showResultNotification(updates: List<Manga>) { private fun showResultNotification(updates: List<Pair<Manga, Array<Chapter>>>) {
val newUpdates = updates.map { it.title.chop(45) }.toMutableSet() val notifications = ArrayList<Pair<Notification, Int>>()
updates.forEach {
// Append new chapters from a previous, existing notification val manga = it.first
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val chapters = it.second
val previousNotification = notificationManager.activeNotifications val chapterNames = chapters.map { chapter -> chapter.name.chop(45) }.toSet()
.find { it.id == Notifications.ID_LIBRARY_RESULT } notifications.add(Pair(notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachiyomi_icon)
if (previousNotification != null) { try {
val oldUpdates = previousNotification.notification.extras val icon = GlideApp.with(this@LibraryUpdateService)
.getString(Notification.EXTRA_BIG_TEXT) .asBitmap().load(manga).dontTransform().centerCrop().circleCrop()
.override(256, 256).submit().get()
if (!oldUpdates.isNullOrEmpty()) { setLargeIcon(icon)
newUpdates += oldUpdates.split("\n")
}
} }
catch (e: Exception) { }
setContentTitle(manga.title.chop(45))
color = ContextCompat.getColor(this@LibraryUpdateService, R.color.colorAccentLight)
setContentText(chapterNames.first())
setStyle(NotificationCompat.BigTextStyle().bigText(
if (chapterNames.size > 5) {
"${chapterNames.take(4).joinToString(", ")}, " +
getString(R.string.notification_and_n_more, (chapterNames.size - 4))
} else chapterNames.joinToString(", ")))
priority = NotificationCompat.PRIORITY_HIGH
setGroup(Notifications.GROUP_NEW_CHAPTERS)
setContentIntent(
NotificationReceiver.openChapterPendingActivity(
this@LibraryUpdateService, manga, chapters.first()
)
)
addAction(R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(this@LibraryUpdateService,
manga, chapters, Notifications.ID_NEW_CHAPTERS))
addAction(R.drawable.ic_book_white_24dp, getString(R.string.action_view_chapters),
NotificationReceiver.openChapterPendingActivity(this@LibraryUpdateService,
manga, Notifications.ID_NEW_CHAPTERS))
setAutoCancel(true)
}, manga.id.hashCode()))
} }
notificationManager.notify(Notifications.ID_LIBRARY_RESULT, notification(Notifications.CHANNEL_LIBRARY) { NotificationManagerCompat.from(this).apply {
setSmallIcon(R.drawable.ic_book_white_24dp) notifications.forEach {
notify(it.second, it.first)
}
notify(Notifications.ID_NEW_CHAPTERS, notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachiyomi_icon)
setLargeIcon(notificationBitmap) setLargeIcon(notificationBitmap)
setContentTitle(getString(R.string.notification_new_chapters)) setContentTitle(getString(R.string.notification_new_chapters))
if (newUpdates.size > 1) { color = ContextCompat.getColor(applicationContext, R.color.colorAccentLight)
setContentText(getString(R.string.notification_new_chapters_text, newUpdates.size)) setContentText(getString(R.string.notification_new_chapters_text, updates.size))
setStyle(NotificationCompat.BigTextStyle().bigText(newUpdates.joinToString("\n")))
setNumber(newUpdates.size)
} else {
setContentText(newUpdates.first())
}
priority = NotificationCompat.PRIORITY_HIGH priority = NotificationCompat.PRIORITY_HIGH
setGroup(Notifications.GROUP_NEW_CHAPTERS)
setGroupSummary(true)
setContentIntent(getNotificationIntent()) setContentIntent(getNotificationIntent())
setAutoCancel(true) setAutoCancel(true)
}) })
} }
}
/** /**
* Cancels the progress notification. * Cancels the progress notification.

View File

@ -1,9 +1,13 @@
package eu.kanade.tachiyomi.data.notification package eu.kanade.tachiyomi.data.notification
import android.app.Activity
import android.app.KeyguardManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Handler import android.os.Handler
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -12,11 +16,17 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.getUriCompat import eu.kanade.tachiyomi.util.getUriCompat
import eu.kanade.tachiyomi.util.notificationManager import eu.kanade.tachiyomi.util.notificationManager
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
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 eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
@ -54,12 +64,21 @@ class NotificationReceiver : BroadcastReceiver() {
ACTION_DELETE_IMAGE -> deleteImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION), ACTION_DELETE_IMAGE -> deleteImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
// Cancel library update and dismiss notification // Cancel library update and dismiss notification
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context, Notifications.ID_LIBRARY_PROGRESS) ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
// Open reader activity // Open reader activity
ACTION_OPEN_CHAPTER -> { ACTION_OPEN_CHAPTER -> {
openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1), openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)) intent.getLongExtra(EXTRA_CHAPTER_ID, -1))
} }
ACTION_MARK_AS_READ -> {
val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
if (notificationId > -1) dismissNotification(
context, notificationId, intent.getIntExtra(EXTRA_GROUP_ID, 0)
)
val urls = intent.getStringArrayExtra(EXTRA_CHAPTER_URL) ?: return
val mangaId = intent.getLongExtra(EXTRA_MANGA_ID, -1)
markAsRead(urls, mangaId)
}
} }
} }
@ -80,17 +99,17 @@ class NotificationReceiver : BroadcastReceiver() {
* @param notificationId id of notification * @param notificationId id of notification
*/ */
private fun shareImage(context: Context, path: String, notificationId: Int) { private fun shareImage(context: Context, path: String, notificationId: Int) {
val km = context.getSystemService(Activity.KEYGUARD_SERVICE) as KeyguardManager
// Create intent // Create intent
val intent = Intent(Intent.ACTION_SEND).apply { val intent = Intent(Intent.ACTION_SEND).apply {
val uri = File(path).getUriCompat(context) val uri = File(path).getUriCompat(context)
putExtra(Intent.EXTRA_STREAM, uri) putExtra(Intent.EXTRA_STREAM, uri)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
clipData = ClipData.newRawUri(null, uri)
type = "image/*" type = "image/*"
} }
// Dismiss notification // Close Navigation Shade
dismissNotification(context, notificationId)
// Launch share activity
context.startActivity(intent)
} }
/** /**
@ -101,17 +120,18 @@ class NotificationReceiver : BroadcastReceiver() {
* @param chapterId id of chapter * @param chapterId id of chapter
*/ */
internal fun openChapter(context: Context, mangaId: Long, chapterId: Long) { internal fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
dismissNotification(context, Notifications.ID_NEW_CHAPTERS)
val db = DatabaseHelper(context) val db = DatabaseHelper(context)
val manga = db.getManga(mangaId).executeAsBlocking() val manga = db.getManga(mangaId).executeAsBlocking()
val chapter = db.getChapter(chapterId).executeAsBlocking() val chapter = db.getChapter(chapterId).executeAsBlocking()
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
if (manga != null && chapter != null) { if (manga != null && chapter != null) {
val intent = ReaderActivity.newIntent(context, manga, chapter).apply { val intent = ReaderActivity.newIntent(context, manga, chapter).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
} }
context.startActivity(intent) context.startActivity(intent)
} else { } else {
context.toast(context.getString(R.string.chapter_error)) context.toast(context.getString(R.string.no_next_chapter))
} }
} }
@ -138,9 +158,31 @@ class NotificationReceiver : BroadcastReceiver() {
* @param context context of application * @param context context of application
* @param notificationId id of notification * @param notificationId id of notification
*/ */
private fun cancelLibraryUpdate(context: Context, notificationId: Int) { private fun cancelLibraryUpdate(context: Context) {
LibraryUpdateService.stop(context) LibraryUpdateService.stop(context)
Handler().post { dismissNotification(context, notificationId) } Handler().post { dismissNotification(context, Notifications.ID_LIBRARY_PROGRESS) }
}
/**
* Method called when user wants to stop a library update
*
* @param context context of application
* @param notificationId id of notification
*/
private fun markAsRead(chapterUrls: Array<String>, mangaId: Long) {
val db: DatabaseHelper = Injekt.get()
chapterUrls.forEach {
val chapter = db.getChapter(it, mangaId).executeAsBlocking() ?: return
chapter.read = true
db.updateChapterProgress(chapter).executeAsBlocking()
val preferences: PreferencesHelper = Injekt.get()
if (preferences.removeAfterMarkedAsRead()) {
val manga = db.getManga(mangaId).executeAsBlocking() ?: return
val sourceManager: SourceManager = Injekt.get()
val source = sourceManager.get(manga.source) ?: return
downloadManager.deleteChapters(listOf(chapter), manga, source)
}
}
} }
companion object { companion object {
@ -155,6 +197,9 @@ class NotificationReceiver : BroadcastReceiver() {
// Called to cancel library update. // Called to cancel library update.
private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE" private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"
// Called to cancel library update.
private const val ACTION_MARK_AS_READ = "$ID.$NAME.MARK_AS_READ"
// Called to open chapter // Called to open chapter
private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER" private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER"
@ -179,12 +224,18 @@ class NotificationReceiver : BroadcastReceiver() {
// Value containing notification id. // Value containing notification id.
private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID" private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID"
// Value containing group id.
private const val EXTRA_GROUP_ID = "$ID.$NAME.EXTRA_GROUP_ID"
// Value containing manga id. // Value containing manga id.
private const val EXTRA_MANGA_ID = "$ID.$NAME.EXTRA_MANGA_ID" private const val EXTRA_MANGA_ID = "$ID.$NAME.EXTRA_MANGA_ID"
// Value containing chapter id. // Value containing chapter id.
private const val EXTRA_CHAPTER_ID = "$ID.$NAME.EXTRA_CHAPTER_ID" private const val EXTRA_CHAPTER_ID = "$ID.$NAME.EXTRA_CHAPTER_ID"
// Value containing chapter url.
private const val EXTRA_CHAPTER_URL = "$ID.$NAME.EXTRA_CHAPTER_URL"
/** /**
* Returns a [PendingIntent] that resumes the download of a chapter * Returns a [PendingIntent] that resumes the download of a chapter
* *
@ -246,6 +297,32 @@ class NotificationReceiver : BroadcastReceiver() {
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
/**
* Returns [PendingIntent] that starts a service which dismissed the notification
*
* @param context context of application
* @param notificationId id of notification
* @return [PendingIntent]
*/
internal fun dismissNotification(context: Context, notificationId: Int, groupId: Int? =
null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val groupKey = context.notificationManager.activeNotifications.find {
it.id == notificationId
}?.groupKey
if (groupId != null && groupId != 0 && groupKey != null && groupKey.isNotEmpty()) {
val notifications = context.notificationManager.activeNotifications.filter {
it.groupKey == groupKey
}
if (notifications.size == 2) {
context.notificationManager.cancel(groupId)
return
}
}
}
context.notificationManager.cancel(notificationId)
}
/** /**
* Returns [PendingIntent] that starts a service which cancels the notification and starts a share activity * Returns [PendingIntent] that starts a service which cancels the notification and starts a share activity
* *
@ -255,12 +332,17 @@ class NotificationReceiver : BroadcastReceiver() {
* @return [PendingIntent] * @return [PendingIntent]
*/ */
internal fun shareImagePendingBroadcast(context: Context, path: String, notificationId: Int): PendingIntent { internal fun shareImagePendingBroadcast(context: Context, path: String, notificationId: Int): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply { //val shareIntent = ShareStartingActivity.newIntent(context, path)
action = ACTION_SHARE_IMAGE val shareIntent = Intent(Intent.ACTION_SEND).apply {
putExtra(EXTRA_FILE_LOCATION, path) val uri = File(path).getUriCompat(context)
putExtra(EXTRA_NOTIFICATION_ID, notificationId) putExtra(Intent.EXTRA_STREAM, uri)
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_CLEAR_TOP
clipData = ClipData.newRawUri(null, uri)
type = "image/*"
} }
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) //val shareIntent2 = Intent.createChooser(shareIntent, context.getString(R.string.action_share))
return PendingIntent.getActivity(context, 0, shareIntent, PendingIntent
.FLAG_CANCEL_CURRENT)
} }
/** /**
@ -281,19 +363,55 @@ class NotificationReceiver : BroadcastReceiver() {
} }
/** /**
* Returns [PendingIntent] that start a reader activity containing chapter. * Returns [PendingIntent] that starts a reader activity containing chapter.
* *
* @param context context of application * @param context context of application
* @param manga manga of chapter * @param manga manga of chapter
* @param chapter chapter that needs to be opened * @param chapter chapter that needs to be opened
*/ */
internal fun openChapterPendingBroadcast(context: Context, manga: Manga, chapter: Chapter): PendingIntent { internal fun openChapterPendingActivity(context: Context, manga: Manga, chapter:
val intent = Intent(context, NotificationReceiver::class.java).apply { Chapter): PendingIntent {
action = ACTION_OPEN_CHAPTER val newIntent = ReaderActivity.newIntent(context, manga, chapter)
putExtra(EXTRA_MANGA_ID, manga.id) return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent
putExtra(EXTRA_CHAPTER_ID, chapter.id) .FLAG_UPDATE_CURRENT)
} }
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
/**
* Returns [PendingIntent] that opens the manga info controller.
*
* @param context context of application
* @param manga manga of chapter
*/
internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int):
PendingIntent {
val newIntent =
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(MangaController.MANGA_EXTRA, manga.id)
.putExtra("notificationId", manga.id.hashCode())
.putExtra("groupId", groupId)
return PendingIntent.getActivity(
context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT
)
}
/**
* Returns [PendingIntent] that marks a chapter as read and deletes it if preferred
*
* @param context context of application
* @param manga manga of chapter
*/
internal fun markAsReadPendingBroadcast(context: Context, manga: Manga, chapters:
Array<Chapter>, groupId: Int):
PendingIntent {
val newIntent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_MARK_AS_READ
putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
putExtra(EXTRA_MANGA_ID, manga.id)
putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode())
putExtra(EXTRA_GROUP_ID, groupId)
}
return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
/** /**

View File

@ -23,15 +23,21 @@ object Notifications {
* Notification channel and ids used by the library updater. * Notification channel and ids used by the library updater.
*/ */
const val CHANNEL_LIBRARY = "library_channel" const val CHANNEL_LIBRARY = "library_channel"
const val ID_LIBRARY_PROGRESS = 101 const val ID_LIBRARY_PROGRESS = -101
const val ID_LIBRARY_RESULT = 102
/** /**
* Notification channel and ids used by the downloader. * Notification channel and ids used by the downloader.
*/ */
const val CHANNEL_DOWNLOADER = "downloader_channel" const val CHANNEL_DOWNLOADER = "downloader_channel"
const val ID_DOWNLOAD_CHAPTER = 201 const val ID_DOWNLOAD_CHAPTER = -201
const val ID_DOWNLOAD_CHAPTER_ERROR = 202 const val ID_DOWNLOAD_CHAPTER_ERROR = -202
/**
* Notification channel and ids used by the library updater.
*/
const val CHANNEL_NEW_CHAPTERS = "new_chapters_channel"
const val ID_NEW_CHAPTERS = -301
const val GROUP_NEW_CHAPTERS = "eu.kanade.tachiyomi.NEW_CHAPTERS"
/** /**
* Creates the notification channels introduced in Android Oreo. * Creates the notification channels introduced in Android Oreo.
@ -44,10 +50,16 @@ object Notifications {
val channels = listOf( val channels = listOf(
NotificationChannel(CHANNEL_COMMON, context.getString(R.string.channel_common), NotificationChannel(CHANNEL_COMMON, context.getString(R.string.channel_common),
NotificationManager.IMPORTANCE_LOW), NotificationManager.IMPORTANCE_LOW),
NotificationChannel(CHANNEL_LIBRARY, context.getString(R.string.channel_library), NotificationChannel(CHANNEL_LIBRARY, context.getString(R.string.channel_library_updates),
NotificationManager.IMPORTANCE_LOW), NotificationManager.IMPORTANCE_LOW).apply {
setShowBadge(false)
},
NotificationChannel(CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader), NotificationChannel(CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader),
NotificationManager.IMPORTANCE_LOW) NotificationManager.IMPORTANCE_LOW).apply {
setShowBadge(false)
},
NotificationChannel(CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
NotificationManager.IMPORTANCE_DEFAULT)
) )
context.notificationManager.createNotificationChannels(channels) context.notificationManager.createNotificationChannels(channels)
} }

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

@ -33,7 +33,7 @@ class PreferencesHelper(val context: Context) {
fun clear() = prefs.edit().clear().apply() fun clear() = prefs.edit().clear().apply()
fun theme() = prefs.getInt(Keys.theme, 1) fun theme() = prefs.getInt(Keys.theme, 5)
fun rotation() = rxPrefs.getInteger(Keys.rotation, 1) fun rotation() = rxPrefs.getInteger(Keys.rotation, 1)
@ -65,7 +65,7 @@ class PreferencesHelper(val context: Context) {
fun zoomStart() = rxPrefs.getInteger(Keys.zoomStart, 1) fun zoomStart() = rxPrefs.getInteger(Keys.zoomStart, 1)
fun readerTheme() = rxPrefs.getInteger(Keys.readerTheme, 0) fun readerTheme() = rxPrefs.getInteger(Keys.readerTheme, 2)
fun cropBorders() = rxPrefs.getBoolean(Keys.cropBorders, false) fun cropBorders() = rxPrefs.getBoolean(Keys.cropBorders, false)

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() {
@ -46,7 +46,7 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
} }
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String> { override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String> {
return prefs.getStringSet(key, defValues) return prefs.getStringSet(key, defValues)!!
} }
override fun putStringSet(key: String?, values: MutableSet<String>?) { override fun putStringSet(key: String?, values: MutableSet<String>?) {

View File

@ -1,7 +1,7 @@
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.track.model.TrackSearch
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper

View File

@ -8,6 +8,7 @@ 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
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
@ -18,7 +19,7 @@ import java.util.Calendar
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()
@ -45,7 +46,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
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")
@ -128,7 +129,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
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")
} }
@ -189,7 +190,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
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")
} }
@ -235,7 +236,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
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")
} }

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

@ -8,6 +8,7 @@ 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
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import rx.Completable import rx.Completable
import rx.Observable import rx.Observable
import java.lang.Exception import java.lang.Exception
@ -134,7 +135,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
override fun logout() { override fun logout() {
super.logout() super.logout()
preferences.trackToken(this).delete() preferences.trackToken(this).delete()
networkService.cookieManager.remove(HttpUrl.parse(BASE_URL)!!) networkService.cookieManager.remove(BASE_URL.toHttpUrlOrNull()!!)
} }
val isAuthorized: Boolean val isAuthorized: Boolean
@ -148,9 +149,9 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
private fun checkCookies(): Boolean { private fun checkCookies(): Boolean {
var ckCount = 0 var ckCount = 0
val url = HttpUrl.parse(BASE_URL)!! val url = BASE_URL.toHttpUrlOrNull()!!
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++
} }

View File

@ -12,7 +12,7 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
myanimelist.ensureLoggedIn() myanimelist.ensureLoggedIn()
var request = chain.request() var request = chain.request()
request.body()?.let { 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

@ -11,6 +11,8 @@ 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.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
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 +87,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 +127,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 +174,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 +264,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 body.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
} }
private fun Element.searchTitle() = select("strong").text()!! private fun Element.searchTitle() = select("strong").text()!!

View File

@ -15,6 +15,7 @@ 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.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -22,7 +23,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 +64,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 +121,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 +144,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,12 +2,14 @@ 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 androidx.core.content.ContextCompat
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
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.notificationManager import eu.kanade.tachiyomi.util.notificationManager
class UpdaterJob : Job() { class UpdaterJob : Job() {
@ -27,6 +29,7 @@ class UpdaterJob : Job() {
setContentTitle(context.getString(R.string.app_name)) setContentTitle(context.getString(R.string.app_name))
setContentText(context.getString(R.string.update_check_notification_update_available)) setContentText(context.getString(R.string.update_check_notification_update_available))
setSmallIcon(android.R.drawable.stat_sys_download_done) setSmallIcon(android.R.drawable.stat_sys_download_done)
color = ContextCompat.getColor(context, R.color.colorAccentLight)
// Download action // Download action
addAction(android.R.drawable.stat_sys_download_done, addAction(android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download), context.getString(R.string.action_download),

View File

@ -2,11 +2,13 @@ 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 androidx.core.content.ContextCompat
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
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.notificationManager import eu.kanade.tachiyomi.util.notificationManager
/** /**
@ -95,6 +97,7 @@ internal class UpdaterNotifier(private val context: Context) {
setSmallIcon(android.R.drawable.stat_sys_warning) setSmallIcon(android.R.drawable.stat_sys_warning)
setOnlyAlertOnce(false) setOnlyAlertOnce(false)
setProgress(0, 0, false) setProgress(0, 0, false)
color = ContextCompat.getColor(context, R.color.colorAccentLight)
// Retry action // Retry action
addAction(R.drawable.ic_refresh_grey_24dp_img, addAction(R.drawable.ic_refresh_grey_24dp_img,
context.getString(R.string.action_retry), context.getString(R.string.action_retry),

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

@ -27,7 +27,7 @@ interface GithubService {
} }
} }
@GET("/repos/inorichi/tachiyomi/releases/latest") @GET("/repos/Jays2Kings/tachiyomi/releases/latest")
fun getLatestVersion(): Observable<GithubRelease> fun getLatestVersion(): Observable<GithubRelease>
} }

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.launchNow import eu.kanade.tachiyomi.util.launchNow
import kotlinx.coroutines.experimental.async import kotlinx.coroutines.async
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers

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

@ -39,7 +39,7 @@ class ExtensionInstallActivity : Activity() {
} }
private fun checkInstallationResult(resultCode: Int) { private fun checkInstallationResult(resultCode: Int) {
val downloadId = intent.extras.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID)
val success = resultCode == RESULT_OK val success = resultCode == RESULT_OK
val extensionManager = Injekt.get<ExtensionManager>() val extensionManager = Injekt.get<ExtensionManager>()

View File

@ -7,7 +7,6 @@ import android.content.IntentFilter
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.model.LoadResult
import eu.kanade.tachiyomi.util.launchNow import eu.kanade.tachiyomi.util.launchNow
import kotlinx.coroutines.experimental.async
/** /**
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only * Broadcast receiver that listens for the system's packages installed, updated or removed, and only
@ -91,7 +90,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent) ?: val pkgName = getPackageNameFromIntent(intent) ?:
return LoadResult.Error("Package name not found") return LoadResult.Error("Package name not found")
return async { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await() return ExtensionLoader.loadExtensionFromPkgName(context, pkgName)
} }
/** /**

View File

@ -13,8 +13,8 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.util.Hash import eu.kanade.tachiyomi.util.Hash
import kotlinx.coroutines.experimental.async import kotlinx.coroutines.async
import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.runBlocking
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -121,7 +121,7 @@ internal object ExtensionLoader {
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader) val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS) val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
.split(";") .split(";")
.map { .map {
val sourceClass = it.trim() val sourceClass = it.trim()

View File

@ -14,22 +14,12 @@ class AndroidCookieJar(context: Context) : CookieJar {
private val syncManager by lazy { CookieSyncManager.createInstance(context) } private val syncManager by lazy { CookieSyncManager.createInstance(context) }
init { override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
// Init sync manager when using anything below L
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
syncManager
}
}
override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
val urlString = url.toString() val urlString = url.toString()
for (cookie in cookies) { for (cookie in cookies) {
manager.setCookie(urlString, cookie.toString()) manager.setCookie(urlString, cookie.toString())
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
syncManager.sync()
}
} }
override fun loadForRequest(url: HttpUrl): List<Cookie> { override fun loadForRequest(url: HttpUrl): List<Cookie> {

View File

@ -42,7 +42,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
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())
@ -71,8 +71,8 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
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)
@ -144,7 +144,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
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")

View File

@ -103,7 +103,7 @@ class NetworkHelper(context: Context) {
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
) )

View File

@ -37,7 +37,7 @@ fun Call.asObservable(): Observable<Response> {
} }
override fun isUnsubscribed(): Boolean { override fun isUnsubscribed(): Boolean {
return call.isCanceled return call.isCanceled()
} }
} }
@ -50,7 +50,7 @@ fun Call.asObservableSuccess(): Observable<Response> {
return asObservable().doOnNext { response -> return asObservable().doOnNext { response ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
response.close() response.close()
throw Exception("HTTP error ${response.code()}") throw Exception("HTTP error ${response.code}")
} }
} }
} }
@ -61,7 +61,7 @@ fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListene
.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()

View File

@ -8,7 +8,7 @@ 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 {

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

@ -23,6 +23,7 @@ interface SManga : Serializable {
var initialized: Boolean var initialized: Boolean
fun copyFrom(other: SManga) { fun copyFrom(other: SManga) {
if (other.author != null) if (other.author != null)
author = other.author author = other.author

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,7 +1,7 @@
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 androidx.appcompat.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

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,8 @@ 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 androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.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;

View File

@ -1,7 +1,7 @@
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 androidx.annotation.CallSuper
import android.view.View import android.view.View
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription

View File

@ -1,11 +1,11 @@
package eu.kanade.tachiyomi.ui.base.controller package eu.kanade.tachiyomi.ui.base.controller
import android.support.v4.widget.DrawerLayout import androidx.drawerlayout.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,6 +1,8 @@
package eu.kanade.tachiyomi.ui.base.holder package eu.kanade.tachiyomi.ui.base.holder
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer

View File

@ -1,10 +1,10 @@
package eu.kanade.tachiyomi.ui.base.holder package eu.kanade.tachiyomi.ui.base.holder
import android.support.v7.widget.RecyclerView import androidx.recyclerview.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,7 +1,7 @@
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;

View File

@ -1,7 +1,7 @@
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 androidx.annotation.NonNull;
import android.view.View; import android.view.View;
import com.bluelinelabs.conductor.Controller; import com.bluelinelabs.conductor.Controller;

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.SearchView import androidx.appcompat.widget.SearchView
import android.view.* import android.view.*
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.ui.catalogue.browse.BrowseCatalogueController
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
import eu.kanade.tachiyomi.ui.catalogue.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.catalogue.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController
import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
import kotlinx.android.synthetic.main.catalogue_main_controller.* import kotlinx.android.synthetic.main.catalogue_main_controller.*
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -98,9 +99,10 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
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))
recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
} }
@ -132,7 +134,7 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
/** /**
* Called when item is clicked * Called when item is clicked
*/ */
override fun onItemClick(position: Int): Boolean { override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position) as? SourceItem ?: return false val item = adapter?.getItem(position) as? SourceItem ?: return false
val source = item.source val source = item.source
if (source is LoginSource && !source.isLogged()) { if (source is LoginSource && !source.isLogged()) {
@ -150,7 +152,7 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
* Called when browse is clicked in [CatalogueAdapter] * Called when browse is clicked in [CatalogueAdapter]
*/ */
override fun onBrowseClick(position: Int) { override fun onBrowseClick(position: Int) {
onItemClick(position) onItemClick(view!!, position)
} }
/** /**

View File

@ -5,8 +5,10 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.LocaleHelper import eu.kanade.tachiyomi.util.LocaleHelper
import kotlinx.android.synthetic.main.catalogue_main_controller_card.* import kotlinx.android.synthetic.main.catalogue_main_controller_card.*
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
class LangHolder(view: View, adapter: FlexibleAdapter<*>) : class LangHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) :
BaseFlexibleViewHolder(view, adapter) { BaseFlexibleViewHolder(view, adapter) {
fun bind(item: LangItem) { fun bind(item: LangItem) {

View File

@ -1,8 +1,10 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
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.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
/** /**
@ -22,16 +24,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<*>): LangHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<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<*>, holder: LangHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: LangHolder, position: Int, payloads: MutableList<Any>) {
position: Int, payloads: List<Any?>?) {
holder.bind(this) holder.bind(this)
} }

View File

@ -4,27 +4,27 @@ 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 androidx.recyclerview.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
init { init {
val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider)) val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
divider = a.getDrawable(0) divider = a.getDrawable(0)!!
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 +36,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,8 +1,10 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
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.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
@ -25,16 +27,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<*>): SourceHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): SourceHolder {
return SourceHolder(view, adapter as CatalogueAdapter) return SourceHolder(view, adapter as CatalogueAdapter)
} }
/** /**
* Binds this item to the given view holder. * Binds this item to the given view holder.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: SourceHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: SourceHolder, position: Int, payloads: MutableList<Any>) {
position: Int, payloads: List<Any?>?) {
holder.bind(this) holder.bind(this)
} }

View File

@ -2,12 +2,18 @@ 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.view.LayoutInflater
import android.support.v4.widget.DrawerLayout import android.view.Menu
import android.support.v7.widget.* import android.view.MenuInflater
import android.view.* import android.view.MenuItem
import com.afollestad.materialdialogs.MaterialDialog import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.google.android.material.snackbar.BaseTransientBottomBar
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
@ -22,9 +28,21 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.HeightTopWindowInsetsListener
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.info.MangaWebViewController import eu.kanade.tachiyomi.ui.manga.info.WebViewActivity
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.connectivityManager
import eu.kanade.tachiyomi.util.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.gone
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.marginTop
import eu.kanade.tachiyomi.util.openInBrowser
import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.updateLayoutParams
import eu.kanade.tachiyomi.util.updatePaddingRelative
import eu.kanade.tachiyomi.util.visible
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.catalogue_controller.* import kotlinx.android.synthetic.main.catalogue_controller.*
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
@ -74,7 +92,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.
@ -130,19 +148,19 @@ 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
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)
navView.onSearchClicked = { navView.onSearchClicked = {
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)
} }
@ -152,30 +170,47 @@ open class BrowseCatalogueController(bundle: Bundle) :
presenter.sourceFilters = newFilters presenter.sourceFilters = newFilters
navView.setFilters(presenter.filterItems) navView.setFilters(presenter.filterItems)
} }
drawer.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
val statusScrim = navView.findViewById(R.id.status_bar_scrim) as View
statusScrim.setOnApplyWindowInsetsListener(HeightTopWindowInsetsListener)
val titleView = navView.findViewById(R.id.title_background) as View
val titleMarginTop = titleView.marginTop
navView.doOnApplyWindowInsets { v, insets, padding ->
v.updatePaddingRelative(
bottom = padding.bottom + insets.systemWindowInsetBottom,
end = padding.right + insets.systemWindowInsetRight
)
titleView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = titleMarginTop + insets.systemWindowInsetTop
}
}
return navView return navView
} }
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)
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 {
@ -185,7 +220,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
@ -199,9 +234,9 @@ open class BrowseCatalogueController(bundle: Bundle) :
recycler.adapter = adapter recycler.adapter = adapter
catalogue_view.addView(recycler, 1) catalogue_view.addView(recycler, 1)
recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
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
} }
@ -272,7 +307,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)
@ -288,9 +323,10 @@ open class BrowseCatalogueController(bundle: Bundle) :
private fun openInWebView() { private fun openInWebView() {
val source = presenter.source as? HttpSource ?: return val source = presenter.source as? HttpSource ?: return
val activity = activity ?: return
router.pushController(MangaWebViewController(source.id, source.baseUrl) val intent = WebViewActivity.newIntent(activity, source.id, source.baseUrl, presenter
.withFadeTransaction()) .source.name)
startActivity(intent)
} }
/** /**
@ -344,7 +380,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
val message = if (error is NoResultsException) "No results found" else (error.message ?: "") val message = if (error is NoResultsException) "No results found" else (error.message ?: "")
snack?.dismiss() snack?.dismiss()
snack = catalogue_view?.snack(message, Snackbar.LENGTH_INDEFINITE) { snack = catalouge_layout?.snack(message, Snackbar.LENGTH_INDEFINITE) {
setAction(R.string.action_retry) { setAction(R.string.action_retry) {
// If not the first page, show bottom progress bar. // If not the first page, show bottom progress bar.
if (adapter.mainItemCount > 0) { if (adapter.mainItemCount > 0) {
@ -464,7 +500,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
* @param position the position of the element clicked. * @param position the position of the element clicked.
* @return true if the item should be selected, false otherwise. * @return true if the item should be selected, false otherwise.
*/ */
override fun onItemClick(position: Int): Boolean { override fun onItemClick(view: View?, position: Int): Boolean {
val item = adapter?.getItem(position) as? CatalogueItem ?: return false val item = adapter?.getItem(position) as? CatalogueItem ?: return false
router.pushController(MangaController(item.manga, true).withFadeTransaction()) router.pushController(MangaController(item.manga, true).withFadeTransaction())
@ -481,31 +517,41 @@ open class BrowseCatalogueController(bundle: Bundle) :
* @param position the position of the element clicked. * @param position the position of the element clicked.
*/ */
override fun onItemLongClick(position: Int) { override fun onItemLongClick(position: Int) {
val activity = activity ?: return
val manga = (adapter?.getItem(position) as? CatalogueItem?)?.manga ?: return val manga = (adapter?.getItem(position) as? CatalogueItem?)?.manga ?: return
snack?.dismiss()
if (manga.favorite) { if (manga.favorite) {
MaterialDialog.Builder(activity)
.items(activity.getString(R.string.remove_from_library))
.itemsCallback { _, _, which, _ ->
when (which) {
0 -> {
presenter.changeMangaFavorite(manga) presenter.changeMangaFavorite(manga)
adapter?.notifyItemChanged(position) adapter?.notifyItemChanged(position)
activity?.toast(activity?.getString(R.string.manga_removed_library)) snack = catalouge_layout?.snack(R.string.manga_removed_library, Snackbar.LENGTH_INDEFINITE) {
setAction(R.string.action_undo) {
if (!manga.favorite) addManga(manga, position)
} }
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (!manga.favorite) presenter.confirmDeletion(manga)
} }
}.show() })
}
(activity as? MainActivity)?.setUndoSnackBar(snack)
} else { } else {
addManga(manga, position)
snack = catalouge_layout?.snack(R.string.manga_added_library)
}
}
private fun addManga(manga: Manga, position: Int) {
presenter.changeMangaFavorite(manga) presenter.changeMangaFavorite(manga)
adapter?.notifyItemChanged(position) adapter?.notifyItemChanged(position)
val categories = presenter.getCategories() val categories = presenter.getCategories()
val defaultCategory = categories.find { it.id == preferences.defaultCategory() } val defaultCategoryId = preferences.defaultCategory()
if (defaultCategory != null) { val defaultCategory = categories.find { it.id == defaultCategoryId }
presenter.moveMangaToCategory(manga, defaultCategory) when {
} else if (categories.size <= 1) { // default or the one from the user defaultCategory != null -> presenter.moveMangaToCategory(manga, defaultCategory)
presenter.moveMangaToCategory(manga, categories.firstOrNull()) defaultCategoryId == 0 || categories.isEmpty() -> // 'Default' or no category
} else { presenter.moveMangaToCategory(manga, null)
else -> {
val ids = presenter.getMangaCategoryIds(manga) val ids = presenter.getMangaCategoryIds(manga)
val preselected = ids.mapNotNull { id -> val preselected = ids.mapNotNull { id ->
categories.indexOfFirst { it.id == id }.takeIf { it != -1 } categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
@ -514,9 +560,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected) ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
.showDialog(router) .showDialog(router)
} }
activity?.toast(activity?.getString(R.string.manga_added_library))
} }
} }
/** /**

View File

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -253,12 +254,15 @@ open class BrowseCataloguePresenter(
*/ */
fun changeMangaFavorite(manga: Manga) { fun changeMangaFavorite(manga: Manga) {
manga.favorite = !manga.favorite manga.favorite = !manga.favorite
if (!manga.favorite) {
coverCache.deleteFromCache(manga.thumbnail_url)
}
db.insertManga(manga).executeAsBlocking() db.insertManga(manga).executeAsBlocking()
} }
fun confirmDeletion(manga: Manga) {
coverCache.deleteFromCache(manga.thumbnail_url)
val downloadManager: DownloadManager = Injekt.get()
downloadManager.deleteManga(manga,source)
}
/** /**
* Changes the active display mode. * Changes the active display mode.
*/ */
@ -316,9 +320,9 @@ open class BrowseCataloguePresenter(
} }
/** /**
* Get the default, and user categories. * Get user categories.
* *
* @return List of categories, default plus user categories * @return List of categories, not including the default category
*/ */
fun getCategories(): List<Category> { fun getCategories(): List<Category> {
return db.getCategories().executeAsBlocking() return db.getCategories().executeAsBlocking()

View File

@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.widget.StateImageViewTarget import eu.kanade.tachiyomi.widget.StateImageViewTarget
import kotlinx.android.synthetic.main.catalogue_grid_item.* import kotlinx.android.synthetic.main.catalogue_grid_item.*
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
/** /**
* Class used to hold the displayed data of a manga in the catalogue, like the cover or the title. * Class used to hold the displayed data of a manga in the catalogue, like the cover or the title.
@ -16,7 +18,7 @@ import kotlinx.android.synthetic.main.catalogue_grid_item.*
* @param adapter the adapter handling this holder. * @param adapter the adapter handling this holder.
* @constructor creates a new catalogue holder. * @constructor creates a new catalogue holder.
*/ */
class CatalogueGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) : class CatalogueGridHolder(private val view: View, private val adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) :
CatalogueHolder(view, adapter) { CatalogueHolder(view, adapter) {
/** /**

View File

@ -4,6 +4,8 @@ import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
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 androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
/** /**
* Generic class used to hold the displayed data of a manga in the catalogue. * Generic class used to hold the displayed data of a manga in the catalogue.
@ -11,7 +13,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
* @param view the inflated view for this holder. * @param view the inflated view for this holder.
* @param adapter the adapter handling this holder. * @param adapter the adapter handling this holder.
*/ */
abstract class CatalogueHolder(view: View, adapter: FlexibleAdapter<*>) : abstract class CatalogueHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) :
BaseFlexibleViewHolder(view, adapter) { BaseFlexibleViewHolder(view, adapter) {
/** /**

View File

@ -12,6 +12,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga
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.*
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference<Boolean>) : class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference<Boolean>) :
AbstractFlexibleItem<CatalogueHolder>() { AbstractFlexibleItem<CatalogueHolder>() {
@ -23,7 +25,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<*>): CatalogueHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<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 {
@ -38,10 +40,10 @@ class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference<Bo
} }
} }
override fun bindViewHolder(adapter: FlexibleAdapter<*>, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: CatalogueHolder, holder: CatalogueHolder,
position: Int, position: Int,
payloads: List<Any?>?) { payloads: MutableList<Any?>?) {
holder.onSetValues(manga) holder.onSetValues(manga)
} }

View File

@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import kotlinx.android.synthetic.main.catalogue_list_item.* import kotlinx.android.synthetic.main.catalogue_list_item.*
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
/** /**
* Class used to hold the displayed data of a manga in the catalogue, like the cover or the title. * Class used to hold the displayed data of a manga in the catalogue, like the cover or the title.
@ -16,7 +18,7 @@ import kotlinx.android.synthetic.main.catalogue_list_item.*
* @param adapter the adapter handling this holder. * @param adapter the adapter handling this holder.
* @constructor creates a new catalogue holder. * @constructor creates a new catalogue holder.
*/ */
class CatalogueListHolder(private val view: View, adapter: FlexibleAdapter<*>) : class CatalogueListHolder(private val view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) :
CatalogueHolder(view, adapter) { CatalogueHolder(view, adapter) {
private val favoriteColor = view.context.getResourceColor(android.R.attr.textColorHint) private val favoriteColor = view.context.getResourceColor(android.R.attr.textColorHint)

View File

@ -3,8 +3,10 @@ package eu.kanade.tachiyomi.ui.catalogue.browse
import android.view.View import android.view.View
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.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.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -17,11 +19,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<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any>) {
holder.progressBar.visibility = View.GONE holder.progressBar.visibility = View.GONE
holder.progressMessage.visibility = View.GONE holder.progressMessage.visibility = View.GONE
@ -40,7 +42,7 @@ class ProgressItem : AbstractFlexibleItem<ProgressItem.Holder>() {
return this === other return this === other
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter) {
val progressBar: ProgressBar = view.findViewById(R.id.progress_bar) val progressBar: ProgressBar = view.findViewById(R.id.progress_bar)
val progressMessage: TextView = view.findViewById(R.id.progress_message) val progressMessage: TextView = view.findViewById(R.id.progress_message)

View File

@ -2,8 +2,10 @@ package eu.kanade.tachiyomi.ui.catalogue.filter
import android.view.View import android.view.View
import android.widget.CheckBox import android.widget.CheckBox
import androidx.recyclerview.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.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
@ -14,11 +16,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<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<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
@ -38,7 +40,7 @@ open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem<Chec
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter) {
val check: CheckBox = itemView.findViewById(R.id.nav_view_item) val check: CheckBox = itemView.findViewById(R.id.nav_view_item)
} }

View File

@ -10,6 +10,8 @@ import eu.davidea.viewholders.ExpandableViewHolder
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
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<GroupItem.Holder, ISectionable<*, *>>() { class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<GroupItem.Holder, ISectionable<*, *>>() {
@ -25,11 +27,11 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<Grou
return 101 return 101
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any?>?) {
holder.title.text = filter.name holder.title.text = filter.name
holder.icon.setVectorCompat(if (isExpanded) holder.icon.setVectorCompat(if (isExpanded)
@ -52,7 +54,7 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<Grou
} }
open class Holder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter, true) { open class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : ExpandableViewHolder(view, adapter, true) {
val title: TextView = itemView.findViewById(R.id.title) val title: TextView = itemView.findViewById(R.id.title)
val icon: ImageView = itemView.findViewById(R.id.expand_icon) val icon: ImageView = itemView.findViewById(R.id.expand_icon)

View File

@ -1,13 +1,15 @@
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 com.google.android.material.R
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
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.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem<HeaderItem.Holder>() { class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem<HeaderItem.Holder>() {
@ -16,11 +18,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<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any?>?) {
val view = holder.itemView as TextView val view = holder.itemView as TextView
view.text = filter.name view.text = filter.name
} }
@ -35,5 +37,5 @@ class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem<HeaderItem.Hold
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter)
} }

View File

@ -10,6 +10,8 @@ import eu.davidea.viewholders.FlexibleViewHolder
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.widget.IgnoreFirstSpinnerListener import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<SelectItem.Holder>() { open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<SelectItem.Holder>() {
@ -17,11 +19,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<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any?>?) {
holder.text.text = filter.name + ": " holder.text.text = filter.name + ": "
val spinner = holder.spinner val spinner = holder.spinner
@ -46,7 +48,7 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<Selec
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter) {
val text: TextView = itemView.findViewById(R.id.nav_view_item_text) val text: TextView = itemView.findViewById(R.id.nav_view_item_text)
val spinner: Spinner = itemView.findViewById(R.id.nav_view_item) val spinner: Spinner = itemView.findViewById(R.id.nav_view_item)

View File

@ -1,10 +1,12 @@
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 com.google.android.material.R
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
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.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
@ -15,11 +17,12 @@ 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<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: Holder, position: Int, payloads: MutableList<Any?>?) {
} }
@ -33,5 +36,5 @@ class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem<Separator
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter)
} }

View File

@ -1,8 +1,10 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
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.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
@ -22,11 +24,11 @@ class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGrou
return 100 return 100
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any?>?) {
holder.title.text = filter.name holder.title.text = filter.name
holder.icon.setVectorCompat(if (isExpanded) holder.icon.setVectorCompat(if (isExpanded)
@ -48,5 +50,5 @@ class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGrou
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : GroupItem.Holder(view, adapter) class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : GroupItem.Holder(view, adapter)
} }

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.graphics.drawable.VectorDrawableCompat import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
import android.view.View import android.view.View
import android.widget.CheckedTextView import android.widget.CheckedTextView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -10,6 +10,8 @@ import eu.davidea.viewholders.FlexibleViewHolder
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.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) { class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) {
@ -21,11 +23,11 @@ class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem
return 102 return 102
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any?>?) {
val view = holder.text val view = holder.text
view.text = name view.text = name
val filter = group.filter val filter = group.filter
@ -66,7 +68,7 @@ class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem
return result return result
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter) {
val text: CheckedTextView = itemView.findViewById(R.id.nav_view_item) val text: CheckedTextView = itemView.findViewById(R.id.nav_view_item)
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.design.widget.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -9,6 +9,8 @@ import eu.davidea.viewholders.FlexibleViewHolder
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.widget.SimpleTextWatcher import eu.kanade.tachiyomi.widget.SimpleTextWatcher
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() { open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() {
@ -16,11 +18,11 @@ 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<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any?>?) {
holder.wrapper.hint = filter.name holder.wrapper.hint = filter.name
holder.edit.setText(filter.state) holder.edit.setText(filter.state)
holder.edit.addTextChangedListener(object : SimpleTextWatcher() { holder.edit.addTextChangedListener(object : SimpleTextWatcher() {
@ -40,7 +42,7 @@ open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Hol
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter) {
val wrapper: TextInputLayout = itemView.findViewById(R.id.nav_view_item_wrapper) val wrapper: TextInputLayout = itemView.findViewById(R.id.nav_view_item_wrapper)
val edit: EditText = itemView.findViewById(R.id.nav_view_item) val edit: EditText = itemView.findViewById(R.id.nav_view_item)

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import android.support.design.R import com.google.android.material.R
import android.support.graphics.drawable.VectorDrawableCompat import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import android.view.View import android.view.View
import android.widget.CheckedTextView import android.widget.CheckedTextView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.dpToPx import eu.kanade.tachiyomi.util.dpToPx
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.R as TR import eu.kanade.tachiyomi.R as TR
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriStateItem.Holder>() { open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriStateItem.Holder>() {
@ -22,11 +24,11 @@ open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriS
return 103 return 103
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): 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<*>, holder: Holder, position: Int, payloads: List<Any?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any?>?) {
val view = holder.text val view = holder.text
view.text = filter.name view.text = filter.name
@ -61,7 +63,7 @@ open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriS
return filter.hashCode() return filter.hashCode()
} }
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter) {
val text: CheckedTextView = itemView.findViewById(TR.id.nav_view_item) val text: CheckedTextView = itemView.findViewById(TR.id.nav_view_item)

View File

@ -2,7 +2,7 @@ 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 androidx.recyclerview.widget.RecyclerView
import android.util.SparseArray import android.util.SparseArray
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -19,12 +19,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) {
super.onBindViewHolder(holder, position, payloads) super.onBindViewHolder(holder, position)
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)
} }
@ -38,7 +38,7 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) :
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
bundle = savedInstanceState.getBundle(HOLDER_BUNDLE_KEY) bundle = savedInstanceState.getBundle(HOLDER_BUNDLE_KEY)!!
} }
/** /**
@ -47,7 +47,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)
@ -59,7 +59,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

@ -5,6 +5,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
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 androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
class CatalogueSearchCardItem(val manga: Manga) : AbstractFlexibleItem<CatalogueSearchCardHolder>() { class CatalogueSearchCardItem(val manga: Manga) : AbstractFlexibleItem<CatalogueSearchCardHolder>() {
@ -12,12 +14,12 @@ class CatalogueSearchCardItem(val manga: Manga) : AbstractFlexibleItem<Catalogue
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<*>): 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<*>, holder: CatalogueSearchCardHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: CatalogueSearchCardHolder,
position: Int, payloads: List<Any?>?) { position: Int, payloads: MutableList<Any?>?) {
holder.bind(manga) holder.bind(manga)
} }

View File

@ -1,8 +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 androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.SearchView import androidx.appcompat.widget.SearchView
import android.view.* import android.view.*
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
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener
import kotlinx.android.synthetic.main.catalogue_global_search_controller.* import kotlinx.android.synthetic.main.catalogue_global_search_controller.*
/** /**
@ -131,8 +132,9 @@ 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
recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
@ -169,12 +171,29 @@ open class CatalogueSearchController(
return null return null
} }
override fun handleBack(): Boolean {
return if (extensionFilter != null) {
activity?.finishAffinity()
true
} else super.handleBack()
}
/** /**
* Add search result to adapter. * Add search result to adapter.
* *
* @param searchResult result of search. * @param searchResult result of search.
*/ */
fun setItems(searchResult: List<CatalogueSearchItem>) { fun setItems(searchResult: List<CatalogueSearchItem>) {
if (extensionFilter != null) {
val results = searchResult.first().results
if (results != null && results.size == 1) {
val manga = results.first().manga
router.pushController(MangaController(manga,true,fromExtension = true)
.withFadeTransaction()
)
return
}
}
adapter?.updateDataSet(searchResult) adapter?.updateDataSet(searchResult)
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.ui.catalogue.global_search package eu.kanade.tachiyomi.ui.catalogue.global_search
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.view.View import android.view.View
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
@ -29,7 +29,8 @@ 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
} }

View File

@ -5,6 +5,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.items.IFlexible
/** /**
* Item that contains search result information. * Item that contains search result information.
@ -30,15 +32,15 @@ class CatalogueSearchItem(val source: CatalogueSource, val results: List<Catalog
* *
* @return holder of view. * @return holder of view.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): CatalogueSearchHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<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<*>, holder: CatalogueSearchHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: CatalogueSearchHolder,
position: Int, payloads: List<Any?>?) { position: Int, payloads: MutableList<Any?>?) {
holder.bind(this) holder.bind(this)
} }

View File

@ -1,7 +1,7 @@
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 androidx.drawerlayout.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 +28,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 com.google.android.material.snackbar.Snackbar
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.support.v7.view.ActionMode import androidx.appcompat.view.ActionMode
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.* import android.view.*
import com.google.android.material.snackbar.BaseTransientBottomBar
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
@ -13,7 +12,13 @@ import eu.davidea.flexibleadapter.helpers.UndoHelper
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.marginBottom
import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.util.updateLayoutParams
import eu.kanade.tachiyomi.util.updatePaddingRelative
import kotlinx.android.synthetic.main.categories_controller.* import kotlinx.android.synthetic.main.categories_controller.*
/** /**
@ -41,7 +46,7 @@ class CategoryController : NucleusController<CategoryPresenter>(),
/** /**
* Undo helper used for restoring a deleted category. * Undo helper used for restoring a deleted category.
*/ */
private var undoHelper: UndoHelper? = null private var snack: Snackbar? = null
/** /**
* Creates the presenter for this controller. Not to be manually called. * Creates the presenter for this controller. Not to be manually called.
@ -74,7 +79,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
@ -83,6 +88,14 @@ class CategoryController : NucleusController<CategoryPresenter>(),
fab.clicks().subscribeUntilDestroy { fab.clicks().subscribeUntilDestroy {
CategoryCreateDialog(this@CategoryController).showDialog(router, null) CategoryCreateDialog(this@CategoryController).showDialog(router, null)
} }
val fabBaseMarginBottom = fab?.marginBottom ?: 0
recycler.doOnApplyWindowInsets { v, insets, padding ->
v.updatePaddingRelative(bottom = padding.bottom + insets.systemWindowInsetBottom)
fab?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom
}
}
} }
/** /**
@ -92,8 +105,9 @@ class CategoryController : NucleusController<CategoryPresenter>(),
*/ */
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
// Manually call callback to delete categories if required // Manually call callback to delete categories if required
undoHelper?.onDeleteConfirmed(Snackbar.Callback.DISMISS_EVENT_MANUAL) snack?.dismiss()
undoHelper = null confirmDelete()
snack = null
actionMode = null actionMode = null
adapter = null adapter = null
super.onDestroyView(view) super.onDestroyView(view)
@ -166,10 +180,22 @@ class CategoryController : NucleusController<CategoryPresenter>(),
when (item.itemId) { when (item.itemId) {
R.id.action_delete -> { R.id.action_delete -> {
undoHelper = UndoHelper(adapter, this) adapter.removeItems(adapter.selectedPositions)
undoHelper?.start(adapter.selectedPositions, view!!, snack =
R.string.snack_categories_deleted, R.string.action_undo, 3000) view?.snack(R.string.snack_categories_deleted, Snackbar.LENGTH_INDEFINITE) {
var undoing = false
setAction(R.string.action_undo) {
adapter.restoreDeletedItems()
undoing = true
}
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (!undoing) confirmDelete()
}
})
}
(activity as? MainActivity)?.setUndoSnackBar(snack)
mode.finish() mode.finish()
} }
R.id.action_edit -> { R.id.action_edit -> {
@ -205,9 +231,9 @@ class CategoryController : NucleusController<CategoryPresenter>(),
* @param position The position of the clicked item. * @param position The position of the clicked item.
* @return true if this click should enable selection mode. * @return true if this click should enable selection mode.
*/ */
override fun onItemClick(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 {
@ -270,7 +296,7 @@ class CategoryController : NucleusController<CategoryPresenter>(),
*/ */
override fun onActionCanceled(action: Int, positions: MutableList<Int>?) { override fun onActionCanceled(action: Int, positions: MutableList<Int>?) {
adapter?.restoreDeletedItems() adapter?.restoreDeletedItems()
undoHelper = null snack = null
} }
/** /**
@ -282,7 +308,13 @@ class CategoryController : NucleusController<CategoryPresenter>(),
override fun onActionConfirmed(action: Int, event: Int) { override fun onActionConfirmed(action: Int, event: Int) {
val adapter = adapter ?: return val adapter = adapter ?: return
presenter.deleteCategories(adapter.deletedItems.map { it.category }) presenter.deleteCategories(adapter.deletedItems.map { it.category })
undoHelper = null snack = null
}
fun confirmDelete() {
val adapter = adapter ?: return
presenter.deleteCategories(adapter.deletedItems.map { it.category })
snack = null
} }
/** /**

View File

@ -1,8 +1,10 @@
package eu.kanade.tachiyomi.ui.category package eu.kanade.tachiyomi.ui.category
import android.view.View import android.view.View
import androidx.recyclerview.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.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
@ -29,7 +31,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<*>): CategoryHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): CategoryHolder {
return CategoryHolder(view, adapter as CategoryAdapter) return CategoryHolder(view, adapter as CategoryAdapter)
} }
@ -41,11 +43,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<*>, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: CategoryHolder, position: Int, payloads: MutableList<Any>) {
holder: CategoryHolder,
position: Int,
payloads: List<Any?>?) {
holder.bind(category) holder.bind(category)
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.ui.download package eu.kanade.tachiyomi.ui.download
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
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
@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.util.inflate
* *
* @param context the context of the fragment containing this adapter. * @param context the context of the fragment containing this adapter.
*/ */
class DownloadAdapter : RecyclerView.Adapter<DownloadHolder>() { class DownloadAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<DownloadHolder>() {
private var items = emptyList<Download>() private var items = emptyList<Download>()

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