diff --git a/app/build.gradle b/app/build.gradle index 141e7ec695..be30aff7f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,17 +29,17 @@ ext { } android { - compileSdkVersion 27 - buildToolsVersion '28.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.2' publishNonDefault true defaultConfig { applicationId "eu.kanade.tachiyomi" - minSdkVersion 16 - targetSdkVersion 27 - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - versionCode 41 - versionName "0.8.4" + minSdkVersion 21 + targetSdkVersion 29 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + versionCode 42 + versionName "0.9.0" buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\"" buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\"" @@ -60,6 +60,9 @@ android { versionNameSuffix "-${getCommitCount()}" applicationIdSuffix ".debug" } + release { + applicationIdSuffix = '.j2k' + } } flavorDimensions "default" @@ -73,7 +76,6 @@ android { dimension "default" } dev { - minSdkVersion 21 resConfigs "en", "xxhdpi" dimension "default" } @@ -92,6 +94,15 @@ android { checkReleaseBuilds false } + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = "1.8" + } + } dependencies { @@ -101,35 +112,35 @@ dependencies { implementation 'com.github.inorichi:junrar-android:634c1f5' // Android support library - final support_library_version = '27.0.2' - implementation "com.android.support:support-v4:$support_library_version" - implementation "com.android.support:appcompat-v7:$support_library_version" - implementation "com.android.support:cardview-v7:$support_library_version" - implementation "com.android.support:design:$support_library_version" - implementation "com.android.support:recyclerview-v7:$support_library_version" - implementation "com.android.support:preference-v7:$support_library_version" - implementation "com.android.support:support-annotations:$support_library_version" - implementation "com.android.support:customtabs:$support_library_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.preference:preference:1.1.0' + implementation 'androidx.annotation:annotation:1.1.0' + implementation 'androidx.browser:browser:1.0.0' - 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 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.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 - implementation "com.squareup.okhttp3:okhttp:3.10.0" - implementation 'com.squareup.okio:okio:1.14.0' + final okhttp_version = "4.2.1" + implementation "com.squareup.okhttp3:okhttp:$okhttp_version" + implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" + implementation 'com.squareup.okio:okio:2.4.0' // REST - final retrofit_version = '2.3.0' + final retrofit_version = '2.6.2' implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version" @@ -146,17 +157,17 @@ dependencies { implementation 'com.github.inorichi:unifile:e9ee588' // HTML parser - implementation 'org.jsoup:jsoup:1.10.2' + implementation 'org.jsoup:jsoup:1.12.1' // Job scheduling 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 implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0' // 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-sqlite:8be19de@aar' implementation 'io.requery:sqlite-android:3.25.2' @@ -170,16 +181,16 @@ dependencies { implementation "com.github.inorichi.injekt:injekt-core:65b0440" // 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:okhttp3-integration:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version" // Transformations - implementation 'jp.wasabeef:glide-transformations:3.1.1' + implementation 'jp.wasabeef:glide-transformations:4.0.0' // Logging - implementation 'com.jakewharton.timber:timber:4.6.1' + implementation 'com.jakewharton.timber:timber:4.7.1' // Crash reports implementation 'ch.acra:acra:4.9.2' @@ -190,24 +201,24 @@ dependencies { // UI implementation 'com.dmitrymalkovich.android:material-design-dimens:1.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-ui:1.0.0-b1' + implementation 'eu.davidea:flexible-adapter:5.1.0' + implementation 'eu.davidea:flexible-adapter-ui:1.0.0' implementation 'com.nononsenseapps:filepicker:2.5.2' implementation 'com.github.amulyakhare:TextDrawable:558677e' implementation 'com.afollestad.material-dialogs:core:0.9.6.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 'me.gujun.android.taggroup:library:1.4@aar' - implementation 'com.github.chrisbanes:PhotoView:2.1.3' - implementation 'com.github.inorichi:DirectionalViewPager:3acc51a' + implementation 'com.github.chrisbanes:PhotoView:2.3.0' + implementation 'com.github.carlosesco:DirectionalViewPager:a844dbca0a' // Conductor implementation 'com.bluelinelabs:conductor:2.1.5' implementation ("com.bluelinelabs:conductor-support:2.1.5") { exclude group: "com.android.support" } - implementation 'com.github.inorichi:conductor-support-preference:27.0.2' + implementation project(":j2k-preference") // RxBindings final rxbindings_version = '1.0.1' @@ -228,18 +239,19 @@ dependencies { 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-android:$coroutines_version" } buildscript { - ext.kotlin_version = '1.2.71' + ext.kotlin_version = '1.3.50' repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } } @@ -247,12 +259,6 @@ repositories { mavenCentral() } -kotlin { - experimental { - coroutines 'enable' - } -} - androidExtensions { experimental = true } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e13d89ba7c..3861b81dac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,10 @@ + + + + android:theme="@style/Theme.Tachiyomi" + android:networkSecurityConfig="@xml/network_security_config"> @@ -43,6 +47,9 @@ + diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index ef4564f777..7a2688b924 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi import android.app.Application import android.content.Context import android.content.res.Configuration -import android.support.multidex.MultiDex +import androidx.multidex.MultiDex import com.evernote.android.job.JobManager import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 3a46947180..7fc1bcaf94 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -39,7 +39,7 @@ object Migrations { if (oldDir.exists()) { val destDir = context.getExternalFilesDir("covers") if (destDir != null) { - oldDir.listFiles().forEach { + oldDir.listFiles()?.forEach { it.renameTo(File(destDir, it.name)) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt index 0d993a7019..d7095642db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt @@ -57,7 +57,8 @@ class BackupCreateService : IntentService(NAME) { val uri = intent.getParcelableExtra(BackupConst.EXTRA_URI) val flags = intent.getIntExtra(EXTRA_FLAGS, 0) // Create backup - backupManager.createBackup(uri, flags, false) + if (uri != null) + backupManager.createBackup(uri, flags, false) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt index db6866e289..365dd8804f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt @@ -160,9 +160,9 @@ class BackupRestoreService : Service() { * @return the start value of the command. */ 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(BackupConst.EXTRA_URI) + val uri = intent.getParcelableExtra(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY // Unsubscribe from any previous subscription if needed. subscription?.unsubscribe() @@ -175,7 +175,7 @@ class BackupRestoreService : Service() { .subscribeOn(Schedulers.from(executor)) .subscribe() - return Service.START_NOT_STICKY + return START_NOT_STICKY } /** @@ -189,7 +189,7 @@ class BackupRestoreService : Service() { return Observable.just(Unit) .map { - val reader = JsonReader(contentResolver.openInputStream(uri).bufferedReader()) + val reader = JsonReader(contentResolver.openInputStream(uri)!!.bufferedReader()) val json = JsonParser().parse(reader).asJsonObject // Get parser version diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index 4868dec1ec..f606e6640c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.saveTo import okhttp3.Response import okio.Okio +import okio.buffer +import okio.sink import rx.Observable import uy.kohesive.injekt.injectLazy import java.io.File @@ -126,7 +128,7 @@ class ChapterCache(private val context: Context) { editor = diskCache.edit(key) ?: return // Write chapter urls to cache. - Okio.buffer(Okio.sink(editor.newOutputStream(0))).use { + editor.newOutputStream(0).sink().buffer().use { it.write(cachedValue.toByteArray()) it.flush() } @@ -186,12 +188,12 @@ class ChapterCache(private val context: Context) { editor = diskCache.edit(key) ?: throw IOException("Unable to edit key") // Get OutputStream and write image with Okio. - response.body()!!.source().saveTo(editor.newOutputStream(0)) + response.body!!.source().saveTo(editor.newOutputStream(0)) diskCache.flush() editor.commit() } finally { - response.body()?.close() + response.body?.close() editor?.abortUnlessCommitted() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt index 4586f8c182..96db0846bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt @@ -1,7 +1,11 @@ package eu.kanade.tachiyomi.data.cache import android.content.Context +import eu.kanade.tachiyomi.data.database.models.Manga 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.IOException import java.io.InputStream @@ -60,8 +64,7 @@ class CoverCache(private val context: Context) { return false // Remove file. - val file = getCoverFile(thumbnailUrl!!) + val file = getCoverFile(thumbnailUrl) return file.exists() && file.delete() } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt index 6148e49ae7..711cefbcb3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.data.database -import android.arch.persistence.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.SupportSQLiteOpenHelper import android.content.Context import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite import eu.kanade.tachiyomi.data.database.mappers.* diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt index cf65b6a1d6..e8c1a4ed3a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.database -import android.arch.persistence.db.SupportSQLiteDatabase -import android.arch.persistence.db.SupportSQLiteOpenHelper +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.SupportSQLiteOpenHelper import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper @@ -18,7 +18,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) { /** * Version of the database. */ - const val DATABASE_VERSION = 8 + const val DATABASE_VERSION = 9 } override fun onCreate(db: SupportSQLiteDatabase) = with(db) { @@ -67,6 +67,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) { db.execSQL(MangaTable.createLibraryIndexQuery) db.execSQL(ChapterTable.createUnreadChaptersIndexQuery) } + if (oldVersion < 9) { + db.execSQL(MangaTable.addHideTitle) + } } override fun onConfigure(db: SupportSQLiteDatabase) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt index 9ad72908fe..ac89bbc209 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt @@ -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_FAVORITE 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_INITIALIZED import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE @@ -61,6 +62,7 @@ class MangaPutResolver : DefaultPutResolver() { put(COL_LAST_UPDATE, obj.last_update) put(COL_INITIALIZED, obj.initialized) put(COL_VIEWER, obj.viewer) + put(COL_HIDE_TITLE, obj.hide_title) put(COL_CHAPTER_FLAGS, obj.chapter_flags) } } @@ -82,6 +84,7 @@ interface BaseMangaGetResolver { initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1 viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER)) chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS)) + hide_title = cursor.getInt(cursor.getColumnIndex(COL_HIDE_TITLE)) == 1 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index 1782662dce..c7dff69dfa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -16,6 +16,8 @@ interface Manga : SManga { var chapter_flags: Int + var hide_title: Boolean + fun setChapterOrder(order: Int) { setFlags(order, SORT_MASK) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt index 977864c249..b5a4754486 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.data.database.models +import eu.kanade.tachiyomi.source.model.SManga + open class MangaImpl : Manga { override var id: Long? = null @@ -32,6 +34,14 @@ open class MangaImpl : Manga { 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 { if (this === other) return true if (other == null || javaClass != other.javaClass) return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt index b6cb58670d..c10d0b9367 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt @@ -52,6 +52,14 @@ interface ChapterQueries : DbProvider { .build()) .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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt index fc17e36ec2..e9650347aa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -22,10 +22,10 @@ interface HistoryQueries : DbProvider { * Returns history of recent manga containing last read chapter * @param date recent date range */ - fun getRecentManga(date: Date) = db.get() + fun getRecentManga(date: Date, offset: Int = 0) = db.get() .listOfObjects(MangaChapterHistory::class.java) .withQuery(RawQuery.builder() - .query(getRecentMangasQuery()) + .query(getRecentMangasQuery(offset)) .args(date.time) .observesTables(HistoryTable.TABLE) .build()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt index a64a097925..0da34ffe0d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt @@ -82,6 +82,11 @@ interface MangaQueries : DbProvider { .withPutResolver(MangaViewerPutResolver()) .prepare() + fun updateMangaHideTitle(manga: Manga) = db.put() + .`object`(manga) + .withPutResolver(MangaHideTitlePutResolver()) + .prepare() + fun updateMangaTitle(manga: Manga) = db.put() .`object`(manga) .withPutResolver(MangaTitlePutResolver()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index daa5c48fd0..83a9866b09 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -47,7 +47,7 @@ fun getRecentsQuery() = """ * and are read after the given time period * @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}.* FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} @@ -62,7 +62,7 @@ fun getRecentMangasQuery() = """ 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} ORDER BY max_last_read.${History.COL_LAST_READ} DESC - LIMIT 25 + LIMIT 25 OFFSET $offset """ fun getHistoryByMangaId() = """ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt index b9d583d4c9..f1d68c22a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.database.resolvers import android.content.ContentValues -import android.support.annotation.NonNull +import androidx.annotation.NonNull import com.pushtorefresh.storio.sqlite.StorIOSQLite import com.pushtorefresh.storio.sqlite.operations.put.PutResult import com.pushtorefresh.storio.sqlite.queries.Query diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaHideTitlePutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaHideTitlePutResolver.kt new file mode 100644 index 0000000000..48ead1a5e6 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaHideTitlePutResolver.kt @@ -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() { + + 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) + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt index 2b1ff7458d..c642536b01 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt @@ -38,6 +38,8 @@ object MangaTable { const val COL_CATEGORY = "category" + const val COL_HIDE_TITLE = "hideTitle" + val createTableQuery: String get() = """CREATE TABLE $TABLE( $COL_ID INTEGER NOT NULL PRIMARY KEY, @@ -54,6 +56,7 @@ object MangaTable { $COL_LAST_UPDATE LONG, $COL_INITIALIZED BOOLEAN NOT NULL, $COL_VIEWER INTEGER NOT NULL, + $COL_HIDE_TITLE INTEGER NOT NULL, $COL_CHAPTER_FLAGS INTEGER NOT NULL )""" @@ -63,4 +66,7 @@ object MangaTable { val createLibraryIndexQuery: String get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " + "WHERE $COL_FAVORITE = 1" + + val addHideTitle: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_HIDE_TITLE INTEGER DEFAULT 0" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 4e23599d0c..f490956eec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -9,6 +9,9 @@ import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager 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 uy.kohesive.injekt.injectLazy @@ -181,6 +184,7 @@ class DownloadManager(context: Context) { * @param source the source of the manga. */ fun deleteManga(manga: Manga, source: Source) { + downloader.clearQueue(manga, true) queue.remove(manga) provider.findMangaDir(manga, source)?.delete() cache.removeManga(manga) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt index af9d84c0af..dbc7ef0fe8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt @@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.data.download import android.content.Context 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.data.download.model.Download 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) setAutoCancel(true) clearActions() - setContentIntent(NotificationReceiver.openChapterPendingBroadcast(context, download.manga, download.chapter)) + setContentIntent(NotificationReceiver.openChapterPendingActivity(context, download + .manga, download.chapter)) 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)) setContentText(error ?: context.getString(R.string.download_notifier_unkown_error)) setSmallIcon(android.R.drawable.stat_sys_warning) + setCategory(NotificationCompat.CATEGORY_ERROR) clearActions() - setAutoCancel(false) + setAutoCancel(true) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) + color = ContextCompat.getColor(context, R.color.colorAccentLight) setProgress(0, 0, false) } notification.show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt index 894b9e493d..a7ce329a8a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt @@ -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? { + 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. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt index 8fdb4f6267..860dbf145d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt @@ -9,7 +9,7 @@ import android.net.NetworkInfo.State.DISCONNECTED import android.os.Build import android.os.IBinder 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.ReactiveNetwork import com.jakewharton.rxrelay.BehaviorRelay diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 0ca5449361..a68d60c1f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList import eu.kanade.tachiyomi.util.* -import kotlinx.coroutines.experimental.async +import kotlinx.coroutines.async import okhttp3.Response import rx.Observable import rx.android.schedulers.AndroidSchedulers @@ -157,6 +157,26 @@ class Downloader( 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. */ @@ -351,7 +371,7 @@ class Downloader( .map { response -> val file = tmpDir.createFile("$filename.tmp") try { - response.body()!!.source().saveTo(file.openOutputStream()) + response.body!!.source().saveTo(file.openOutputStream()) val extension = getImageExtension(response, file) file.renameTo("$filename.$extension") } catch (e: Exception) { @@ -374,7 +394,7 @@ class Downloader( */ private fun getImageExtension(response: Response, file: UniFile): String { // 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. ?: context.contentResolver.getType(file.uri) // Else read magic numbers. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index ab386b1330..2fb2e5d83a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -22,7 +22,7 @@ class LibraryUpdateJob : Job() { val preferences = Injekt.get() val interval = prefInterval ?: preferences.libraryUpdateInterval().getOrDefault() if (interval > 0) { - val restrictions = preferences.libraryUpdateRestriction() + val restrictions = preferences.libraryUpdateRestriction()!! val acRestriction = "ac" in restrictions val wifiRestriction = if ("wifi" in restrictions) JobRequest.NetworkType.UNMETERED diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 415a458687..17aad93ec3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -9,7 +9,9 @@ import android.graphics.BitmapFactory import android.os.Build import android.os.IBinder 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.data.database.DatabaseHelper 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.download.DownloadManager 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.LibraryUpdateService.Companion.start 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.online.HttpSource 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.Subscription import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.* +import java.util.ArrayList import java.util.concurrent.atomic.AtomicInteger /** @@ -88,6 +95,7 @@ class LibraryUpdateService( .setLargeIcon(notificationBitmap) .setOngoing(true) .setOnlyAlertOnce(true) + .setColor(ContextCompat.getColor(this, R.color.colorAccentLight)) .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. val count = AtomicInteger(0) // List containing new updates - val newUpdates = ArrayList() + val newUpdates = ArrayList>>() // list containing failed updates val failedUpdates = ArrayList() // List containing categories that get included in downloads. @@ -303,7 +311,8 @@ class LibraryUpdateService( } } // 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. .doOnNext { manga -> @@ -325,6 +334,7 @@ class LibraryUpdateService( cancelProgressNotification() } + .map { manga -> manga.first } } fun downloadChapters(manga: Manga, chapters: List) { @@ -438,39 +448,64 @@ class LibraryUpdateService( * * @param updates a list of manga with new updates. */ - private fun showResultNotification(updates: List) { - val newUpdates = updates.map { it.title.chop(45) }.toMutableSet() - - // Append new chapters from a previous, existing notification - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val previousNotification = notificationManager.activeNotifications - .find { it.id == Notifications.ID_LIBRARY_RESULT } - - if (previousNotification != null) { - val oldUpdates = previousNotification.notification.extras - .getString(Notification.EXTRA_BIG_TEXT) - - if (!oldUpdates.isNullOrEmpty()) { - newUpdates += oldUpdates.split("\n") + private fun showResultNotification(updates: List>>) { + val notifications = ArrayList>() + updates.forEach { + val manga = it.first + val chapters = it.second + val chapterNames = chapters.map { chapter -> chapter.name.chop(45) }.toSet() + notifications.add(Pair(notification(Notifications.CHANNEL_NEW_CHAPTERS) { + setSmallIcon(R.drawable.ic_tachiyomi_icon) + try { + val icon = GlideApp.with(this@LibraryUpdateService) + .asBitmap().load(manga).dontTransform().centerCrop().circleCrop() + .override(256, 256).submit().get() + setLargeIcon(icon) } - } + 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) { - setSmallIcon(R.drawable.ic_book_white_24dp) - setLargeIcon(notificationBitmap) - setContentTitle(getString(R.string.notification_new_chapters)) - if (newUpdates.size > 1) { - setContentText(getString(R.string.notification_new_chapters_text, newUpdates.size)) - setStyle(NotificationCompat.BigTextStyle().bigText(newUpdates.joinToString("\n"))) - setNumber(newUpdates.size) - } else { - setContentText(newUpdates.first()) + NotificationManagerCompat.from(this).apply { + notifications.forEach { + notify(it.second, it.first) } - priority = NotificationCompat.PRIORITY_HIGH - setContentIntent(getNotificationIntent()) - setAutoCancel(true) - }) + + notify(Notifications.ID_NEW_CHAPTERS, notification(Notifications.CHANNEL_NEW_CHAPTERS) { + setSmallIcon(R.drawable.ic_tachiyomi_icon) + setLargeIcon(notificationBitmap) + setContentTitle(getString(R.string.notification_new_chapters)) + color = ContextCompat.getColor(applicationContext, R.color.colorAccentLight) + setContentText(getString(R.string.notification_new_chapters_text, updates.size)) + priority = NotificationCompat.PRIORITY_HIGH + setGroup(Notifications.GROUP_NEW_CHAPTERS) + setGroupSummary(true) + setContentIntent(getNotificationIntent()) + setAutoCancel(true) + }) + } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 11622c5057..84e49f19b1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -1,9 +1,13 @@ package eu.kanade.tachiyomi.data.notification +import android.app.Activity +import android.app.KeyguardManager import android.app.PendingIntent import android.content.BroadcastReceiver +import android.content.ClipData import android.content.Context import android.content.Intent +import android.os.Build import android.os.Handler import eu.kanade.tachiyomi.R 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.DownloadService 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.util.DiskUtil import eu.kanade.tachiyomi.util.getUriCompat import eu.kanade.tachiyomi.util.notificationManager import eu.kanade.tachiyomi.util.toast +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.io.File 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), intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) // 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 ACTION_OPEN_CHAPTER -> { openChapter(context, intent.getLongExtra(EXTRA_MANGA_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 */ private fun shareImage(context: Context, path: String, notificationId: Int) { + val km = context.getSystemService(Activity.KEYGUARD_SERVICE) as KeyguardManager // Create intent val intent = Intent(Intent.ACTION_SEND).apply { val uri = File(path).getUriCompat(context) putExtra(Intent.EXTRA_STREAM, uri) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + clipData = ClipData.newRawUri(null, uri) type = "image/*" } - // Dismiss notification - dismissNotification(context, notificationId) - // Launch share activity - context.startActivity(intent) + // Close Navigation Shade + } /** @@ -101,17 +120,18 @@ class NotificationReceiver : BroadcastReceiver() { * @param chapterId id of chapter */ internal fun openChapter(context: Context, mangaId: Long, chapterId: Long) { + dismissNotification(context, Notifications.ID_NEW_CHAPTERS) val db = DatabaseHelper(context) val manga = db.getManga(mangaId).executeAsBlocking() val chapter = db.getChapter(chapterId).executeAsBlocking() - + context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) if (manga != null && chapter != null) { val intent = ReaderActivity.newIntent(context, manga, chapter).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP } context.startActivity(intent) } 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 notificationId id of notification */ - private fun cancelLibraryUpdate(context: Context, notificationId: Int) { + private fun cancelLibraryUpdate(context: 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, 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 { @@ -155,6 +197,9 @@ class NotificationReceiver : BroadcastReceiver() { // Called to 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 private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER" @@ -179,12 +224,18 @@ class NotificationReceiver : BroadcastReceiver() { // Value containing 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. private const val EXTRA_MANGA_ID = "$ID.$NAME.EXTRA_MANGA_ID" // Value containing 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 * @@ -246,6 +297,32 @@ class NotificationReceiver : BroadcastReceiver() { 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 * @@ -255,12 +332,17 @@ class NotificationReceiver : BroadcastReceiver() { * @return [PendingIntent] */ internal fun shareImagePendingBroadcast(context: Context, path: String, notificationId: Int): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_SHARE_IMAGE - putExtra(EXTRA_FILE_LOCATION, path) - putExtra(EXTRA_NOTIFICATION_ID, notificationId) + //val shareIntent = ShareStartingActivity.newIntent(context, path) + val shareIntent = Intent(Intent.ACTION_SEND).apply { + val uri = File(path).getUriCompat(context) + 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 manga manga of chapter * @param chapter chapter that needs to be opened */ - internal fun openChapterPendingBroadcast(context: Context, manga: Manga, chapter: Chapter): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_OPEN_CHAPTER + internal fun openChapterPendingActivity(context: Context, manga: Manga, chapter: + Chapter): PendingIntent { + val newIntent = ReaderActivity.newIntent(context, manga, chapter) + return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, 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, 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_CHAPTER_ID, chapter.id) + putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode()) + putExtra(EXTRA_GROUP_ID, groupId) } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT) } /** @@ -309,4 +427,4 @@ class NotificationReceiver : BroadcastReceiver() { return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index be537dd14f..b99c44cadd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -23,15 +23,21 @@ object Notifications { * Notification channel and ids used by the library updater. */ const val CHANNEL_LIBRARY = "library_channel" - const val ID_LIBRARY_PROGRESS = 101 - const val ID_LIBRARY_RESULT = 102 + const val ID_LIBRARY_PROGRESS = -101 /** * Notification channel and ids used by the downloader. */ const val CHANNEL_DOWNLOADER = "downloader_channel" - const val ID_DOWNLOAD_CHAPTER = 201 - const val ID_DOWNLOAD_CHAPTER_ERROR = 202 + const val ID_DOWNLOAD_CHAPTER = -201 + 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. @@ -44,10 +50,16 @@ object Notifications { val channels = listOf( NotificationChannel(CHANNEL_COMMON, context.getString(R.string.channel_common), NotificationManager.IMPORTANCE_LOW), - NotificationChannel(CHANNEL_LIBRARY, context.getString(R.string.channel_library), - NotificationManager.IMPORTANCE_LOW), + NotificationChannel(CHANNEL_LIBRARY, context.getString(R.string.channel_library_updates), + NotificationManager.IMPORTANCE_LOW).apply { + setShowBadge(false) + }, 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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/EmptyPreferenceDataStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/EmptyPreferenceDataStore.kt index 10e83b84eb..d162cfd659 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/EmptyPreferenceDataStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/EmptyPreferenceDataStore.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.data.preference -import android.support.v7.preference.PreferenceDataStore +import androidx.preference.PreferenceDataStore class EmptyPreferenceDataStore : PreferenceDataStore() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 68e6371ee7..8af98a9e83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -33,7 +33,7 @@ class PreferencesHelper(val context: Context) { 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) @@ -65,7 +65,7 @@ class PreferencesHelper(val context: Context) { 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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt index bb07bb0a52..03a4d17187 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.preference import android.content.SharedPreferences -import android.support.v7.preference.PreferenceDataStore +import androidx.preference.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?): MutableSet { - return prefs.getStringSet(key, defValues) + return prefs.getStringSet(key, defValues)!! } override fun putStringSet(key: String?, values: MutableSet?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt index 9397f71e42..417e8ba5ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.track -import android.support.annotation.CallSuper -import android.support.annotation.DrawableRes +import androidx.annotation.CallSuper +import androidx.annotation.DrawableRes import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.preference.PreferencesHelper diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index 041852ea0f..92c01aac93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.network.asObservableSuccess import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody @@ -18,7 +19,7 @@ import java.util.Calendar class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { 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() @@ -45,7 +46,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() netResponse.close() if (responseBody.isEmpty()) { throw Exception("Null Response") @@ -128,7 +129,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() if (responseBody.isEmpty()) { throw Exception("Null Response") } @@ -189,7 +190,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() if (responseBody.isEmpty()) { throw Exception("Null Response") } @@ -235,7 +236,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() if (responseBody.isEmpty()) { throw Exception("Null Response") } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt index 661c265236..c678372c6b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt @@ -84,7 +84,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() if (responseBody.isEmpty()) { throw Exception("Null Response") } @@ -127,7 +127,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept .asObservableSuccess() .map { netResponse -> // get comic info - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() jsonToTrack(parser.parse(responseBody).obj) } } @@ -144,7 +144,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept return authClient.newCall(requestUserRead) .asObservableSuccess() .map { netResponse -> - val resp = netResponse.body()?.string() + val resp = netResponse.body?.string() val coll = gson.fromJson(resp, Collection::class.java) track.status = coll.status?.id!! track.last_chapter_read = coll.ep_status!! @@ -154,7 +154,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept fun accessToken(code: String): Observable { return client.newCall(accessTokenRequest(code)).asObservableSuccess().map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() if (responseBody.isEmpty()) { throw Exception("Null Response") } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt index 69565f447b..b403daf8a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt @@ -14,7 +14,7 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor { fun addTocken(tocken: String, oidFormBody: FormBody): FormBody { 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("access_token", tocken) @@ -29,18 +29,18 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor { if (currAuth.isExpired()) { val response = chain.proceed(BangumiApi.refreshTokenRequest(currAuth.refresh_token!!)) if (response.isSuccessful) { - newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java)) + newAuth(gson.fromJson(response.body!!.string(), OAuth::class.java)) } else { response.close() } } - var authRequest = if (originalRequest.method() == "GET") originalRequest.newBuilder() + var authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder() .header("User-Agent", "Tachiyomi") - .url(originalRequest.url().newBuilder() + .url(originalRequest.url.newBuilder() .addQueryParameter("access_token", currAuth.access_token).build()) .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") .build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt index 8810dd2743..1a74b8d9ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt @@ -22,7 +22,7 @@ class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor { if (currAuth.isExpired()) { val response = chain.proceed(KitsuApi.refreshTokenRequest(refreshToken)) if (response.isSuccessful) { - newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java)) + newAuth(gson.fromJson(response.body!!.string(), OAuth::class.java)) } else { response.close() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt index ca0de7d3d3..5a3df974b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.data.track.kitsu -import android.support.annotation.CallSuper +import androidx.annotation.CallSuper import com.github.salomonbrys.kotson.* import com.google.gson.JsonObject import eu.kanade.tachiyomi.data.database.models.Track diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index fbfc1e019b..35d821111c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.TrackSearch import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import rx.Completable import rx.Observable import java.lang.Exception @@ -134,7 +135,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) { override fun logout() { super.logout() preferences.trackToken(this).delete() - networkService.cookieManager.remove(HttpUrl.parse(BASE_URL)!!) + networkService.cookieManager.remove(BASE_URL.toHttpUrlOrNull()!!) } val isAuthorized: Boolean @@ -148,9 +149,9 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) { private fun checkCookies(): Boolean { var ckCount = 0 - val url = HttpUrl.parse(BASE_URL)!! + val url = BASE_URL.toHttpUrlOrNull()!! 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++ } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt index 0a032c6a5b..9089c3a343 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt @@ -12,7 +12,7 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor myanimelist.ensureLoggedIn() var request = chain.request() - request.body()?.let { + request.body?.let { val contentType = it.contentType().toString() val updatedBody = when { contentType.contains("x-www-form-urlencoded") -> updateFormBody(it) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyanimelistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyanimelistApi.kt index efc3abefc0..5462eb4f92 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyanimelistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyanimelistApi.kt @@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.util.selectInt import eu.kanade.tachiyomi.util.selectText import okhttp3.* +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject import org.jsoup.Jsoup import org.jsoup.nodes.Document @@ -85,7 +87,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI .map {response -> var libTrack: Track? = null response.use { - if (it.priorResponse()?.isRedirect != true) { + if (it.priorResponse?.isRedirect != true) { val trackForm = Jsoup.parse(it.consumeBody()) 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() 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? { use { - if (it.code() != 200) throw Exception("HTTP error ${it.code()}") - return it.body()?.string() + if (it.code != 200) throw Exception("HTTP error ${it.code}") + return it.body?.string() } } private fun Response.consumeXmlBody(): String? { use { res -> - if (res.code() != 200) throw Exception("Export list error") - BufferedReader(InputStreamReader(GZIPInputStream(res.body()?.source()?.inputStream()))).use { reader -> + if (res.code != 200) throw Exception("Export list error") + BufferedReader(InputStreamReader(GZIPInputStream(res.body?.source()?.inputStream()))).use { reader -> val sb = StringBuilder() reader.forEachLine { line -> sb.append(line) @@ -262,7 +264,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI .put("score", track.score) .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()!! diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index 0180e015eb..afef886bab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess import okhttp3.* +import okhttp3.MediaType.Companion.toMediaTypeOrNull import rx.Observable 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 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() fun addLibManga(track: Track, user_id: String): Observable { @@ -63,7 +64,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() if (responseBody.isEmpty()) { throw Exception("Null Response") } @@ -120,13 +121,13 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter return authClient.newCall(requestMangas) .asObservableSuccess() .map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() parser.parse(responseBody).obj }.flatMap { mangas -> authClient.newCall(request) .asObservableSuccess() .map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() if (responseBody.isEmpty()) { throw Exception("Null Response") } @@ -143,13 +144,13 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter } 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 } fun accessToken(code: String): Observable { return client.newCall(accessTokenRequest(code)).asObservableSuccess().map { netResponse -> - val responseBody = netResponse.body()?.string().orEmpty() + val responseBody = netResponse.body?.string().orEmpty() if (responseBody.isEmpty()) { throw Exception("Null Response") } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt index 1540207277..6e10b4de3d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt @@ -22,7 +22,7 @@ class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Intercept if (currAuth.isExpired()) { val response = chain.proceed(ShikimoriApi.refreshTokenRequest(refreshToken)) if (response.isSuccessful) { - newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java)) + newAuth(gson.fromJson(response.body!!.string(), OAuth::class.java)) } else { response.close() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt index 594ecd31b9..deda43a7a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt @@ -2,12 +2,14 @@ package eu.kanade.tachiyomi.data.updater import android.app.PendingIntent 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.JobManager import com.evernote.android.job.JobRequest import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.notificationManager class UpdaterJob : Job() { @@ -27,6 +29,7 @@ class UpdaterJob : Job() { setContentTitle(context.getString(R.string.app_name)) setContentText(context.getString(R.string.update_check_notification_update_available)) setSmallIcon(android.R.drawable.stat_sys_download_done) + color = ContextCompat.getColor(context, R.color.colorAccentLight) // Download action addAction(android.R.drawable.stat_sys_download_done, context.getString(R.string.action_download), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt index 509c65bb4b..81023f6096 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt @@ -2,11 +2,13 @@ package eu.kanade.tachiyomi.data.updater import android.content.Context 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.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.notificationManager /** @@ -95,6 +97,7 @@ internal class UpdaterNotifier(private val context: Context) { setSmallIcon(android.R.drawable.stat_sys_warning) setOnlyAlertOnce(false) setProgress(0, 0, false) + color = ContextCompat.getColor(context, R.color.colorAccentLight) // Retry action addAction(R.drawable.ic_refresh_grey_24dp_img, context.getString(R.string.action_retry), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt index 4bcff3f1f0..5dd8dee2eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt @@ -71,7 +71,7 @@ class UpdaterService : IntentService(UpdaterService::class.java.name) { val apkFile = File(externalCacheDir, "update.apk") if (response.isSuccessful) { - response.body()!!.source().saveTo(apkFile) + response.body!!.source().saveTo(apkFile) } else { response.close() throw Exception("Unsuccessful response") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt index e19e3528d5..b32a9ff8e6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt @@ -27,7 +27,7 @@ interface GithubService { } } - @GET("/repos/inorichi/tachiyomi/releases/latest") + @GET("/repos/Jays2Kings/tachiyomi/releases/latest") fun getLatestVersion(): Observable } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index a625c06e74..8dfcb27a40 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.launchNow -import kotlinx.coroutines.experimental.async +import kotlinx.coroutines.async import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index f8ef81b89b..d8eb982122 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -32,7 +32,7 @@ internal class ExtensionGithubApi { } private fun parseResponse(response: Response): List { - val text = response.body()?.use { it.string() } ?: return emptyList() + val text = response.body?.use { it.string() } ?: return emptyList() val json = gson.fromJson(text) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt index 421cfb191d..5dcd6bb126 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt @@ -39,7 +39,7 @@ class ExtensionInstallActivity : Activity() { } 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 extensionManager = Injekt.get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt index 5067aa9362..f1d50ce4c3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt @@ -7,7 +7,6 @@ import android.content.IntentFilter import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.LoadResult 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 @@ -91,7 +90,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) : private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { val pkgName = getPackageNameFromIntent(intent) ?: return LoadResult.Error("Package name not found") - return async { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await() + return ExtensionLoader.loadExtensionFromPkgName(context, pkgName) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index 805effe064..c4018f0461 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -13,8 +13,8 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.util.Hash -import kotlinx.coroutines.experimental.async -import kotlinx.coroutines.experimental.runBlocking +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -121,7 +121,7 @@ internal object ExtensionLoader { 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(";") .map { val sourceClass = it.trim() diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt b/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt index 6d425bfb99..b588dbea26 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt @@ -14,22 +14,12 @@ class AndroidCookieJar(context: Context) : CookieJar { private val syncManager by lazy { CookieSyncManager.createInstance(context) } - init { - // 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) { + override fun saveFromResponse(url: HttpUrl, cookies: List) { val urlString = url.toString() for (cookie in cookies) { manager.setCookie(urlString, cookie.toString()) } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - syncManager.sync() - } } override fun loadForRequest(url: HttpUrl): List { diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt index a3f4283a11..a34475d4c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt @@ -42,7 +42,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { val response = chain.proceed(chain.request()) // 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 { response.close() val solutionRequest = resolveWithWebView(chain.request()) @@ -71,8 +71,8 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { var solutionUrl: String? = null var challengeFound = false - val origRequestUrl = request.url().toString() - val headers = request.headers().toMultimap().mapValues { it.value.getOrNull(0) ?: "" } + val origRequestUrl = request.url.toString() + val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" } handler.post { val view = WebView(context) @@ -144,7 +144,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { return Request.Builder().get() .url(solution) - .headers(request.headers()) + .headers(request.headers) .addHeader("Referer", origRequestUrl) .addHeader("Accept", "text/html,application/xhtml+xml,application/xml") .addHeader("Accept-Language", "en") diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index 275dca17dc..60893a7e32 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -103,7 +103,7 @@ class NetworkHelper(context: Context) { val specCompat = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) .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_256_CBC_SHA ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index d82a75923e..7ea2dfa819 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -37,7 +37,7 @@ fun Call.asObservable(): Observable { } override fun isUnsubscribed(): Boolean { - return call.isCanceled + return call.isCanceled() } } @@ -50,7 +50,7 @@ fun Call.asObservableSuccess(): Observable { return asObservable().doOnNext { response -> if (!response.isSuccessful) { 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 -> val originalResponse = chain.proceed(chain.request()) originalResponse.newBuilder() - .body(ProgressResponseBody(originalResponse.body()!!, listener)) + .body(ProgressResponseBody(originalResponse.body!!, listener)) .build() } .build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt index f8123c5199..0d1c418535 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt @@ -8,7 +8,7 @@ import java.io.IOException class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { private val bufferedSource: BufferedSource by lazy { - Okio.buffer(source(responseBody.source())) + source(responseBody.source()).buffer() } override fun contentType(): MediaType { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt index 6b3f99aceb..a1e75187de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.source -import android.support.v7.preference.PreferenceScreen +import androidx.preference.PreferenceScreen interface ConfigurableSource : Source { - fun setupPreferenceScreen(screen: PreferenceScreen) + fun setupPreferenceScreen(screen: PreferenceScreen) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt index 8a1ba1af0c..b05ab53ecd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt @@ -23,6 +23,7 @@ interface SManga : Serializable { var initialized: Boolean fun copyFrom(other: SManga) { + if (other.author != null) author = other.author diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt index 2b9745ea14..f9f867fae2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.base.activity -import android.support.v7.app.AppCompatActivity +import androidx.appcompat.app.AppCompatActivity import eu.kanade.tachiyomi.util.LocaleHelper abstract class BaseActivity : AppCompatActivity() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt index acf2ff1e86..292bf2e326 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.base.controller import android.os.Bundle -import android.support.v7.app.AppCompatActivity +import androidx.appcompat.app.AppCompatActivity import android.view.LayoutInflater import android.view.MenuItem import android.view.View diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt index 492c5a2804..79a6ce86a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.base.controller import android.content.pm.PackageManager.PERMISSION_GRANTED import android.os.Build -import android.support.v4.content.ContextCompat +import androidx.core.content.ContextCompat import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.java index db8efbd830..fa4b62846f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.java @@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.base.controller; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RxController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RxController.kt index 2b2e1ecc6e..a53effc73f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RxController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RxController.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.base.controller import android.os.Bundle -import android.support.annotation.CallSuper +import androidx.annotation.CallSuper import android.view.View import rx.Observable import rx.Subscription diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/SecondaryDrawerController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/SecondaryDrawerController.kt index ba2ce016a1..a6d4aaa56b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/SecondaryDrawerController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/SecondaryDrawerController.kt @@ -1,11 +1,11 @@ package eu.kanade.tachiyomi.ui.base.controller -import android.support.v4.widget.DrawerLayout +import androidx.drawerlayout.widget.DrawerLayout import android.view.ViewGroup 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) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/TabbedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/TabbedController.kt index 02fba36c31..f70d2e11f1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/TabbedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/TabbedController.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.base.controller -import android.support.design.widget.TabLayout +import com.google.android.material.tabs.TabLayout interface TabbedController { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseFlexibleViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseFlexibleViewHolder.kt index 3cc6b9c2af..2720eac37a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseFlexibleViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseFlexibleViewHolder.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.ui.base.holder import android.view.View +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.viewholders.FlexibleViewHolder import kotlinx.android.extensions.LayoutContainer diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseViewHolder.kt index c809b7eebc..fe7e418c07 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseViewHolder.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.ui.base.holder -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.View 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? get() = itemView diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.java index 5210a3a2a8..99642a5016 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.java @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.base.presenter; import android.os.Bundle; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import nucleus.factory.PresenterFactory; import nucleus.presenter.Presenter; diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.java index 33272a1b20..36890cd1be 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.java @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.base.presenter; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.View; import com.bluelinelabs.conductor.Controller; diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt index ceed07a35d..f5fb95bbfe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.catalogue import android.Manifest.permission.WRITE_EXTERNAL_STORAGE -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.SearchView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.appcompat.widget.SearchView import android.view.* import com.bluelinelabs.conductor.ControllerChangeHandler 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.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog import kotlinx.android.synthetic.main.catalogue_main_controller.* import uy.kohesive.injekt.Injekt @@ -98,9 +99,10 @@ class CatalogueController : NucleusController(), adapter = CatalogueAdapter(this) // Create recycler and set adapter. - recycler.layoutManager = LinearLayoutManager(view.context) + recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) recycler.adapter = adapter recycler.addItemDecoration(SourceDividerItemDecoration(view.context)) + recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) } @@ -132,7 +134,7 @@ class CatalogueController : NucleusController(), /** * 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 source = item.source if (source is LoginSource && !source.isLogged()) { @@ -150,7 +152,7 @@ class CatalogueController : NucleusController(), * Called when browse is clicked in [CatalogueAdapter] */ override fun onBrowseClick(position: Int) { - onItemClick(position) + onItemClick(view!!, position) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/LangHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/LangHolder.kt index 66c94967d3..c4b711be79 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/LangHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/LangHolder.kt @@ -5,8 +5,10 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.util.LocaleHelper 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>) : BaseFlexibleViewHolder(view, adapter) { fun bind(item: LangItem) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/LangItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/LangItem.kt index dfa6a91d2f..c3f15a3f50 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/LangItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/LangItem.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.ui.catalogue import android.view.View +import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractHeaderItem +import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R /** @@ -22,16 +24,14 @@ data class LangItem(val code: String) : AbstractHeaderItem() { /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): LangHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LangHolder { return LangHolder(view, adapter) } /** * Binds this item to the given view holder. */ - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: LangHolder, - position: Int, payloads: List?) { - + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: LangHolder, position: Int, payloads: MutableList) { holder.bind(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/SourceDividerItemDecoration.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/SourceDividerItemDecoration.kt index a4c33beb75..b927d4e3e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/SourceDividerItemDecoration.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/SourceDividerItemDecoration.kt @@ -4,27 +4,27 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.Drawable -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.View -class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() { +class SourceDividerItemDecoration(context: Context) : androidx.recyclerview.widget.RecyclerView.ItemDecoration() { private val divider: Drawable init { val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider)) - divider = a.getDrawable(0) + divider = a.getDrawable(0)!! 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 for (i in 0 until childCount - 1) { val child = parent.getChildAt(i) val holder = parent.getChildViewHolder(child) if (holder 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 bottom = top + divider.intrinsicHeight 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, - state: RecyclerView.State) { + override fun getItemOffsets(outRect: Rect, view: View, parent: androidx.recyclerview.widget.RecyclerView, + state: androidx.recyclerview.widget.RecyclerView.State) { outRect.set(0, 0, 0, divider.intrinsicHeight) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/SourceItem.kt index 57c1fdf8ae..bd4bb03852 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/SourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/SourceItem.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.ui.catalogue import android.view.View +import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractSectionableItem +import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R 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. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): SourceHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): SourceHolder { return SourceHolder(view, adapter as CatalogueAdapter) } /** * Binds this item to the given view holder. */ - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: SourceHolder, - position: Int, payloads: List?) { - + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: SourceHolder, position: Int, payloads: MutableList) { holder.bind(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCatalogueController.kt index 97cfd76e16..8f1136be04 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCatalogueController.kt @@ -2,12 +2,18 @@ package eu.kanade.tachiyomi.ui.catalogue.browse import android.content.res.Configuration import android.os.Bundle -import android.support.design.widget.Snackbar -import android.support.v4.widget.DrawerLayout -import android.support.v7.widget.* -import android.view.* -import com.afollestad.materialdialogs.MaterialDialog +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +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.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents import eu.davidea.flexibleadapter.FlexibleAdapter 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.withFadeTransaction 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.info.MangaWebViewController -import eu.kanade.tachiyomi.util.* +import eu.kanade.tachiyomi.ui.manga.info.WebViewActivity +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 kotlinx.android.synthetic.main.catalogue_controller.* import kotlinx.android.synthetic.main.main_activity.* @@ -74,7 +92,7 @@ open class BrowseCatalogueController(bundle: Bundle) : /** * 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. @@ -130,19 +148,19 @@ open class BrowseCatalogueController(bundle: Bundle) : super.onDestroyView(view) } - override fun createSecondaryDrawer(drawer: DrawerLayout): ViewGroup? { + override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup? { // Inflate and prepare drawer val navView = drawer.inflate(R.layout.catalogue_drawer) as CatalogueNavigationView this.navView = navView 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 = { val allDefault = presenter.sourceFilters == presenter.source.getFilterList() showProgressBar() adapter?.clear() - drawer.closeDrawer(Gravity.END) + drawer.closeDrawer(GravityCompat.END) presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters) } @@ -152,30 +170,47 @@ open class BrowseCatalogueController(bundle: Bundle) : presenter.sourceFilters = newFilters 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 { + topMargin = titleMarginTop + insets.systemWindowInsetTop + } + } return navView } - override fun cleanupSecondaryDrawer(drawer: DrawerLayout) { + override fun cleanupSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout) { navView = null } private fun setupRecycler(view: View) { numColumnsSubscription?.unsubscribe() - var oldPosition = RecyclerView.NO_POSITION + var oldPosition = androidx.recyclerview.widget.RecyclerView.NO_POSITION val oldRecycler = catalogue_view?.getChildAt(1) - if (oldRecycler is RecyclerView) { - oldPosition = (oldRecycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() + if (oldRecycler is androidx.recyclerview.widget.RecyclerView) { + oldPosition = (oldRecycler.layoutManager as androidx.recyclerview.widget.LinearLayoutManager).findFirstVisibleItemPosition() oldRecycler.adapter = null catalogue_view?.removeView(oldRecycler) } val recycler = if (presenter.isListMode) { - RecyclerView(view.context).apply { + androidx.recyclerview.widget.RecyclerView(view.context).apply { id = R.id.recycler - layoutManager = LinearLayoutManager(context) - addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) + layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) + addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(context, androidx.recyclerview.widget.DividerItemDecoration.VERTICAL)) } } else { (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 .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 { return when (adapter?.getItemViewType(position)) { R.layout.catalogue_grid_item, null -> 1 @@ -199,9 +234,9 @@ open class BrowseCatalogueController(bundle: Bundle) : recycler.adapter = adapter catalogue_view.addView(recycler, 1) - - if (oldPosition != RecyclerView.NO_POSITION) { - recycler.layoutManager.scrollToPosition(oldPosition) + recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) + if (oldPosition != androidx.recyclerview.widget.RecyclerView.NO_POSITION) { + recycler.layoutManager?.scrollToPosition(oldPosition) } this.recycler = recycler } @@ -272,7 +307,7 @@ open class BrowseCatalogueController(bundle: Bundle) : override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { 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_web_view -> openInWebView() else -> return super.onOptionsItemSelected(item) @@ -288,9 +323,10 @@ open class BrowseCatalogueController(bundle: Bundle) : private fun openInWebView() { val source = presenter.source as? HttpSource ?: return - - router.pushController(MangaWebViewController(source.id, source.baseUrl) - .withFadeTransaction()) + val activity = activity ?: return + val intent = WebViewActivity.newIntent(activity, source.id, source.baseUrl, presenter + .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 ?: "") snack?.dismiss() - snack = catalogue_view?.snack(message, Snackbar.LENGTH_INDEFINITE) { + snack = catalouge_layout?.snack(message, Snackbar.LENGTH_INDEFINITE) { setAction(R.string.action_retry) { // If not the first page, show bottom progress bar. if (adapter.mainItemCount > 0) { @@ -464,7 +500,7 @@ open class BrowseCatalogueController(bundle: Bundle) : * @param position the position of the element clicked. * @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 router.pushController(MangaController(item.manga, true).withFadeTransaction()) @@ -481,31 +517,41 @@ open class BrowseCatalogueController(bundle: Bundle) : * @param position the position of the element clicked. */ override fun onItemLongClick(position: Int) { - val activity = activity ?: return val manga = (adapter?.getItem(position) as? CatalogueItem?)?.manga ?: return + snack?.dismiss() if (manga.favorite) { - MaterialDialog.Builder(activity) - .items(activity.getString(R.string.remove_from_library)) - .itemsCallback { _, _, which, _ -> - when (which) { - 0 -> { - presenter.changeMangaFavorite(manga) - adapter?.notifyItemChanged(position) - activity?.toast(activity?.getString(R.string.manga_removed_library)) - } - } - }.show() - } else { presenter.changeMangaFavorite(manga) adapter?.notifyItemChanged(position) + 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() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + if (!manga.favorite) presenter.confirmDeletion(manga) + } + }) + } + (activity as? MainActivity)?.setUndoSnackBar(snack) + } else { + addManga(manga, position) + snack = catalouge_layout?.snack(R.string.manga_added_library) + } + } - val categories = presenter.getCategories() - val defaultCategory = categories.find { it.id == preferences.defaultCategory() } - if (defaultCategory != null) { - presenter.moveMangaToCategory(manga, defaultCategory) - } else if (categories.size <= 1) { // default or the one from the user - presenter.moveMangaToCategory(manga, categories.firstOrNull()) - } else { + private fun addManga(manga: Manga, position: Int) { + presenter.changeMangaFavorite(manga) + adapter?.notifyItemChanged(position) + + val categories = presenter.getCategories() + val defaultCategoryId = preferences.defaultCategory() + val defaultCategory = categories.find { it.id == defaultCategoryId } + when { + defaultCategory != null -> presenter.moveMangaToCategory(manga, defaultCategory) + defaultCategoryId == 0 || categories.isEmpty() -> // 'Default' or no category + presenter.moveMangaToCategory(manga, null) + else -> { val ids = presenter.getMangaCategoryIds(manga) val preselected = ids.mapNotNull { id -> categories.indexOfFirst { it.id == id }.takeIf { it != -1 } @@ -514,9 +560,7 @@ open class BrowseCatalogueController(bundle: Bundle) : ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected) .showDialog(router) } - activity?.toast(activity?.getString(R.string.manga_added_library)) } - } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCataloguePresenter.kt index 6bc440eae6..d81f1f044b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCataloguePresenter.kt @@ -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.Manga 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.source.CatalogueSource import eu.kanade.tachiyomi.source.SourceManager @@ -253,12 +254,15 @@ open class BrowseCataloguePresenter( */ fun changeMangaFavorite(manga: Manga) { manga.favorite = !manga.favorite - if (!manga.favorite) { - coverCache.deleteFromCache(manga.thumbnail_url) - } 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. */ @@ -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 { return db.getCategories().executeAsBlocking() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueGridHolder.kt index 8daad99477..1d69853b29 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueGridHolder.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.widget.StateImageViewTarget 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. @@ -16,7 +18,7 @@ import kotlinx.android.synthetic.main.catalogue_grid_item.* * @param adapter the adapter handling this 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>) : CatalogueHolder(view, adapter) { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueHolder.kt index 9a0ec3916b..2356d1686f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueHolder.kt @@ -4,6 +4,8 @@ import android.view.View import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.database.models.Manga 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. @@ -11,7 +13,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder * @param view the inflated view for this holder. * @param adapter the adapter handling this holder. */ -abstract class CatalogueHolder(view: View, adapter: FlexibleAdapter<*>) : +abstract class CatalogueHolder(view: View, adapter: FlexibleAdapter>) : BaseFlexibleViewHolder(view, adapter) { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueItem.kt index 71696565be..19c03650ed 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueItem.kt @@ -12,6 +12,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.widget.AutofitRecyclerView 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) : AbstractFlexibleItem() { @@ -23,7 +25,7 @@ class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference): CatalogueHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): CatalogueHolder { val parent = adapter.recyclerView return if (parent is AutofitRecyclerView) { view.apply { @@ -38,10 +40,10 @@ class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference, + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: CatalogueHolder, position: Int, - payloads: List?) { + payloads: MutableList?) { holder.onSetValues(manga) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueListHolder.kt index 2624215574..08799d6406 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/CatalogueListHolder.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.util.getResourceColor 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. @@ -16,7 +18,7 @@ import kotlinx.android.synthetic.main.catalogue_list_item.* * @param adapter the adapter handling this holder. * @constructor creates a new catalogue holder. */ -class CatalogueListHolder(private val view: View, adapter: FlexibleAdapter<*>) : +class CatalogueListHolder(private val view: View, adapter: FlexibleAdapter>) : CatalogueHolder(view, adapter) { private val favoriteColor = view.context.getResourceColor(android.R.attr.textColorHint) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/ProgressItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/ProgressItem.kt index 0eeeb51d3a..1f54272a75 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/ProgressItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/ProgressItem.kt @@ -3,8 +3,10 @@ package eu.kanade.tachiyomi.ui.catalogue.browse import android.view.View import android.widget.ProgressBar import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R @@ -17,11 +19,11 @@ class ProgressItem : AbstractFlexibleItem() { return R.layout.catalogue_progress_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList) { holder.progressBar.visibility = View.GONE holder.progressMessage.visibility = View.GONE @@ -40,7 +42,7 @@ class ProgressItem : AbstractFlexibleItem() { return this === other } - class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter) { val progressBar: ProgressBar = view.findViewById(R.id.progress_bar) val progressMessage: TextView = view.findViewById(R.id.progress_message) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/CheckboxItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/CheckboxItem.kt index 5ad10faf81..8431ca5859 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/CheckboxItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/CheckboxItem.kt @@ -2,8 +2,10 @@ package eu.kanade.tachiyomi.ui.catalogue.filter import android.view.View import android.widget.CheckBox +import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter @@ -14,11 +16,11 @@ open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList) { val view = holder.check view.text = filter.name view.isChecked = filter.state @@ -38,7 +40,7 @@ open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem) : FlexibleViewHolder(view, adapter) { + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter) { val check: CheckBox = itemView.findViewById(R.id.nav_view_item) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt index 325371d944..c7ea5b5a7f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt @@ -10,6 +10,8 @@ import eu.davidea.viewholders.ExpandableViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.setVectorCompat +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem>() { @@ -25,11 +27,11 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList?) { holder.title.text = filter.name holder.icon.setVectorCompat(if (isExpanded) @@ -52,7 +54,7 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem) : ExpandableViewHolder(view, adapter, true) { + open class Holder(view: View, adapter: FlexibleAdapter>) : ExpandableViewHolder(view, adapter, true) { val title: TextView = itemView.findViewById(R.id.title) val icon: ImageView = itemView.findViewById(R.id.expand_icon) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/HeaderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/HeaderItem.kt index fc929af2e4..a3826c0156 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/HeaderItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/HeaderItem.kt @@ -1,13 +1,15 @@ package eu.kanade.tachiyomi.ui.catalogue.filter import android.annotation.SuppressLint -import android.support.design.R +import com.google.android.material.R import android.view.View import android.widget.TextView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.viewholders.FlexibleViewHolder 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() { @@ -16,11 +18,11 @@ class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList?) { val view = holder.itemView as TextView view.text = filter.name } @@ -35,5 +37,5 @@ class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem) : FlexibleViewHolder(view, adapter) + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SelectItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SelectItem.kt index 6a3e9005ec..5e0a4f3263 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SelectItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SelectItem.kt @@ -10,6 +10,8 @@ import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter 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() { @@ -17,11 +19,11 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList?) { holder.text.text = filter.name + ": " val spinner = holder.spinner @@ -46,7 +48,7 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem) : FlexibleViewHolder(view, adapter) { + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter) { val text: TextView = itemView.findViewById(R.id.nav_view_item_text) val spinner: Spinner = itemView.findViewById(R.id.nav_view_item) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SeparatorItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SeparatorItem.kt index 61fa80c8b8..fb25fa0c2b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SeparatorItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SeparatorItem.kt @@ -1,10 +1,12 @@ package eu.kanade.tachiyomi.ui.catalogue.filter import android.annotation.SuppressLint -import android.support.design.R +import com.google.android.material.R import android.view.View +import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractHeaderItem +import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.source.model.Filter @@ -15,11 +17,12 @@ class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, + holder: Holder, position: Int, payloads: MutableList?) { } @@ -33,5 +36,5 @@ class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem) : FlexibleViewHolder(view, adapter) + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt index b91205c26a..6b12985bfb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.ui.catalogue.filter import android.view.View +import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem +import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.ISectionable import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter @@ -22,11 +24,11 @@ class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList?) { holder.title.text = filter.name holder.icon.setVectorCompat(if (isExpanded) @@ -48,5 +50,5 @@ class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem) : GroupItem.Holder(view, adapter) + class Holder(view: View, adapter: FlexibleAdapter>) : GroupItem.Holder(view, adapter) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt index 87d0a501bf..763b1f7195 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.catalogue.filter -import android.support.graphics.drawable.VectorDrawableCompat -import android.support.v4.content.ContextCompat +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat +import androidx.core.content.ContextCompat import android.view.View import android.widget.CheckedTextView import eu.davidea.flexibleadapter.FlexibleAdapter @@ -10,6 +10,8 @@ import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter 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(group) { @@ -21,11 +23,11 @@ class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem return 102 } - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList?) { val view = holder.text view.text = name val filter = group.filter @@ -66,7 +68,7 @@ class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem return result } - class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter) { val text: CheckedTextView = itemView.findViewById(R.id.nav_view_item) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TextItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TextItem.kt index 18c57b6404..1c368ca52c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TextItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TextItem.kt @@ -1,6 +1,6 @@ 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.widget.EditText import eu.davidea.flexibleadapter.FlexibleAdapter @@ -9,6 +9,8 @@ import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter 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() { @@ -16,11 +18,11 @@ open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList?) { holder.wrapper.hint = filter.name holder.edit.setText(filter.state) holder.edit.addTextChangedListener(object : SimpleTextWatcher() { @@ -40,7 +42,7 @@ open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem) : FlexibleViewHolder(view, adapter) { + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter) { val wrapper: TextInputLayout = itemView.findViewById(R.id.nav_view_item_wrapper) val edit: EditText = itemView.findViewById(R.id.nav_view_item) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TriStateItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TriStateItem.kt index d122251c99..28ea006dc6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TriStateItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TriStateItem.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.catalogue.filter -import android.support.design.R -import android.support.graphics.drawable.VectorDrawableCompat +import com.google.android.material.R +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import android.view.View import android.widget.CheckedTextView 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.getResourceColor 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() { @@ -22,11 +24,11 @@ open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList?) { val view = holder.text view.text = filter.name @@ -61,7 +63,7 @@ open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem) : FlexibleViewHolder(view, adapter) { + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter) { val text: CheckedTextView = itemView.findViewById(TR.id.nav_view_item) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchAdapter.kt index 0b1b822e0f..ec419c7641 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchAdapter.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.catalogue.global_search import android.os.Bundle import android.os.Parcelable -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.util.SparseArray import eu.davidea.flexibleadapter.FlexibleAdapter @@ -19,12 +19,12 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) : */ private var bundle = Bundle() - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List?) { - super.onBindViewHolder(holder, position, payloads) + override fun onBindViewHolder(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, position: Int) { + super.onBindViewHolder(holder, position) restoreHolderState(holder) } - override fun onViewRecycled(holder: RecyclerView.ViewHolder) { + override fun onViewRecycled(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder) { super.onViewRecycled(holder) saveHolderState(holder, bundle) } @@ -38,7 +38,7 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) : override fun onRestoreInstanceState(savedInstanceState: Bundle) { 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 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 holderState = SparseArray() holder.itemView.saveHierarchyState(holderState) @@ -59,7 +59,7 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) : * * @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 holderState = bundle.getSparseParcelableArray(key) if (holderState != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardItem.kt index 3d43b12e96..9da45250b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardItem.kt @@ -5,6 +5,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.kanade.tachiyomi.R 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() { @@ -12,12 +14,12 @@ class CatalogueSearchCardItem(val manga: Manga) : AbstractFlexibleItem): CatalogueSearchCardHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): CatalogueSearchCardHolder { return CatalogueSearchCardHolder(view, adapter as CatalogueSearchCardAdapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: CatalogueSearchCardHolder, - position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: CatalogueSearchCardHolder, + position: Int, payloads: MutableList?) { holder.bind(manga) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchController.kt index a3f7f4ca28..5cefa5de6b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchController.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.catalogue.global_search import android.os.Bundle -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.SearchView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.appcompat.widget.SearchView import android.view.* import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents 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.withFadeTransaction import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import kotlinx.android.synthetic.main.catalogue_global_search_controller.* /** @@ -131,8 +132,9 @@ open class CatalogueSearchController( adapter = CatalogueSearchAdapter(this) // Create recycler and set adapter. - recycler.layoutManager = LinearLayoutManager(view.context) + recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) recycler.adapter = adapter + recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) } override fun onDestroyView(view: View) { @@ -169,12 +171,29 @@ open class CatalogueSearchController( return null } + override fun handleBack(): Boolean { + return if (extensionFilter != null) { + activity?.finishAffinity() + true + } else super.handleBack() + } + /** * Add search result to adapter. * * @param searchResult result of search. */ fun setItems(searchResult: List) { + 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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchHolder.kt index eefb379604..6f29d45762 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchHolder.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.catalogue.global_search -import android.support.v7.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager import android.view.View import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga @@ -29,7 +29,8 @@ class CatalogueSearchHolder(view: View, val adapter: CatalogueSearchAdapter) : init { // 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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchItem.kt index 9fe950082c..0a69cbd13a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchItem.kt @@ -5,6 +5,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.CatalogueSource +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible /** * Item that contains search result information. @@ -30,15 +32,15 @@ class CatalogueSearchItem(val source: CatalogueSource, val results: List): CatalogueSearchHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): CatalogueSearchHolder { return CatalogueSearchHolder(view, adapter as CatalogueSearchAdapter) } /** * Bind item to view. */ - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: CatalogueSearchHolder, - position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: CatalogueSearchHolder, + position: Int, payloads: MutableList?) { holder.bind(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/latest/LatestUpdatesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/latest/LatestUpdatesController.kt index 221a2142e4..41c133a820 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/latest/LatestUpdatesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/latest/LatestUpdatesController.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.catalogue.latest import android.os.Bundle -import android.support.v4.widget.DrawerLayout +import androidx.drawerlayout.widget.DrawerLayout import android.view.Menu import android.view.ViewGroup import eu.kanade.tachiyomi.R @@ -28,11 +28,11 @@ class LatestUpdatesController(bundle: Bundle) : BrowseCatalogueController(bundle 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 } - override fun cleanupSecondaryDrawer(drawer: DrawerLayout) { + override fun cleanupSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout) { } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt index c698c2d6e6..b2c8f57311 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt @@ -1,11 +1,10 @@ package eu.kanade.tachiyomi.ui.category -import android.support.design.widget.Snackbar -import android.support.v7.app.AppCompatActivity -import android.support.v7.view.ActionMode -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode import android.view.* +import com.google.android.material.snackbar.BaseTransientBottomBar import com.jakewharton.rxbinding.view.clicks import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter @@ -13,7 +12,13 @@ import eu.davidea.flexibleadapter.helpers.UndoHelper import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category 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.updateLayoutParams +import eu.kanade.tachiyomi.util.updatePaddingRelative import kotlinx.android.synthetic.main.categories_controller.* /** @@ -41,7 +46,7 @@ class CategoryController : NucleusController(), /** * 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. @@ -74,7 +79,7 @@ class CategoryController : NucleusController(), super.onViewCreated(view) adapter = CategoryAdapter(this@CategoryController) - recycler.layoutManager = LinearLayoutManager(view.context) + recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) recycler.setHasFixedSize(true) recycler.adapter = adapter adapter?.isHandleDragEnabled = true @@ -83,6 +88,14 @@ class CategoryController : NucleusController(), fab.clicks().subscribeUntilDestroy { 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 { + bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom + } + } } /** @@ -92,8 +105,9 @@ class CategoryController : NucleusController(), */ override fun onDestroyView(view: View) { // Manually call callback to delete categories if required - undoHelper?.onDeleteConfirmed(Snackbar.Callback.DISMISS_EVENT_MANUAL) - undoHelper = null + snack?.dismiss() + confirmDelete() + snack = null actionMode = null adapter = null super.onDestroyView(view) @@ -166,10 +180,22 @@ class CategoryController : NucleusController(), when (item.itemId) { R.id.action_delete -> { - undoHelper = UndoHelper(adapter, this) - undoHelper?.start(adapter.selectedPositions, view!!, - R.string.snack_categories_deleted, R.string.action_undo, 3000) - + adapter.removeItems(adapter.selectedPositions) + snack = + 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() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + if (!undoing) confirmDelete() + } + }) + } + (activity as? MainActivity)?.setUndoSnackBar(snack) mode.finish() } R.id.action_edit -> { @@ -205,9 +231,9 @@ class CategoryController : NucleusController(), * @param position The position of the clicked item. * @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. - if (actionMode != null && position != RecyclerView.NO_POSITION) { + if (actionMode != null && position != androidx.recyclerview.widget.RecyclerView.NO_POSITION) { toggleSelection(position) return true } else { @@ -270,7 +296,7 @@ class CategoryController : NucleusController(), */ override fun onActionCanceled(action: Int, positions: MutableList?) { adapter?.restoreDeletedItems() - undoHelper = null + snack = null } /** @@ -282,7 +308,13 @@ class CategoryController : NucleusController(), override fun onActionConfirmed(action: Int, event: Int) { val adapter = adapter ?: return 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 } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt index 7f24ab5289..4eb6f8f794 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.ui.category import android.view.View +import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category @@ -29,7 +31,7 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem): CategoryHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): CategoryHolder { return CategoryHolder(view, adapter as CategoryAdapter) } @@ -41,11 +43,7 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem, - holder: CategoryHolder, - position: Int, - payloads: List?) { - + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: CategoryHolder, position: Int, payloads: MutableList) { holder.bind(category) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt index 795b85f131..6cf6c1bebc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.download -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.ViewGroup import eu.kanade.tachiyomi.R 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. */ -class DownloadAdapter : RecyclerView.Adapter() { +class DownloadAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() { private var items = emptyList() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt index 9252374f11..2430892b7f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt @@ -1,12 +1,13 @@ package eu.kanade.tachiyomi.ui.download -import android.support.v7.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager import android.view.* import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.base.controller.NucleusController +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import kotlinx.android.synthetic.main.download_controller.* import rx.Observable import rx.Subscription @@ -62,8 +63,9 @@ class DownloadController : NucleusController() { recycler.adapter = adapter // Set the layout manager for the recycler and fixed size. - recycler.layoutManager = LinearLayoutManager(view.context) + recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) recycler.setHasFixedSize(true) + recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) // Suscribe to changes DownloadService.runningRelay diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt index 874861bb17..26e539e71b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.extension -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.SearchView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.appcompat.widget.SearchView import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import kotlinx.android.synthetic.main.extension_controller.* @@ -63,9 +64,10 @@ open class ExtensionController : NucleusController(), // Initialize adapter, scroll listener and recycler views adapter = ExtensionAdapter(this) // Create recycler and set adapter. - ext_recycler.layoutManager = LinearLayoutManager(view.context) + ext_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) ext_recycler.adapter = adapter ext_recycler.addItemDecoration(ExtensionDividerItemDecoration(view.context)) + ext_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) } override fun onDestroyView(view: View) { @@ -116,7 +118,7 @@ open class ExtensionController : NucleusController(), searchItem.fixExpand() } - override fun onItemClick(position: Int): Boolean { + override fun onItemClick(view: View?, position: Int): Boolean { val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return false if (extension is Extension.Installed) { openDetails(extension) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt index 2005a93889..6d606fdab4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt @@ -3,11 +3,11 @@ package eu.kanade.tachiyomi.ui.extension import android.annotation.SuppressLint import android.content.Context import android.os.Bundle -import android.support.v7.preference.* -import android.support.v7.preference.internal.AbstractMultiSelectListPreference -import android.support.v7.widget.DividerItemDecoration -import android.support.v7.widget.DividerItemDecoration.VERTICAL -import android.support.v7.widget.LinearLayoutManager +import androidx.preference.* +import androidx.preference.MultiSelectListPreference +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL +import androidx.recyclerview.widget.LinearLayoutManager import android.util.TypedValue import android.view.ContextThemeWrapper import android.view.LayoutInflater @@ -21,8 +21,10 @@ import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.online.LoginSource import eu.kanade.tachiyomi.ui.base.controller.NucleusController +import eu.kanade.tachiyomi.ui.setting.preference import eu.kanade.tachiyomi.ui.setting.preferenceCategory import eu.kanade.tachiyomi.util.LocaleHelper +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.widget.preference.LoginPreference import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog import kotlinx.android.synthetic.main.extension_detail_controller.* @@ -47,7 +49,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) : } override fun createPresenter(): ExtensionDetailsPresenter { - return ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)) + return ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!) } override fun getTitle(): String? { @@ -79,17 +81,19 @@ class ExtensionDetailsController(bundle: Bundle? = null) : val multiSource = extension.sources.size > 1 - for (source in extension.sources) { + /*for (source in extension.sources) { if (source is ConfigurableSource) { addPreferencesForSource(screen, source, multiSource) } - } + }*/ manager.setPreferences(screen) - extension_prefs_recycler.layoutManager = LinearLayoutManager(context) + extension_prefs_recycler.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(context) extension_prefs_recycler.adapter = PreferenceGroupAdapter(screen) - extension_prefs_recycler.addItemDecoration(DividerItemDecoration(context, VERTICAL)) + extension_prefs_recycler.addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(context, VERTICAL)) + extension_prefs_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) if (screen.preferenceCount == 0) { extension_prefs_empty_view.show(R.drawable.ic_no_settings, @@ -165,7 +169,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) : .newInstance(preference.getKey()) is ListPreference -> ListPreferenceDialogController .newInstance(preference.getKey()) - is AbstractMultiSelectListPreference -> MultiSelectListPreferenceDialogController + is MultiSelectListPreference -> MultiSelectListPreferenceDialogController .newInstance(preference.getKey()) else -> throw IllegalArgumentException("Tried to display dialog for unknown " + "preference type. Did you forget to override onDisplayPreferenceDialog()?") @@ -174,8 +178,8 @@ class ExtensionDetailsController(bundle: Bundle? = null) : f.showDialog(router) } - override fun findPreference(key: CharSequence?): Preference { - return preferenceScreen!!.getPreference(lastOpenPreferencePosition!!) + override fun findPreference(key: CharSequence): T? { + return preferenceScreen!!.findPreference(key) } override fun loginDialogClosed(source: LoginSource) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDividerItemDecoration.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDividerItemDecoration.kt index 40fe44505d..5b5946939d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDividerItemDecoration.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDividerItemDecoration.kt @@ -4,27 +4,27 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.Drawable -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.View -class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() { +class ExtensionDividerItemDecoration(context: Context) : androidx.recyclerview.widget.RecyclerView.ItemDecoration() { private val divider: Drawable init { val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider)) - divider = a.getDrawable(0) + divider = a.getDrawable(0)!! 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 for (i in 0 until childCount - 1) { val child = parent.getChildAt(i) val holder = parent.getChildViewHolder(child) if (holder is ExtensionHolder && parent.getChildViewHolder(parent.getChildAt(i + 1)) is ExtensionHolder) { - val params = child.layoutParams as RecyclerView.LayoutParams + val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams val top = child.bottom + params.bottomMargin val bottom = top + divider.intrinsicHeight val left = parent.paddingLeft + holder.margin @@ -36,8 +36,8 @@ class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecora } } - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, - state: RecyclerView.State) { + override fun getItemOffsets(outRect: Rect, view: View, parent: androidx.recyclerview.widget.RecyclerView, + state: androidx.recyclerview.widget.RecyclerView.State) { outRect.set(0, 0, 0, divider.intrinsicHeight) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupHolder.kt index 85c2a597ce..01a53c647d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupHolder.kt @@ -5,8 +5,10 @@ import android.view.View import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import kotlinx.android.synthetic.main.extension_card_header.title +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible -class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) : +class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter>) : BaseFlexibleViewHolder(view, adapter) { @SuppressLint("SetTextI18n") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupItem.kt index c7f5656457..6babafc24a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupItem.kt @@ -4,6 +4,8 @@ import android.view.View import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.kanade.tachiyomi.R +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible /** * Item that contains the group header. @@ -23,15 +25,15 @@ data class ExtensionGroupItem(val name: String, val size: Int) : AbstractHeaderI /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): ExtensionGroupHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ExtensionGroupHolder { return ExtensionGroupHolder(view, adapter) } /** * Binds this item to the given view holder. */ - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: ExtensionGroupHolder, - position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ExtensionGroupHolder, + position: Int, payloads: MutableList?) { holder.bind(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt index 2ed363e971..61c219becd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.source.CatalogueSource +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible /** * Item that contains source information. @@ -29,15 +31,15 @@ data class ExtensionItem(val extension: Extension, /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): ExtensionHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ExtensionHolder { return ExtensionHolder(view, adapter as ExtensionAdapter) } /** * Binds this item to the given view holder. */ - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: ExtensionHolder, - position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ExtensionHolder, + position: Int, payloads: MutableList?) { if (payloads == null || payloads.isEmpty()) { holder.bind(this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt index 3094e90620..6c1f8bc0a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt @@ -24,10 +24,10 @@ class ExtensionTrustDialog(bundle: Bundle? = null) : DialogController(bundle) .positiveText(R.string.ext_trust) .negativeText(R.string.ext_uninstall) .onPositive { _, _ -> - (targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)) + (targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!) } .onNegative { _, _ -> - (targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)) + (targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!) } .build() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt deleted file mode 100644 index 1aa376eb87..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt +++ /dev/null @@ -1,43 +0,0 @@ -package eu.kanade.tachiyomi.ui.library - -import android.app.Dialog -import android.os.Bundle -import com.afollestad.materialdialogs.MaterialDialog -import com.bluelinelabs.conductor.Controller -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.widget.DialogCheckboxView - -class DeleteLibraryMangasDialog(bundle: Bundle? = null) : - DialogController(bundle) where T : Controller, T: DeleteLibraryMangasDialog.Listener { - - private var mangas = emptyList() - - constructor(target: T, mangas: List) : this() { - this.mangas = mangas - targetController = target - } - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val view = DialogCheckboxView(activity!!).apply { - setDescription(R.string.confirm_delete_manga) - setOptionDescription(R.string.also_delete_chapters) - } - - return MaterialDialog.Builder(activity!!) - .title(R.string.action_remove) - .customView(view, true) - .positiveText(android.R.string.yes) - .negativeText(android.R.string.no) - .onPositive { _, _ -> - val deleteChapters = view.isChecked() - (targetController as? Listener)?.deleteMangasFromLibrary(mangas, deleteChapters) - } - .build() - } - - interface Listener { - fun deleteMangasFromLibrary(mangas: List, deleteChapters: Boolean) - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index b14996f111..12857908a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.ui.library import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.database.models.Manga +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible /** * Adapter storing a list of manga in a certain category. @@ -38,7 +40,11 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) : } fun performFilter() { - updateDataSet(mangas.filter { it.filter(searchText) }) + var s = getFilter(String::class.java) + if (s == null) { + s = "" + } + updateDataSet(mangas.filter { it.filter(s) }) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt index f2969dbce2..c8a51dbcdc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt @@ -1,10 +1,13 @@ package eu.kanade.tachiyomi.ui.library import android.content.Context -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View import android.widget.FrameLayout +import com.google.android.material.snackbar.Snackbar import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter import eu.kanade.tachiyomi.R @@ -13,9 +16,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.util.inflate -import eu.kanade.tachiyomi.util.plusAssign -import eu.kanade.tachiyomi.util.toast +import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.widget.AutofitRecyclerView import kotlinx.android.synthetic.main.library_category.view.* import rx.subscriptions.CompositeSubscription @@ -60,6 +61,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att */ private var subscriptions = CompositeSubscription() + private var lastTouchUpY = 0f + fun onCreate(controller: LibraryController) { this.controller = controller @@ -82,18 +85,22 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recycler: RecyclerView, newState: Int) { // Disable swipe refresh when view is not at the top - val firstPos = (recycler.layoutManager as LinearLayoutManager) + val firstPos = (recycler.layoutManager as androidx.recyclerview.widget.LinearLayoutManager) .findFirstCompletelyVisibleItemPosition() swipe_refresh.isEnabled = firstPos <= 0 } }) + recycler.doOnApplyWindowInsets { v, insets, padding -> + v.updatePaddingRelative(bottom = padding.bottom + insets.systemWindowInsetBottom) + } // Double the distance required to trigger sync swipe_refresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt()) swipe_refresh.setOnRefreshListener { if (!LibraryUpdateService.isRunning(context)) { LibraryUpdateService.start(context, category) - context.toast(R.string.updating_category) + controller.snack?.dismiss() + controller.snack = swipe_refresh.snack(R.string.updating_category) } // It can be a very long operation, so we disable swipe refresh and show a toast. swipe_refresh.isRefreshing = false @@ -110,7 +117,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att } subscriptions += controller.searchRelay - .doOnNext { adapter.searchText = it } + .doOnNext { adapter.setFilter(it) } .skip(1) .subscribe { adapter.performFilter() } @@ -119,6 +126,16 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att subscriptions += controller.selectionRelay .subscribe { onSelectionChanged(it) } + + subscriptions += controller.selectAllRelay + .subscribe { + if (it == category.id) { + adapter.currentItems.forEach { item -> + controller.setSelection(item.manga, true) + } + controller.invalidateActionMode() + } + } } fun onRecycle() { @@ -201,18 +218,25 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att * @param position the position of the element clicked. * @return true if the item should be selected, false otherwise. */ - override fun onItemClick(position: Int): Boolean { + override fun onItemClick(view: View?, position: Int): Boolean { // If the action mode is created and the position is valid, toggle the selection. val item = adapter.getItem(position) ?: return false if (adapter.mode == SelectableAdapter.Mode.MULTI) { toggleSelection(position) return true } else { - openManga(item.manga) + openManga(item.manga, lastTouchUpY) return false } } + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + when (ev?.action) { + MotionEvent.ACTION_UP -> lastTouchUpY = ev.y + } + return super.dispatchTouchEvent(ev) + } + /** * Called when a manga is long clicked. * @@ -228,8 +252,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att * * @param manga the manga to open. */ - private fun openManga(manga: Manga) { - controller.openManga(manga) + private fun openManga(manga: Manga, startY:Float?) { + controller.openManga(manga, startY) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 4f8c9451c0..567530ea87 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -4,23 +4,33 @@ import android.app.Activity import android.content.Intent import android.content.res.Configuration import android.graphics.Color +import android.net.Uri import android.os.Bundle -import android.support.design.widget.TabLayout -import android.support.v4.graphics.drawable.DrawableCompat -import android.support.v4.widget.DrawerLayout -import android.support.v7.app.AppCompatActivity -import android.support.v7.view.ActionMode -import android.support.v7.widget.SearchView -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode +import androidx.appcompat.widget.SearchView +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.GravityCompat import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.f2prateek.rx.preferences.Preference +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.tabs.TabLayout import com.jakewharton.rxbinding.support.v4.view.pageSelections import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.PublishRelay import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category +import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -33,8 +43,15 @@ import eu.kanade.tachiyomi.ui.category.CategoryController import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.migration.MigrationController +import eu.kanade.tachiyomi.ui.migration.MigrationInterface +import eu.kanade.tachiyomi.ui.migration.SearchController +import eu.kanade.tachiyomi.util.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.inflate +import eu.kanade.tachiyomi.util.marginBottom +import eu.kanade.tachiyomi.util.marginTop +import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.toast +import eu.kanade.tachiyomi.util.updatePaddingRelative import kotlinx.android.synthetic.main.library_controller.* import kotlinx.android.synthetic.main.main_activity.* import rx.Subscription @@ -43,7 +60,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.IOException - class LibraryController( bundle: Bundle? = null, private val preferences: PreferencesHelper = Injekt.get() @@ -52,7 +68,7 @@ class LibraryController( SecondaryDrawerController, ActionMode.Callback, ChangeMangaCategoriesDialog.Listener, - DeleteLibraryMangasDialog.Listener { + MigrationInterface { /** * Position of the active category. @@ -75,6 +91,11 @@ class LibraryController( */ val selectedMangas = mutableSetOf() + /** + * Current mangas to move. + */ + private var migratingMangas = mutableSetOf() + private var selectedCoverManga: Manga? = null /** @@ -92,6 +113,11 @@ class LibraryController( */ val libraryMangaRelay: BehaviorRelay = BehaviorRelay.create() + /** + * Relay to notify the library's viewpager to select all manga + */ + val selectAllRelay: PublishRelay = PublishRelay.create() + /** * Number of manga per row in grid mode. */ @@ -111,7 +137,7 @@ class LibraryController( /** * Drawer listener to allow swipe only for closing the drawer. */ - private var drawerListener: DrawerLayout.DrawerListener? = null + private var drawerListener: androidx.drawerlayout.widget.DrawerLayout.DrawerListener? = null private var tabsVisibilityRelay: BehaviorRelay = BehaviorRelay.create(false) @@ -119,6 +145,8 @@ class LibraryController( private var searchViewSubscription: Subscription? = null + var snack: Snackbar? = null + init { setHasOptionsMenu(true) retainViewMode = RetainViewMode.RETAIN_DETACH @@ -174,10 +202,17 @@ class LibraryController( super.onDestroyView(view) } - override fun createSecondaryDrawer(drawer: DrawerLayout): ViewGroup { + override fun onDetach(view: View) { + snack?.dismiss() + snack = null + super.onDetach(view) + } + + override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup { val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView navView = view - drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.END) + drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED, + GravityCompat.END) navView?.onGroupClicked = { group -> when (group) { @@ -188,10 +223,21 @@ class LibraryController( } } + 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 = view.findViewById(R.id.status_bar_scrim) as View + statusScrim.setOnApplyWindowInsetsListener(HeightTopWindowInsetsListener) + view.doOnApplyWindowInsets { _, insets, _ -> + view.recycler.updatePaddingRelative( + bottom = view.recycler.marginBottom + insets.systemWindowInsetBottom, + top = view.recycler.marginTop + insets.systemWindowInsetTop + ) + } return view } - override fun cleanupSecondaryDrawer(drawer: DrawerLayout) { + override fun cleanupSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout) { navView = null } @@ -340,6 +386,10 @@ class LibraryController( searchItem.fixExpand() } + fun search(query:String) { + this.query = query + } + override fun onPrepareOptionsMenu(menu: Menu) { val navView = navView ?: return @@ -353,10 +403,11 @@ class LibraryController( override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_filter -> { - navView?.let { activity?.drawer?.openDrawer(Gravity.END) } + navView?.let { activity?.drawer?.openDrawer(GravityCompat.END) } } R.id.action_update_library -> { activity?.let { LibraryUpdateService.start(it) } + snack = view?.snack(R.string.updating_library) } R.id.action_edit_categories -> { router.pushController(CategoryController().withFadeTransaction()) @@ -390,6 +441,15 @@ class LibraryController( } else { mode.title = resources?.getString(R.string.label_selected, count) menu.findItem(R.id.action_edit_cover)?.isVisible = count == 1 + menu.findItem(R.id.action_hide_title)?.isVisible = + !preferences.libraryAsList().getOrDefault() + if (!preferences.libraryAsList().getOrDefault()) { + val showAll = + (selectedMangas.filter { (it as? LibraryManga)?.hide_title == true }).size == selectedMangas.size + menu.findItem(R.id.action_hide_title)?.title = activity?.getString( + if (showAll) R.string.label_show_title else R.string.label_hide_title + ) + } } return false } @@ -401,12 +461,44 @@ class LibraryController( destroyActionModeIfNeeded() } R.id.action_move_to_category -> showChangeMangaCategoriesDialog() - R.id.action_delete -> showDeleteMangaDialog() + R.id.action_delete -> deleteMangasFromLibrary() + R.id.action_select_all -> { + adapter?.categories?.getOrNull(library_pager.currentItem)?.id?.let { + selectAllRelay.call(it) + } + } + R.id.action_migrate -> startMangaMigration() + R.id.action_hide_title -> { + val showAll = (selectedMangas.filter { (it as? LibraryManga)?.hide_title == true } + ).size == selectedMangas.size + presenter.hideShowTitle(selectedMangas.toList(), !showAll) + destroyActionModeIfNeeded() + } else -> return false } return true } + override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? { + if (manga.id != prevManga.id) { + presenter.migrateManga(prevManga, manga, replace = replace) + } + val nextManga = migratingMangas.firstOrNull() ?: return null + migratingMangas.remove(nextManga) + return nextManga + } + + private fun startMangaMigration() { + migratingMangas = selectedMangas.distinctBy { it.id }.toMutableSet() + destroyActionModeIfNeeded() + val manga = migratingMangas.firstOrNull() ?: return + val searchController = SearchController(manga) + searchController.totalProgress = migratingMangas.size + searchController.targetController = this + router.pushController(searchController.withFadeTransaction()) + migratingMangas.remove(manga) + } + override fun onDestroyActionMode(mode: ActionMode?) { // Clear all the manga selections and notify child views. selectedMangas.clear() @@ -414,11 +506,11 @@ class LibraryController( actionMode = null } - fun openManga(manga: Manga) { + fun openManga(manga: Manga, startY: Float?) { // Notify the presenter a manga is being opened. presenter.onOpenManga() - router.pushController(MangaController(manga).withFadeTransaction()) + router.pushController(MangaController(manga, startY).withFadeTransaction()) } /** @@ -458,8 +550,26 @@ class LibraryController( .showDialog(router) } - private fun showDeleteMangaDialog() { - DeleteLibraryMangasDialog(this, selectedMangas.toList()).showDialog(router) + private fun deleteMangasFromLibrary() { + val mangas = selectedMangas.toList() + presenter.removeMangaFromLibrary(mangas) + destroyActionModeIfNeeded() + snack?.dismiss() + snack = view?.snack(activity?.getString(R.string.manga_removed_library) ?: "", Snackbar.LENGTH_INDEFINITE) { + var undoing = false + setAction(R.string.action_undo) { + presenter.addMangas(mangas) + undoing = true + } + addCallback(object : BaseTransientBottomBar.BaseCallback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + if (!undoing) + presenter.confirmDeletion(mangas) + } + }) + } + (activity as? MainActivity)?.setUndoSnackBar(snack) } override fun updateCategoriesForMangas(mangas: List, categories: List) { @@ -467,11 +577,6 @@ class LibraryController( destroyActionModeIfNeeded() } - override fun deleteMangasFromLibrary(mangas: List, deleteChapters: Boolean) { - presenter.removeMangaFromLibrary(mangas, deleteChapters) - destroyActionModeIfNeeded() - } - /** * Changes the cover for the selected manga. */ @@ -497,9 +602,9 @@ class LibraryController( try { // Get the file's input stream from the incoming Intent - activity.contentResolver.openInputStream(data.data).use { + activity.contentResolver.openInputStream(data.data ?: Uri.EMPTY).use { // Update cover to selected file, show error if something went wrong - if (presenter.editCoverWithStream(it, manga)) { + if (it != null && presenter.editCoverWithStream(it, manga)) { // TODO refresh cover } else { activity.toast(R.string.notification_cover_update_failed) @@ -521,3 +626,15 @@ class LibraryController( } } + +object HeightTopWindowInsetsListener : View.OnApplyWindowInsetsListener { + override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { + val topInset = insets.systemWindowInsetTop + v.setPadding(0,topInset,0,0) + if (v.layoutParams.height != topInset) { + v.layoutParams.height = topInset + v.requestLayout() + } + return insets + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index 2bc68cf3d9..545ddade2d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -6,6 +6,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.source.LocalSource 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 library, like the cover or the title. @@ -18,7 +20,7 @@ import kotlinx.android.synthetic.main.catalogue_grid_item.* */ class LibraryGridHolder( private val view: View, - private val adapter: FlexibleAdapter<*> + private val adapter: FlexibleAdapter> ) : LibraryHolder(view, adapter) { @@ -30,7 +32,11 @@ class LibraryGridHolder( */ override fun onSetValues(item: LibraryItem) { // Update the title of the manga. - title.text = item.manga.title + with(title) { + visibility = if (item.manga.hide_title) View.GONE else View.VISIBLE + text = item.manga.title + } + gradient.visibility = if (item.manga.hide_title) View.GONE else View.VISIBLE // Update the unread count and its visibility. with(unread_text) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index 41d7f98796..db89816755 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.ui.library import android.view.View import eu.davidea.flexibleadapter.FlexibleAdapter 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 library. @@ -13,7 +15,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder abstract class LibraryHolder( view: View, - adapter: FlexibleAdapter<*> + adapter: FlexibleAdapter> ) : BaseFlexibleViewHolder(view, adapter) { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index ca161827ca..4e88d6f10b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -13,9 +13,11 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.widget.AutofitRecyclerView import kotlinx.android.synthetic.main.catalogue_grid_item.view.* +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference) : - AbstractFlexibleItem(), IFilterable { + AbstractFlexibleItem(), IFilterable { var downloadCount = -1 @@ -26,7 +28,7 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference R.layout.catalogue_grid_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): LibraryHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder { val parent = adapter.recyclerView return if (parent is AutofitRecyclerView) { view.apply { @@ -41,10 +43,10 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference } } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: LibraryHolder, position: Int, - payloads: List?) { + payloads: MutableList?) { holder.onSetValues(this) } @@ -57,7 +59,15 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference */ override fun filter(constraint: String): Boolean { return manga.title.contains(constraint, true) || - (manga.author?.contains(constraint, true) ?: false) + (manga.author?.contains(constraint, true) ?: false) || + (if (constraint.startsWith("-")) + manga.genre?.split(", ")?.find { + it.toLowerCase() == constraint.substringAfter("-").toLowerCase() + } == null + else + manga.genre?.split(", ")?.find { + it.toLowerCase() == constraint.toLowerCase() } != null + ) } override fun equals(other: Any?): Boolean { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 83cc69e25c..c5fd8fc898 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -6,6 +6,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.source.LocalSource 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 library, like the cover or the title. @@ -19,7 +21,7 @@ import kotlinx.android.synthetic.main.catalogue_list_item.* class LibraryListHolder( private val view: View, - private val adapter: FlexibleAdapter<*> + private val adapter: FlexibleAdapter> ) : LibraryHolder(view, adapter) { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 17ac0cba58..a2f84f4345 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -5,18 +5,23 @@ import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category +import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.Manga 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.getOrDefault import eu.kanade.tachiyomi.source.LocalSource +import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.ui.migration.MigrationFlags import eu.kanade.tachiyomi.util.combineLatest import eu.kanade.tachiyomi.util.isNullOrUnsubscribed +import eu.kanade.tachiyomi.util.syncChaptersWithSource import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -185,24 +190,27 @@ class LibraryPresenter( val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 -> when (sortingMode) { - LibrarySort.ALPHA -> i1.manga.title.compareTo(i2.manga.title, true) + LibrarySort.ALPHA -> sortAlphabetical(i1, i2) LibrarySort.LAST_READ -> { // Get index of manga, set equal to list if size unknown. val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size val manga2LastRead = lastReadManga[i2.manga.id!!] ?: lastReadManga.size - manga1LastRead.compareTo(manga2LastRead) + val mangaCompare = manga1LastRead.compareTo(manga2LastRead) + if (mangaCompare == 0) sortAlphabetical(i1, i2) else mangaCompare } LibrarySort.LAST_UPDATED -> i2.manga.last_update.compareTo(i1.manga.last_update) LibrarySort.UNREAD -> i1.manga.unread.compareTo(i2.manga.unread) LibrarySort.TOTAL -> { val manga1TotalChapter = totalChapterManga[i1.manga.id!!] ?: 0 val mange2TotalChapter = totalChapterManga[i2.manga.id!!] ?: 0 - manga1TotalChapter.compareTo(mange2TotalChapter) + val mangaCompare = manga1TotalChapter.compareTo(mange2TotalChapter) + if (mangaCompare == 0) sortAlphabetical(i1, i2) else mangaCompare } LibrarySort.SOURCE -> { val source1Name = sourceManager.getOrStub(i1.manga.source).name val source2Name = sourceManager.getOrStub(i2.manga.source).name - source1Name.compareTo(source2Name) + val mangaCompare = source1Name.compareTo(source2Name) + if (mangaCompare == 0) sortAlphabetical(i1, i2) else mangaCompare } else -> throw Exception("Unknown sorting mode") } @@ -216,22 +224,27 @@ class LibraryPresenter( return map.mapValues { entry -> entry.value.sortedWith(comparator) } } + fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int { + return i1.manga.title.removeArticles().compareTo(i2.manga.title.removeArticles(), true) + } + + fun String.removeArticles(): String { + return this.replace(Regex("^(an|a|the) ", RegexOption.IGNORE_CASE), "") + } + /** * Get the categories and all its manga from the database. * * @return an observable of the categories and its manga. */ private fun getLibraryObservable(): Observable { - return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(), - { dbCategories, libraryManga -> - val categories = if (libraryManga.containsKey(0)) - arrayListOf(Category.createDefault()) + dbCategories - else - dbCategories + return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga -> + val categories = if (libraryManga.containsKey(0)) arrayListOf(Category.createDefault()) + dbCategories + else dbCategories - this.categories = categories - Library(categories, libraryManga) - }) + this.categories = categories + Library(categories, libraryManga) + } } /** @@ -304,7 +317,7 @@ class LibraryPresenter( * @param mangas the list of manga to delete. * @param deleteChapters whether to also delete downloaded chapters. */ - fun removeMangaFromLibrary(mangas: List, deleteChapters: Boolean) { + fun removeMangaFromLibrary(mangas: List) { // Create a set of the list val mangaToDelete = mangas.distinctBy { it.id } mangaToDelete.forEach { it.favorite = false } @@ -313,20 +326,29 @@ class LibraryPresenter( .onErrorResumeNext { Observable.empty() } .subscribeOn(Schedulers.io()) .subscribe() + } + fun confirmDeletion(mangas: List) { Observable.fromCallable { + val mangaToDelete = mangas.distinctBy { it.id } mangaToDelete.forEach { manga -> coverCache.deleteFromCache(manga.thumbnail_url) - if (deleteChapters) { - val source = sourceManager.get(manga.source) as? HttpSource - if (source != null) { - downloadManager.deleteManga(manga, source) - } - } + val source = sourceManager.get(manga.source) as? HttpSource + if (source != null) + downloadManager.deleteManga(manga, source) } - } - .subscribeOn(Schedulers.io()) - .subscribe() + }.subscribeOn(Schedulers.io()).subscribe() + } + + fun addMangas(mangas: List) { + val mangaToAdd = mangas.distinctBy { it.id } + mangaToAdd.forEach { it.favorite = true } + + Observable.fromCallable { db.insertMangas(mangaToAdd).executeAsBlocking() } + .onErrorResumeNext { Observable.empty() } + .subscribeOn(Schedulers.io()) + .subscribe() + mangaToAdd.forEach { db.insertManga(it).executeAsBlocking() } } /** @@ -347,6 +369,88 @@ class LibraryPresenter( db.setMangaCategories(mc, mangas) } + fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) { + val source = sourceManager.get(manga.source) ?: return + + //state = state.copy(isReplacingManga = true) + + Observable.defer { source.fetchChapterList(manga) } + .onErrorReturn { emptyList() } + .doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) } + .onErrorReturn { emptyList() } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + //.doOnUnsubscribe { state = state.copy(isReplacingManga = false) } + .subscribe() + } + + fun hideShowTitle(mangas: List, hide: Boolean) { + mangas.forEach { it.hide_title = hide } + db.inTransaction { + mangas.forEach { + db.updateMangaHideTitle(it).executeAsBlocking() + } + } + } + + private fun migrateMangaInternal(source: Source, sourceChapters: List, + prevManga: Manga, manga: Manga, replace: Boolean) { + + val flags = preferences.migrateFlags().getOrDefault() + val migrateChapters = MigrationFlags.hasChapters(flags) + val migrateCategories = MigrationFlags.hasCategories(flags) + val migrateTracks = MigrationFlags.hasTracks(flags) + + db.inTransaction { + // Update chapters read + if (migrateChapters) { + try { + syncChaptersWithSource(db, sourceChapters, manga, source) + } catch (e: Exception) { + // Worst case, chapters won't be synced + } + + val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() + val maxChapterRead = + prevMangaChapters.filter { it.read }.maxBy { it.chapter_number }?.chapter_number + if (maxChapterRead != null) { + val dbChapters = db.getChapters(manga).executeAsBlocking() + for (chapter in dbChapters) { + if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) { + chapter.read = true + } + } + db.insertChapters(dbChapters).executeAsBlocking() + } + } + // Update categories + if (migrateCategories) { + val categories = db.getCategoriesForManga(prevManga).executeAsBlocking() + val mangaCategories = categories.map { MangaCategory.create(manga, it) } + db.setMangaCategories(mangaCategories, listOf(manga)) + } + // Update track + if (migrateTracks) { + val tracks = db.getTracks(prevManga).executeAsBlocking() + for (track in tracks) { + track.id = null + track.manga_id = manga.id!! + } + db.insertTracks(tracks).executeAsBlocking() + } + // Update favorite status + if (replace) { + prevManga.favorite = false + db.updateMangaFavorite(prevManga).executeAsBlocking() + } + manga.favorite = true + db.updateMangaFavorite(manga).executeAsBlocking() + + // SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title + db.updateMangaTitle(manga).executeAsBlocking() + } + } + /** * Update cover with local file. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 88433d5a6e..37318f2901 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -3,15 +3,25 @@ package eu.kanade.tachiyomi.ui.main import android.animation.ObjectAnimator import android.app.SearchManager import android.content.Intent +import android.content.res.Configuration import android.graphics.Color +import android.graphics.Rect +import android.os.Build import android.os.Bundle -import android.support.v4.view.GravityCompat -import android.support.v4.widget.DrawerLayout -import android.support.v7.graphics.drawable.DrawerArrowDrawable +import android.view.MotionEvent +import androidx.core.view.GravityCompat +import androidx.appcompat.app.AppCompatDelegate.* +import androidx.appcompat.graphics.drawable.DrawerArrowDrawable +import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.core.graphics.ColorUtils import com.bluelinelabs.conductor.* +import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.Migrations import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.controller.* @@ -24,11 +34,19 @@ import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController import eu.kanade.tachiyomi.ui.setting.SettingsMainController +import eu.kanade.tachiyomi.util.doOnApplyWindowInsets +import eu.kanade.tachiyomi.util.getResourceColor +import eu.kanade.tachiyomi.util.launchUI +import eu.kanade.tachiyomi.util.marginBottom +import eu.kanade.tachiyomi.util.marginTop import eu.kanade.tachiyomi.util.openInBrowser +import eu.kanade.tachiyomi.util.updateLayoutParams +import eu.kanade.tachiyomi.util.updatePadding +import eu.kanade.tachiyomi.util.updatePaddingRelative import kotlinx.android.synthetic.main.main_activity.* +import kotlinx.coroutines.delay import uy.kohesive.injekt.injectLazy - class MainActivity : BaseActivity() { private lateinit var router: Router @@ -39,6 +57,20 @@ class MainActivity : BaseActivity() { private var secondaryDrawer: ViewGroup? = null + private var snackBar:Snackbar? = null + var extraViewForUndo:View? = null + private var canDismissSnackBar = false + + fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) { + this.snackBar = snackBar + canDismissSnackBar = false + launchUI { + delay(1000) + canDismissSnackBar = true + } + extraViewForUndo = extraViewToCheck + } + private val startScreenId by lazy { when (preferences.startScreen()) { 2 -> R.id.nav_drawer_recently_read @@ -50,10 +82,14 @@ class MainActivity : BaseActivity() { lateinit var tabAnimator: TabsAnimator override fun onCreate(savedInstanceState: Bundle?) { + setDefaultNightMode(when (preferences.theme()) { + 1 -> MODE_NIGHT_NO + 2, 3, 4 -> MODE_NIGHT_YES + else -> MODE_NIGHT_FOLLOW_SYSTEM + }) setTheme(when (preferences.theme()) { - 2 -> R.style.Theme_Tachiyomi_Dark - 3 -> R.style.Theme_Tachiyomi_Amoled - 4 -> R.style.Theme_Tachiyomi_DarkBlue + 3, 6 -> R.style.Theme_Tachiyomi_Amoled + 4, 7 -> R.style.Theme_Tachiyomi_DarkBlue else -> R.style.Theme_Tachiyomi }) super.onCreate(savedInstanceState) @@ -103,6 +139,75 @@ class MainActivity : BaseActivity() { val container: ViewGroup = findViewById(R.id.controller_container) + val content: LinearLayout = findViewById(R.id.main_content) + container.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + content.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + nav_view.doOnApplyWindowInsets { v, _, _ -> + v.updatePaddingRelative( + bottom = v.marginBottom, + top = v.marginTop + ) + } + content.setOnApplyWindowInsetsListener { v, insets -> + window.navigationBarColor = + // if the os does not support light nav bar and is portrait, draw a dark translucent + // nav bar + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + (v.rootWindowInsets.systemWindowInsetLeft > 0 || + v.rootWindowInsets.systemWindowInsetRight > 0)) + // For lollipop, draw opaque nav bar + Color.BLACK + else Color.argb(179, 0, 0, 0) + } + // if the android q+ device has gesture nav, transparent nav bar + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + && (v.rootWindowInsets.systemWindowInsetBottom != v.rootWindowInsets + .tappableElementInsets.bottom)) { + getColor(android.R.color.transparent) + } + // if in landscape with 2/3 button mode, fully opaque nav bar + else if (v.rootWindowInsets.systemWindowInsetLeft > 0 + || v.rootWindowInsets.systemWindowInsetRight > 0) { + getResourceColor(android.R.attr.colorBackground) + } + // if in portrait with 2/3 button mode, translucent nav bar + else { + ColorUtils.setAlphaComponent( + getResourceColor(android.R.attr.colorBackground), 179) + } + v.setPadding(insets.systemWindowInsetLeft, insets.systemWindowInsetTop, + insets.systemWindowInsetRight, 0) + insets + } + val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + if (Build.VERSION.SDK_INT >= 26 && currentNightMode == Configuration.UI_MODE_NIGHT_NO) { + content.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + } + + val drawerContainer: FrameLayout = findViewById(R.id.drawer_container) + drawerContainer.setOnApplyWindowInsetsListener { v, insets -> + val contextView = window?.decorView?.findViewById(R.id.action_mode_bar) + contextView?.updateLayoutParams { + leftMargin = insets.systemWindowInsetLeft + rightMargin = insets.systemWindowInsetRight + } + // Consume any horizontal insets and pad all content in. There's not much we can do + // with horizontal insets + v.updatePadding( + left = insets.systemWindowInsetLeft, + right = insets.systemWindowInsetRight + ) + insets.replaceSystemWindowInsets( + 0, insets.systemWindowInsetTop, + 0, insets.systemWindowInsetBottom + ) + } + router = Conductor.attachRouter(this, container, savedInstanceState) if (!router.hasRootController()) { // Set start screen @@ -150,6 +255,10 @@ class MainActivity : BaseActivity() { } private fun handleIntentAction(intent: Intent): Boolean { + val notificationId = intent.getIntExtra("notificationId", -1) + if (notificationId > -1) NotificationReceiver.dismissNotification( + applicationContext, notificationId, intent.getIntExtra("groupId", 0) + ) when (intent.action) { SHORTCUT_LIBRARY -> setSelectedDrawerItem(R.id.nav_drawer_library) SHORTCUT_RECENTLY_UPDATED -> setSelectedDrawerItem(R.id.nav_drawer_recent_updates) @@ -220,6 +329,32 @@ class MainActivity : BaseActivity() { router.setRoot(controller.withFadeTransaction().tag(id.toString())) } + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + if (ev?.action == MotionEvent.ACTION_DOWN) { + if (snackBar != null && snackBar!!.isShown) { + val sRect = Rect() + snackBar!!.view.getGlobalVisibleRect(sRect) + + val extRect:Rect? = if (extraViewForUndo != null) Rect() else null + extraViewForUndo?.getGlobalVisibleRect(extRect) + //This way the snackbar will only be dismissed if + //the user clicks outside it. + if (canDismissSnackBar && !sRect.contains(ev.x.toInt(), ev.y.toInt()) + && (extRect == null || + !extRect.contains(ev.x.toInt(), ev.y.toInt()))) { + snackBar?.dismiss() + snackBar = null + extraViewForUndo = null + } + } + else if (snackBar != null) { + snackBar = null + extraViewForUndo = null + } + } + return super.dispatchTouchEvent(ev) + } + private fun syncActivityViewWithController(to: Controller?, from: Controller? = null) { if (from is DialogController || to is DialogController) { return @@ -227,9 +362,9 @@ class MainActivity : BaseActivity() { val showHamburger = router.backstackSize == 1 if (showHamburger) { - drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) + drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED) } else { - drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) + drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED) } ObjectAnimator.ofFloat(drawerArrow, "progress", if (showHamburger) 0f else 1f).start() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/TabsAnimator.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/TabsAnimator.kt index 304d32f2d4..c975c9516f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/TabsAnimator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/TabsAnimator.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.main import android.animation.ObjectAnimator -import android.support.design.widget.TabLayout +import com.google.android.material.tabs.TabLayout import android.view.ViewTreeObserver import android.view.animation.DecelerateInterpolator diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index c56cc7541c..698b179e0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -1,24 +1,26 @@ package eu.kanade.tachiyomi.ui.manga import android.Manifest.permission.WRITE_EXTERNAL_STORAGE +import android.app.Activity import android.os.Bundle -import android.support.design.widget.TabLayout -import android.support.graphics.drawable.VectorDrawableCompat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import android.widget.TextView +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.support.RouterPagerAdapter +import com.google.android.material.tabs.TabLayout import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.PublishRelay import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager @@ -38,7 +40,10 @@ import java.util.Date class MangaController : RxController, TabbedController { - constructor(manga: Manga?, fromCatalogue: Boolean = false) : super(Bundle().apply { + constructor(manga: Manga?, fromCatalogue: Boolean = false, fromExtension: Boolean = false) : + super + (Bundle() + .apply { putLong(MANGA_EXTRA, manga?.id ?: 0) putBoolean(FROM_CATALOGUE_EXTRA, fromCatalogue) }) { @@ -46,13 +51,37 @@ class MangaController : RxController, TabbedController { if (manga != null) { source = Injekt.get().getOrStub(manga.source) } + backClosesApp = fromExtension + } + + constructor(manga: Manga?, startY:Float?) : super(Bundle().apply { + putLong(MANGA_EXTRA, manga?.id ?: 0) + putBoolean(FROM_CATALOGUE_EXTRA, false) + }) { + this.manga = manga + startingChapterYPos = startY + if (manga != null) { + source = Injekt.get().getOrStub(manga.source) + } } constructor(mangaId: Long) : this( Injekt.get().getManga(mangaId).executeAsBlocking()) - @Suppress("unused") - constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA)) + constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA)) { + val notificationId = bundle.getInt("notificationId", -1) + val context = applicationContext ?: return + if (notificationId > -1) NotificationReceiver.dismissNotification( + context, notificationId, bundle.getInt("groupId", 0) + ) + } + + override fun handleBack(): Boolean { + return if (backClosesApp) { + activity?.finishAffinity() + true + } else super.handleBack() + } var manga: Manga? = null private set @@ -60,6 +89,10 @@ class MangaController : RxController, TabbedController { var source: Source? = null private set + private var backClosesApp = false + + var startingChapterYPos:Float? = null + private var adapter: MangaDetailAdapter? = null val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false) @@ -161,10 +194,11 @@ class MangaController : RxController, TabbedController { } override fun configureRouter(router: Router, position: Int) { + val touchOffset = if (activity?.tabs?.height == 0) 144f else 0f if (!router.hasRootController()) { val controller = when (position) { INFO_CONTROLLER -> MangaInfoController() - CHAPTERS_CONTROLLER -> ChaptersController() + CHAPTERS_CONTROLLER -> ChaptersController(startingChapterYPos?.minus(touchOffset)) TRACK_CONTROLLER -> TrackController() else -> error("Wrong position $position") } @@ -187,7 +221,7 @@ class MangaController : RxController, TabbedController { const val CHAPTERS_CONTROLLER = 1 const val TRACK_CONTROLLER = 2 - private val tabField = TabLayout.Tab::class.java.getDeclaredField("mView") + private val tabField = TabLayout.Tab::class.java.getDeclaredField("view") .apply { isAccessible = true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt index d8639cfb8a..c85c359ae3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem(), Chapter by chapter { @@ -26,14 +28,14 @@ class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem return R.layout.chapters_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): ChapterHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ChapterHolder { return ChapterHolder(view, adapter as ChaptersAdapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ChapterHolder, position: Int, - payloads: List?) { + payloads: MutableList?) { holder.bind(this, manga) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt index 8397e0b414..7232cdda9d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt @@ -5,12 +5,15 @@ import android.animation.AnimatorListenerAdapter import android.annotation.SuppressLint import android.app.Activity import android.content.Intent -import android.support.design.widget.Snackbar -import android.support.v7.app.AppCompatActivity -import android.support.v7.view.ActionMode -import android.support.v7.widget.DividerItemDecoration -import android.support.v7.widget.LinearLayoutManager +import android.os.Build +import com.google.android.material.snackbar.Snackbar +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager import android.view.* +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.snackbar.BaseTransientBottomBar import com.jakewharton.rxbinding.support.v4.widget.refreshes import com.jakewharton.rxbinding.view.clicks import eu.davidea.flexibleadapter.FlexibleAdapter @@ -23,13 +26,18 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import eu.kanade.tachiyomi.util.getCoordinates -import eu.kanade.tachiyomi.util.snack -import eu.kanade.tachiyomi.util.toast +import eu.kanade.tachiyomi.util.* import kotlinx.android.synthetic.main.chapters_controller.* import timber.log.Timber +import com.google.android.material.floatingactionbutton.FloatingActionButton +import android.content.Context +import android.util.AttributeSet +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.RecyclerView +import eu.kanade.tachiyomi.ui.main.MainActivity +import kotlin.math.* -class ChaptersController : NucleusController(), +class ChaptersController() : NucleusController(), ActionMode.Callback, FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, @@ -40,16 +48,23 @@ class ChaptersController : NucleusController(), DownloadCustomChaptersDialog.Listener, DeleteChaptersDialog.Listener { + constructor(startY: Float?) : this() { + this.startingChapterYPos = startY + } + /** * Adapter containing a list of chapters. */ private var adapter: ChaptersAdapter? = null + private var scrollToUnread = true + /** * Action mode for multiple selection. */ private var actionMode: ActionMode? = null + private var snack:Snackbar? = null /** * Selected items. Used to restore selections after a rotation. */ @@ -59,6 +74,7 @@ class ChaptersController : NucleusController(), setHasOptionsMenu(true) setOptionsMenuHidden(true) } + var startingChapterYPos:Float? = null override fun createPresenter(): ChaptersPresenter { val ctrl = parentController as MangaController @@ -82,6 +98,16 @@ class ChaptersController : NucleusController(), recycler.setHasFixedSize(true) adapter?.fastScroller = fast_scroller + val fabBaseMarginBottom = fab?.marginBottom ?: 0 + recycler.doOnApplyWindowInsets { v, insets, padding -> + v.updatePaddingRelative(bottom = padding.bottom + insets.systemWindowInsetBottom) + fab?.updateLayoutParams { + bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom + } + fast_scroller?.updateLayoutParams { + bottomMargin = insets.systemWindowInsetBottom + } + } swipe_refresh.refreshes().subscribeUntilDestroy { fetchChaptersFromSource() } fab.clicks().subscribeUntilDestroy { @@ -99,8 +125,15 @@ class ChaptersController : NucleusController(), if (!reveal_view.showRevealEffect(coordinates.x, coordinates.y, revealAnimationListener)) { openChapter(item.chapter) } - } else { - view.context.toast(R.string.no_next_chapter) + } else if (snack == null || snack?.getText() != view.context.getString(R.string.no_next_chapter)) { + snack = view.snack(R.string.no_next_chapter, Snackbar.LENGTH_LONG) { + addCallback(object : BaseTransientBottomBar.BaseCallback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + if (snack == transientBottomBar) snack = null + } + }) + } } } } @@ -184,8 +217,9 @@ class ChaptersController : NucleusController(), fun onNextChapters(chapters: List) { // If the list is empty, fetch chapters from source if the conditions are met // We use presenter chapters instead because they are always unfiltered - if (presenter.chapters.isEmpty()) + if (presenter.chapters.isEmpty()) { initialFetchChapters() + } val adapter = adapter ?: return adapter.updateDataSet(chapters) @@ -201,7 +235,21 @@ class ChaptersController : NucleusController(), } actionMode?.invalidate() } + scrollToUnread() + } + private fun scrollToUnread() { + if (adapter?.items.isNullOrEmpty()) return + if (scrollToUnread) { + val index = presenter.getFirstUnreadIndex() + val centerOfScreen = + if (startingChapterYPos != null) startingChapterYPos!!.toInt() - recycler.top - 96 + else recycler.height / 2 - 96 + (recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( + index, centerOfScreen + ) + } + scrollToUnread = false } private fun initialFetchChapters() { @@ -242,7 +290,7 @@ class ChaptersController : NucleusController(), startActivity(intent) } - override fun onItemClick(position: Int): Boolean { + override fun onItemClick(view: View?, position: Int): Boolean { val adapter = adapter ?: return false val item = adapter.getItem(position) ?: return false if (actionMode != null && adapter.mode == SelectableAdapter.Mode.MULTI) { @@ -325,6 +373,11 @@ class ChaptersController : NucleusController(), actionMode = null } + override fun onDetach(view: View) { + destroyActionModeIfNeeded() + super.onDetach(view) + } + override fun onMenuItemClick(position: Int, item: MenuItem) { val chapter = adapter?.getItem(position) ?: return val chapters = listOf(chapter) @@ -364,12 +417,20 @@ class ChaptersController : NucleusController(), val view = view destroyActionModeIfNeeded() presenter.downloadChapters(chapters) - if (view != null && !presenter.manga.favorite) { - recycler?.snack(view.context.getString(R.string.snack_add_to_library), Snackbar.LENGTH_INDEFINITE) { + if (view != null && !presenter.manga.favorite && (snack == null || + snack?.getText() != view.context.getString(R.string.snack_add_to_library))) { + snack = view.snack(view.context.getString(R.string.snack_add_to_library), Snackbar.LENGTH_INDEFINITE) { setAction(R.string.action_add) { presenter.addToLibrary() } + addCallback(object : BaseTransientBottomBar.BaseCallback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + if (snack == transientBottomBar) snack = null + } + }) } + (activity as? MainActivity)?.setUndoSnackBar(snack) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index ff000061e3..65419a5a74 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -223,7 +223,7 @@ class ChaptersPresenter( */ fun getNextUnreadChapter(): ChapterItem? { return chapters.sortedByDescending { it.source_order }.find { !it.read } - } +} /** * Mark the selected chapter list as read/unread. @@ -415,4 +415,9 @@ class ChaptersPresenter( return manga.sortDescending() } + fun getFirstUnreadIndex(): Int { + val index = chapters.sortedByDescending { it.source_order }.indexOfFirst { !it.read } + return if (sortDescending()) (chapters.size - 1) - index + else index + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index 5340627072..77847e80dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -6,22 +6,27 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.drawable.Drawable -import android.net.Uri import android.os.Build import android.os.Bundle -import android.support.customtabs.CustomTabsIntent -import android.support.v4.content.pm.ShortcutInfoCompat -import android.support.v4.content.pm.ShortcutManagerCompat -import android.support.v4.graphics.drawable.IconCompat -import android.view.* -import android.widget.Toast +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat import com.afollestad.materialdialogs.MaterialDialog import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.target.SimpleTarget +import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar import com.jakewharton.rxbinding.support.v4.widget.refreshes import com.jakewharton.rxbinding.view.clicks import com.jakewharton.rxbinding.view.longClicks @@ -39,20 +44,26 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog +import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.util.getResourceColor +import eu.kanade.tachiyomi.util.doOnApplyWindowInsets +import eu.kanade.tachiyomi.util.getUriCompat +import eu.kanade.tachiyomi.util.marginBottom import eu.kanade.tachiyomi.util.openInBrowser import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.toast -import eu.kanade.tachiyomi.util.truncateCenter +import eu.kanade.tachiyomi.util.updateLayoutParams +import eu.kanade.tachiyomi.util.updatePaddingRelative import jp.wasabeef.glide.transformations.CropSquareTransformation import jp.wasabeef.glide.transformations.MaskTransformation import kotlinx.android.synthetic.main.manga_info_controller.* import uy.kohesive.injekt.injectLazy +import java.io.File import java.text.DateFormat import java.text.DecimalFormat import java.util.Date +import kotlin.math.max /** * Fragment that shows manga information. @@ -67,6 +78,13 @@ class MangaInfoController : NucleusController(), */ private val preferences: PreferencesHelper by injectLazy() + /** + * Snackbar containing an error message when a request fails. + */ + private var snack: Snackbar? = null + + private var container:View? = null + init { setHasOptionsMenu(true) setOptionsMenuHidden(true) @@ -89,13 +107,14 @@ class MangaInfoController : NucleusController(), fab_favorite.clicks().subscribeUntilDestroy { onFabClick() } // Set onLongClickListener to manage categories when FAB is clicked. - fab_favorite.longClicks().subscribeUntilDestroy{ onFabLongClick() } + fab_favorite.longClicks().subscribeUntilDestroy { onFabLongClick() } // Set SwipeRefresh to refresh manga data. swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() } manga_full_title.longClicks().subscribeUntilDestroy { - copyToClipboard(view.context.getString(R.string.title), manga_full_title.text.toString()) + copyToClipboard(view.context.getString(R.string.title), manga_full_title.text + .toString(), R.string.manga_info_full_title_label) } manga_full_title.clicks().subscribeUntilDestroy { @@ -103,7 +122,8 @@ class MangaInfoController : NucleusController(), } manga_artist.longClicks().subscribeUntilDestroy { - copyToClipboard(manga_artist_label.text.toString(), manga_artist.text.toString()) + copyToClipboard(manga_artist_label.text.toString(), manga_artist.text.toString(), R + .string.manga_info_artist_label) } manga_artist.clicks().subscribeUntilDestroy { @@ -111,7 +131,8 @@ class MangaInfoController : NucleusController(), } manga_author.longClicks().subscribeUntilDestroy { - copyToClipboard(manga_author.text.toString(), manga_author.text.toString()) + copyToClipboard(manga_author.text.toString(), manga_author.text.toString(), R.string + .manga_info_author_label) } manga_author.clicks().subscribeUntilDestroy { @@ -119,15 +140,39 @@ class MangaInfoController : NucleusController(), } manga_summary.longClicks().subscribeUntilDestroy { - copyToClipboard(view.context.getString(R.string.description), manga_summary.text.toString()) + copyToClipboard(view.context.getString(R.string.description), manga_summary.text + .toString(), R.string.description) } - //manga_genres_tags.setOnTagClickListener { tag -> performGlobalSearch(tag) } + manga_genres_tags.setOnTagClickListener { tag -> performLocalSearch(tag) } manga_cover.longClicks().subscribeUntilDestroy { - copyToClipboard(view.context.getString(R.string.title), presenter.manga.title) + copyToClipboard(view.context.getString(R.string.title), presenter.manga.title, R.string.manga_info_full_title_label) + } + container = (view as ViewGroup).findViewById(R.id.manga_info_layout) as? View + val bottomM = manga_genres_tags.marginBottom + val fabBaseMarginBottom = fab_favorite.marginBottom + val manga_coverMarginBottom = fab_favorite.marginBottom + container?.doOnApplyWindowInsets { v, insets, padding -> + if (resources?.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) { + fab_favorite?.updateLayoutParams { + bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom + } + manga_cover?.updateLayoutParams { + bottomMargin = manga_coverMarginBottom + insets.systemWindowInsetBottom + } + } + else { + manga_genres_tags?.updateLayoutParams { + bottomMargin = bottomM + +insets.systemWindowInsetBottom + } + } + } + info_scrollview.doOnApplyWindowInsets { v, insets, padding -> + v.updatePaddingRelative( + bottom = max(padding.bottom, insets.systemWindowInsetBottom) + ) } - } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -138,7 +183,7 @@ class MangaInfoController : NucleusController(), when (item.itemId) { R.id.action_open_in_browser -> openInBrowser() R.id.action_open_in_web_view -> openInWebView() - R.id.action_share -> shareManga() + R.id.action_share -> prepareToShareManga() R.id.action_add_to_home_screen -> addToHomeScreen() else -> return super.onOptionsItemSelected(item) } @@ -244,6 +289,7 @@ class MangaInfoController : NucleusController(), override fun onDestroyView(view: View) { manga_genres_tags.setOnTagClickListener(null) + snack?.dismiss() super.onDestroyView(view) } @@ -272,16 +318,7 @@ class MangaInfoController : NucleusController(), * Toggles the favorite status and asks for confirmation to delete downloaded chapters. */ private fun toggleFavorite() { - val view = view - - val isNowFavorite = presenter.toggleFavorite() - if (view != null && !isNowFavorite && presenter.hasDownloads()) { - view.snack(view.context.getString(R.string.delete_downloads_for_manga)) { - setAction(R.string.action_delete) { - presenter.deleteDownloads() - } - } - } + presenter.toggleFavorite() } /** @@ -291,34 +328,60 @@ class MangaInfoController : NucleusController(), val context = view?.context ?: return val source = presenter.source as? HttpSource ?: return - context.openInBrowser(source.mangaDetailsRequest(presenter.manga).url().toString()) + context.openInBrowser(source.mangaDetailsRequest(presenter.manga).url.toString()) } private fun openInWebView() { val source = presenter.source as? HttpSource ?: return val url = try { - source.mangaDetailsRequest(presenter.manga).url().toString() + source.mangaDetailsRequest(presenter.manga).url.toString() } catch (e: Exception) { return } - parentController?.router?.pushController(MangaWebViewController(source.id, url) - .withFadeTransaction()) + val activity = activity ?: return + val intent = WebViewActivity.newIntent(activity, source.id, url, presenter.manga.title) + startActivity(intent) } /** * Called to run Intent with [Intent.ACTION_SEND], which show share dialog. */ - private fun shareManga() { + private fun prepareToShareManga() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && manga_cover.drawable != null) + GlideApp.with(activity!!).asBitmap().load(presenter.manga).into(object : + CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + presenter.shareManga(resource) + } + override fun onLoadCleared(placeholder: Drawable?) {} + + override fun onLoadFailed(errorDrawable: Drawable?) { + shareManga() + } + }) + else shareManga() + } + + /** + * Called to run Intent with [Intent.ACTION_SEND], which show share dialog. + */ + fun shareManga(cover: File? = null) { val context = view?.context ?: return val source = presenter.source as? HttpSource ?: return + val stream = cover?.getUriCompat(context) try { - val url = source.mangaDetailsRequest(presenter.manga).url().toString() + val url = source.mangaDetailsRequest(presenter.manga).url.toString() val intent = Intent(Intent.ACTION_SEND).apply { - type = "text/plain" + type = "text/*" putExtra(Intent.EXTRA_TEXT, url) + putExtra(Intent.EXTRA_TITLE, presenter.manga.title) + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + if (stream != null) { + clipData = ClipData.newRawUri(null, stream) + } } startActivity(Intent.createChooser(intent, context.getString(R.string.action_share))) } catch (e: Exception) { @@ -382,11 +445,12 @@ class MangaInfoController : NucleusController(), toggleFavorite() if (manga.favorite) { val categories = presenter.getCategories() - val defaultCategory = categories.find { it.id == preferences.defaultCategory() } + val defaultCategoryId = preferences.defaultCategory() + val defaultCategory = categories.find { it.id == defaultCategoryId } when { defaultCategory != null -> presenter.moveMangaToCategory(manga, defaultCategory) - categories.size <= 1 -> // default or the one from the user - presenter.moveMangaToCategory(manga, categories.firstOrNull()) + defaultCategoryId == 0 || categories.isEmpty() -> // 'Default' or no category + presenter.moveMangaToCategory(manga, null) else -> { val ids = presenter.getMangaCategoryIds(manga) val preselected = ids.mapNotNull { id -> @@ -397,9 +461,35 @@ class MangaInfoController : NucleusController(), .showDialog(router) } } - activity?.toast(activity?.getString(R.string.manga_added_library)) + showAddedSnack() } else { - activity?.toast(activity?.getString(R.string.manga_removed_library)) + showRemovedSnack() + } + } + + private fun showAddedSnack() { + val view = container + snack?.dismiss() + snack = view?.snack(view.context.getString(R.string.manga_added_library)) + } + + private fun showRemovedSnack() { + val view = container + snack?.dismiss() + if (view != null) { + snack = view.snack(view.context.getString(R.string.manga_removed_library), Snackbar.LENGTH_INDEFINITE) { + setAction(R.string.action_undo) { + presenter.setFavorite(true) + } + addCallback(object : BaseTransientBottomBar.BaseCallback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + if (!presenter.manga.favorite) + presenter.confirmDeletion() + } + }) + } + (activity as? MainActivity)?.setUndoSnackBar(snack, fab_favorite) } } @@ -410,12 +500,12 @@ class MangaInfoController : NucleusController(), val manga = presenter.manga if (!manga.favorite) { toggleFavorite() - activity?.toast(activity?.getString(R.string.manga_added_library)) + showAddedSnack() } val categories = presenter.getCategories() - if (categories.size <= 1) { - // default or the one from the user then just add to favorite. - presenter.moveMangaToCategory(manga, categories.firstOrNull()) + if (categories.isEmpty()) { + // no categories exist, display a message about adding categories + snack = container?.snack(R.string.action_add_category) } else { val ids = presenter.getMangaCategoryIds(manga) val preselected = ids.mapNotNull { id -> @@ -490,11 +580,13 @@ class MangaInfoController : NucleusController(), 3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star)) } } - .into(object : SimpleTarget(96, 96) { + .into(object : CustomTarget(96, 96) { override fun onResourceReady(resource: Bitmap, transition: Transition?) { createShortcut(resource) } + override fun onLoadCleared(placeholder: Drawable?) { } + override fun onLoadFailed(errorDrawable: Drawable?) { activity?.toast(R.string.icon_creation_fail) } @@ -507,17 +599,17 @@ class MangaInfoController : NucleusController(), * @param label Label to show to the user describing the content * @param content the actual text to copy to the board */ - private fun copyToClipboard(label: String, content: String) { + private fun copyToClipboard(label: String, content: String, resId: Int) { if (content.isBlank()) return val activity = activity ?: return val view = view ?: return val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - clipboard.primaryClip = ClipData.newPlainText(label, content) + clipboard.setPrimaryClip(ClipData.newPlainText(label, content)) - activity.toast(view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)), - Toast.LENGTH_SHORT) + snack = container?.snack(view.context.getString(R.string.copied_to_clipboard, view.context + .getString(resId))) } /** @@ -525,11 +617,25 @@ class MangaInfoController : NucleusController(), * * @param query the search query to pass to the search controller */ - fun performGlobalSearch(query: String) { + private fun performGlobalSearch(query: String) { val router = parentController?.router ?: return router.pushController(CatalogueSearchController(query).withFadeTransaction()) } + /** + * Perform a local search using the provided query. + * + * @param query the search query to pass to the library controller + */ + private fun performLocalSearch(query: String) { + val router = parentController?.router ?: return + val firstController = router.backstack.first()?.controller() + if (firstController is LibraryController && router.backstack.size == 2) { + router.handleBack() + firstController.search(query) + } + } + /** * Create shortcut using ShortcutManager. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index 5083498cea..a87f56d38b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.ui.manga.info +import android.app.Application +import android.graphics.Bitmap import android.os.Bundle import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.PublishRelay @@ -11,6 +13,8 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.util.DiskUtil +import eu.kanade.tachiyomi.util.ImageUtil import eu.kanade.tachiyomi.util.isNullOrUnsubscribed import rx.Observable import rx.Subscription @@ -18,6 +22,11 @@ import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.InputStream +import java.io.OutputStream import java.util.* /** @@ -100,39 +109,56 @@ class MangaInfoPresenter( */ fun toggleFavorite(): Boolean { manga.favorite = !manga.favorite - if (!manga.favorite) { - coverCache.deleteFromCache(manga.thumbnail_url) - } db.insertManga(manga).executeAsBlocking() sendMangaToView() return manga.favorite } - private fun setFavorite(favorite: Boolean) { + fun confirmDeletion() { + coverCache.deleteFromCache(manga.thumbnail_url) + downloadManager.deleteManga(manga, source) + } + + fun setFavorite(favorite: Boolean) { if (manga.favorite == favorite) { return } toggleFavorite() } - /** - * Returns true if the manga has any downloads. - */ - fun hasDownloads(): Boolean { - return downloadManager.getDownloadCount(manga) > 0 + fun shareManga(cover:Bitmap) { + val context = Injekt.get() + + val destDir = File(context.cacheDir, "shared_image") + + Observable.fromCallable { destDir.deleteRecursively() } // Keep only the last shared file + .map { saveImage(cover, destDir, manga) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeFirst( + { view, file -> view.shareManga(file) }, + { view, error -> view.shareManga() } + ) + } + + private fun saveImage(cover:Bitmap, directory: File, manga: Manga): File? { + directory.mkdirs() + + // Build destination file. + val filename = DiskUtil.buildValidFilename("${manga.title} - Cover.jpg") + + val destFile = File(directory, filename) + val stream: OutputStream = FileOutputStream(destFile) + cover.compress(Bitmap.CompressFormat.JPEG,75,stream) + stream.flush() + stream.close() + return destFile } /** - * Deletes all the downloads for the manga. - */ - fun deleteDownloads() { - downloadManager.deleteManga(manga, source) - } - - /** - * 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 { return db.getCategories().executeAsBlocking() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaWebViewController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaWebViewController.kt deleted file mode 100644 index 8d7d9687a0..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaWebViewController.kt +++ /dev/null @@ -1,58 +0,0 @@ -package eu.kanade.tachiyomi.ui.manga.info - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.webkit.WebView -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.ui.base.controller.BaseController -import eu.kanade.tachiyomi.util.WebViewClientCompat -import uy.kohesive.injekt.injectLazy - -class MangaWebViewController(bundle: Bundle? = null) : BaseController(bundle) { - - private val sourceManager by injectLazy() - - constructor(sourceId: Long, url: String) : this(Bundle().apply { - putLong(SOURCE_KEY, sourceId) - putString(URL_KEY, url) - }) - - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - return inflater.inflate(R.layout.manga_info_web_controller, container, false) - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) - val source = sourceManager.get(args.getLong(SOURCE_KEY)) as? HttpSource ?: return - val url = args.getString(URL_KEY) ?: return - val headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" } - - val web = view as WebView - web.webViewClient = object : WebViewClientCompat() { - override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean { - view.loadUrl(url) - return true - } - } - web.settings.javaScriptEnabled = true - web.settings.userAgentString = source.headers["User-Agent"] - web.loadUrl(url, headers) - } - - override fun onDestroyView(view: View) { - val web = view as WebView - web.stopLoading() - web.destroy() - super.onDestroyView(view) - } - - private companion object { - const val SOURCE_KEY = "source_key" - const val URL_KEY = "url_key" - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/WebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/WebViewActivity.kt new file mode 100644 index 0000000000..0d70817881 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/WebViewActivity.kt @@ -0,0 +1,216 @@ +package eu.kanade.tachiyomi.ui.manga.info + +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.webkit.WebView +import android.widget.LinearLayout +import androidx.core.graphics.ColorUtils +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.ui.base.activity.BaseActivity +import eu.kanade.tachiyomi.util.WebViewClientCompat +import eu.kanade.tachiyomi.util.doOnApplyWindowInsets +import eu.kanade.tachiyomi.util.getResourceColor +import eu.kanade.tachiyomi.util.marginBottom +import eu.kanade.tachiyomi.util.updateLayoutParams +import eu.kanade.tachiyomi.util.updatePadding +import kotlinx.android.synthetic.main.webview_activity.* +import uy.kohesive.injekt.injectLazy + +class WebViewActivity : BaseActivity() { + + private val sourceManager by injectLazy() + private var bundle:Bundle? = null + val preferences: PreferencesHelper by injectLazy() + + companion object { + const val SOURCE_KEY = "source_key" + const val URL_KEY = "url_key" + const val TITLE_KEY = "title_key" + + fun newIntent(context: Context, sourceId: Long, url: String, title:String?): Intent { + val intent = Intent(context, WebViewActivity::class.java) + intent.putExtra(SOURCE_KEY, sourceId) + intent.putExtra(URL_KEY, url) + intent.putExtra(TITLE_KEY, title) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + return intent + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(when (preferences.theme()) { + 3, 6 -> R.style.Theme_Tachiyomi_Amoled + 4, 7 -> R.style.Theme_Tachiyomi_DarkBlue + else -> R.style.Theme_Tachiyomi + }) + super.onCreate(savedInstanceState) + setContentView(R.layout.webview_activity) + title = intent.extras?.getString(TITLE_KEY) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + toolbar.setNavigationOnClickListener { + super.onBackPressed() + } + + val container:ViewGroup = findViewById(R.id.web_view_layout) + val content: LinearLayout = findViewById(R.id.web_linear_layout) + container.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + content.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + + container.setOnApplyWindowInsetsListener { v, insets -> + val contextView = window?.decorView?.findViewById(R.id.action_mode_bar) + contextView?.updateLayoutParams { + leftMargin = insets.systemWindowInsetLeft + rightMargin = insets.systemWindowInsetRight + } + // Consume any horizontal insets and pad all content in. There's not much we can do + // with horizontal insets + v.updatePadding( + left = insets.systemWindowInsetLeft, + right = insets.systemWindowInsetRight + ) + insets.replaceSystemWindowInsets( + 0, insets.systemWindowInsetTop, + 0, insets.systemWindowInsetBottom + ) + } + window.statusBarColor = getResourceColor(R.attr.colorPrimary) + + content.setOnApplyWindowInsetsListener { v, insets -> + window.navigationBarColor = + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + v.context.getResourceColor(android.R.attr.colorPrimary) + } + // if the android q+ device has gesture nav, transparent nav bar + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + && (v.rootWindowInsets.systemWindowInsetBottom != v.rootWindowInsets + .tappableElementInsets.bottom)) { + getColor(android.R.color.transparent) + } else { + v.context.getResourceColor(android.R.attr.colorBackground) + } + v.setPadding(insets.systemWindowInsetLeft, insets.systemWindowInsetTop, + insets.systemWindowInsetRight, 0) + insets + } + val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + if (Build.VERSION.SDK_INT >= 26 && currentNightMode == Configuration.UI_MODE_NIGHT_NO) { + content.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + } + + if (bundle == null) { + val source = sourceManager.get(intent.extras!!.getLong(SOURCE_KEY)) as? HttpSource ?: return + val url = intent.extras!!.getString(URL_KEY) ?: return + val headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" } + webview.webViewClient = object : WebViewClientCompat() { + override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean { + view.loadUrl(url) + return true + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + invalidateOptionsMenu() + title = view?.title + } + + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + invalidateOptionsMenu() + } + } + val marginB = webview.marginBottom + webview.doOnApplyWindowInsets { v, insets, _ -> + val bottomInset = + if (Build.VERSION.SDK_INT >= 29) insets.tappableElementInsets.bottom + else insets.systemWindowInsetBottom + v.updateLayoutParams { + bottomMargin = marginB + bottomInset + } + } + webview.settings.javaScriptEnabled = true + webview.settings.userAgentString = source.headers["User-Agent"] + webview.loadUrl(url, headers) + } + else { + webview.restoreState(bundle) + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + window.statusBarColor = getResourceColor(R.attr.colorPrimary) + toolbar.setBackgroundColor(getResourceColor(R.attr.colorPrimary)) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + window.navigationBarColor = getResourceColor(android.R.attr.colorPrimary) + else if (window.navigationBarColor != getColor(android.R.color.transparent)) + window.navigationBarColor = getResourceColor(android.R.attr.colorBackground) + + val currentNightMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK + if (Build.VERSION.SDK_INT >= 26) { + if (currentNightMode == Configuration.UI_MODE_NIGHT_NO) { + web_linear_layout.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + } else { + web_linear_layout.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + } + } + } + + /** + * Called when the options menu of the toolbar is being created. It adds our custom menu. + */ + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.webview, menu) + return true + } + + override fun onPrepareOptionsMenu(menu: Menu?): Boolean { + val backItem = toolbar.menu.findItem(R.id.action_web_back) + val forwardItem = toolbar.menu.findItem(R.id.action_web_forward) + backItem?.isEnabled = webview.canGoBack() + forwardItem?.isEnabled = webview.canGoForward() + val hasHistory = webview.canGoBack() || webview.canGoForward() + backItem?.isVisible = hasHistory + forwardItem?.isVisible = hasHistory + val translucentWhite = ColorUtils.setAlphaComponent(Color.WHITE, 127) + backItem.icon?.setTint(if (webview.canGoBack()) Color.WHITE else translucentWhite) + forwardItem?.icon?.setTint(if (webview.canGoForward()) Color.WHITE else translucentWhite) + return super.onPrepareOptionsMenu(menu) + } + + override fun onBackPressed() { + if (webview.canGoBack()) webview.goBack() + else super.onBackPressed() + } + + /** + * Called when an item of the options menu was clicked. Used to handle clicks on our menu + * entries. + */ + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_web_back -> webview.goBack() + R.id.action_web_forward -> webview.goForward() + else -> return super.onOptionsItemSelected(item) + } + return true + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt index bb8871097b..d8be0c8741 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt @@ -1,11 +1,11 @@ package eu.kanade.tachiyomi.ui.manga.track -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.ViewGroup import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.inflate -class TrackAdapter(controller: TrackController) : RecyclerView.Adapter() { +class TrackAdapter(controller: TrackController) : androidx.recyclerview.widget.RecyclerView.Adapter() { var items = emptyList() set(value) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt index 0acf3381d0..615d6efe59 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.manga.track import android.content.Intent import android.net.Uri -import android.support.v7.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.toast import kotlinx.android.synthetic.main.track_controller.* import timber.log.Timber @@ -42,8 +43,9 @@ class TrackController : NucleusController(), adapter = TrackAdapter(this) with(view) { - track_recycler.layoutManager = LinearLayoutManager(context) + track_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) track_recycler.adapter = adapter + track_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) swipe_refresh.isEnabled = false swipe_refresh.refreshes().subscribeUntilDestroy { presenter.refresh() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt index c9b3f32654..491673869b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt @@ -19,7 +19,7 @@ class TrackSearchAdapter(context: Context) override fun getView(position: Int, view: View?, parent: ViewGroup): View { var v = view // Get the data item for this position - val track = getItem(position) + val track = getItem(position)!! // Check if an existing view is being reused, otherwise inflate the view val holder: TrackSearchHolder // view lookup cache stored in tag if (v == null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt index c0fd058cd2..353aeeba16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt @@ -6,10 +6,12 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import kotlinx.android.synthetic.main.catalogue_list_item.* +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible class MangaHolder( private val view: View, - private val adapter: FlexibleAdapter<*> + private val adapter: FlexibleAdapter> ) : BaseFlexibleViewHolder(view, adapter) { fun bind(item: MangaItem) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaItem.kt index b8f3602c14..6e06b30246 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaItem.kt @@ -5,6 +5,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible class MangaItem(val manga: Manga) : AbstractFlexibleItem() { @@ -12,14 +14,14 @@ class MangaItem(val manga: Manga) : AbstractFlexibleItem() { return R.layout.catalogue_list_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): MangaHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): MangaHolder { return MangaHolder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: MangaHolder, position: Int, - payloads: List?) { + payloads: MutableList?) { holder.bind(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt index a72bcc8e5a..323b905e06 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.migration import android.app.Dialog import android.os.Bundle -import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -15,11 +14,13 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import kotlinx.android.synthetic.main.migration_controller.* class MigrationController : NucleusController(), FlexibleAdapter.OnItemClickListener, - SourceAdapter.OnSelectClickListener { + SourceAdapter.OnSelectClickListener, + MigrationInterface { private var adapter: FlexibleAdapter>? = null @@ -37,12 +38,21 @@ class MigrationController : NucleusController(), return inflater.inflate(R.layout.migration_controller, container, false) } + fun searchController(manga:Manga): SearchController { + val controller = SearchController(manga) + controller.targetController = this + + return controller + } + override fun onViewCreated(view: View) { super.onViewCreated(view) adapter = FlexibleAdapter(null, this) - migration_recycler.layoutManager = LinearLayoutManager(view.context) + migration_recycler.layoutManager = + androidx.recyclerview.widget.LinearLayoutManager(view.context) migration_recycler.adapter = adapter + migration_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) } override fun onDestroyView(view: View) { @@ -91,7 +101,7 @@ class MigrationController : NucleusController(), } } - override fun onItemClick(position: Int): Boolean { + override fun onItemClick(view: View?, position: Int): Boolean { val item = adapter?.getItem(position) ?: return false if (item is MangaItem) { @@ -106,15 +116,12 @@ class MigrationController : NucleusController(), } override fun onSelectClick(position: Int) { - onItemClick(position) + onItemClick(view, position) } - fun migrateManga(prevManga: Manga, manga: Manga) { - presenter.migrateManga(prevManga, manga, replace = true) - } - - fun copyManga(prevManga: Manga, manga: Manga) { - presenter.migrateManga(prevManga, manga, replace = false) + override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? { + presenter.migrateManga(prevManga, manga, replace) + return null } class LoadingController : DialogController() { @@ -132,4 +139,8 @@ class MigrationController : NucleusController(), const val LOADING_DIALOG_TAG = "LoadingDialog" } -} \ No newline at end of file +} + +interface MigrationInterface { + fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt index 275d3a911a..8545922bed 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt @@ -2,12 +2,17 @@ package eu.kanade.tachiyomi.ui.migration import android.app.Dialog import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter import uy.kohesive.injekt.injectLazy @@ -17,6 +22,16 @@ class SearchController( ) : CatalogueSearchController(manga?.title) { private var newManga: Manga? = null + private var progress = 1 + var totalProgress = 0 + + override fun getTitle(): String? { + if (totalProgress > 1) { + return "($progress/$totalProgress) ${super.getTitle()}" + } + else + return super.getTitle() + } override fun createPresenter(): CatalogueSearchPresenter { return SearchPresenter(initialQuery, manga!!) @@ -34,22 +49,52 @@ class SearchController( newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + if (totalProgress > 1) { + val menuItem = menu.add(Menu.NONE, 1, Menu.NONE, R.string.action_skip_manga) + menuItem.icon = VectorDrawableCompat.create(resources!!, R.drawable + .baseline_skip_next_white_24, null) + menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + 1 -> { + newManga = manga + migrateManga() + } + } + return true + } + fun migrateManga() { - val target = targetController as? MigrationController ?: return + val target = targetController as? MigrationInterface ?: return val manga = manga ?: return val newManga = newManga ?: return - router.popController(this) - target.migrateManga(manga, newManga) + val nextManga = target.migrateManga(manga, newManga, true) + replaceWithNewSearchController(nextManga) } fun copyManga() { - val target = targetController as? MigrationController ?: return + val target = targetController as? MigrationInterface ?: return val manga = manga ?: return val newManga = newManga ?: return - router.popController(this) - target.copyManga(manga, newManga) + val nextManga = target.migrateManga(manga, newManga, false) + replaceWithNewSearchController(nextManga) + } + + private fun replaceWithNewSearchController(manga: Manga?) { + if (manga != null) { + //router.popCurrentController() + val searchController = SearchController(manga) + searchController.targetController = targetController + searchController.progress = progress + 1 + searchController.totalProgress = totalProgress + router.replaceTopController(searchController.withFadeTransaction()) + } else router.popController(this) } override fun onMangaClick(manga: Manga) { @@ -77,14 +122,13 @@ class SearchController( .content(R.string.migration_dialog_what_to_include) .items(MigrationFlags.titles.map { resources?.getString(it) }) .alwaysCallMultiChoiceCallback() - .itemsCallbackMultiChoice(preselected.toTypedArray(), { _, positions, _ -> + .itemsCallbackMultiChoice(preselected.toTypedArray()) { _, positions, _ -> // Save current settings for the next time val newValue = MigrationFlags.getFlagsFromPositions(positions) preferences.migrateFlags().set(newValue) true - }) - .positiveText(R.string.migrate) + }.positiveText(R.string.migrate) .negativeText(R.string.copy) .neutralText(android.R.string.cancel) .onPositive { _, _ -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SelectionHeader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SelectionHeader.kt index cb87fcb9ea..65f3381c8f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SelectionHeader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SelectionHeader.kt @@ -6,6 +6,8 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import kotlinx.android.synthetic.main.catalogue_main_controller_card.* +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible /** * Item that contains the selection header. @@ -22,19 +24,19 @@ class SelectionHeader : AbstractHeaderItem() { /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return SelectionHeader.Holder(view, adapter) } /** * Binds this item to the given view holder. */ - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, - position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, + position: Int, payloads: MutableList?) { // Intentionally empty } - class Holder(view: View, adapter: FlexibleAdapter<*>) : BaseFlexibleViewHolder(view, adapter) { + class Holder(view: View, adapter: FlexibleAdapter>) : BaseFlexibleViewHolder(view, adapter) { init { title.text = "Please select a source to migrate from" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt index e64aa0a8b4..f620ef8142 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt @@ -5,6 +5,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.Source +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible /** * Item that contains source information. @@ -25,15 +27,15 @@ data class SourceItem(val source: Source, val header: SelectionHeader? = null) : /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): SourceHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): SourceHolder { return SourceHolder(view, adapter as SourceAdapter) } /** * Binds this item to the given view holder. */ - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: SourceHolder, - position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: SourceHolder, + position: Int, payloads: MutableList?) { holder.bind(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/PageIndicatorTextView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/PageIndicatorTextView.kt index 7fa36a2a3e..c467ce0db0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/PageIndicatorTextView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/PageIndicatorTextView.kt @@ -5,7 +5,7 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint -import android.support.v7.widget.AppCompatTextView +import androidx.appcompat.widget.AppCompatTextView import android.text.Spannable import android.text.SpannableString import android.text.style.ScaleXSpan @@ -24,12 +24,12 @@ class PageIndicatorTextView( private val strokeColor = Color.rgb(45, 45, 45) override fun onDraw(canvas: Canvas) { - textColorField.set(this, strokeColor) + setTextColor(strokeColor) paint.strokeWidth = 4f paint.style = Paint.Style.STROKE super.onDraw(canvas) - textColorField.set(this, fillColor) + setTextColor(fillColor) paint.strokeWidth = 0f paint.style = Paint.Style.FILL super.onDraw(canvas) @@ -50,12 +50,4 @@ class PageIndicatorTextView( super.setText(finalText, TextView.BufferType.SPANNABLE) } - - private companion object { - // We need to use reflection to set the text color instead of using [setTextColor], - // otherwise the view is invalidated inside [onDraw] and there's an infinite loop - val textColorField = TextView::class.java.getDeclaredField("mCurTextColor").apply { - isAccessible = true - }!! - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index ee324e8966..6b644f3895 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.reader import android.annotation.SuppressLint import android.app.ProgressDialog +import android.content.ClipData import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo @@ -10,14 +11,19 @@ import android.graphics.Bitmap import android.graphics.Color import android.os.Build import android.os.Bundle +import com.google.android.material.bottomsheet.BottomSheetDialog import android.view.* import android.view.animation.Animation import android.view.animation.AnimationUtils +import android.widget.LinearLayout import android.widget.SeekBar import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.notification.NotificationReceiver +import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity @@ -36,6 +42,10 @@ import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.widget.SimpleAnimationListener import eu.kanade.tachiyomi.widget.SimpleSeekBarListener import kotlinx.android.synthetic.main.reader_activity.* +import kotlinx.android.synthetic.main.reader_activity.toolbar +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import me.zhanghai.android.systemuihelper.SystemUiHelper import nucleus.factory.RequiresPresenter import rx.Observable @@ -52,7 +62,8 @@ import java.util.concurrent.TimeUnit * viewers, to which calls from the presenter or UI events are delegated. */ @RequiresPresenter(ReaderPresenter::class) -class ReaderActivity : BaseRxActivity() { +class ReaderActivity : BaseRxActivity(), + SystemUiHelper.OnVisibilityChangeListener { /** * Preferences helper. @@ -76,6 +87,13 @@ class ReaderActivity : BaseRxActivity() { var menuVisible = false private set + /** + * Whether the menu should stay visible. + */ + var menuStickyVisible = false + private set + + private var coroutine: Job? = null /** * System UI helper to hide status & navigation bar on all different API levels. */ @@ -86,6 +104,11 @@ class ReaderActivity : BaseRxActivity() { */ private var config: ReaderConfig? = null + /** + * Current Bottom Sheet on display, used to dismiss + */ + private var bottomSheet: BottomSheetDialog? = null + /** * Progress dialog used when switching chapters from the menu buttons. */ @@ -99,10 +122,13 @@ class ReaderActivity : BaseRxActivity() { const val VERTICAL = 3 const val WEBTOON = 4 - fun newIntent(context: Context, manga: Manga, chapter: Chapter): Intent { + fun newIntent(context: Context, manga: Manga, chapter: Chapter): + Intent { val intent = Intent(context, ReaderActivity::class.java) intent.putExtra("manga", manga.id) intent.putExtra("chapter", chapter.id) + intent.putExtra("chapterUrl", chapter.url) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) return intent } } @@ -112,22 +138,24 @@ class ReaderActivity : BaseRxActivity() { */ override fun onCreate(savedState: Bundle?) { setTheme(when (preferences.readerTheme().getOrDefault()) { - 0 -> R.style.Theme_Reader_Light - else -> R.style.Theme_Reader + 0 -> R.style.Theme_Base_Reader_Light + 1 -> R.style.Theme_Base_Reader_Dark + else -> R.style.Theme_Base_Reader }) super.onCreate(savedState) setContentView(R.layout.reader_activity) if (presenter.needsInit()) { - val manga = intent.extras.getLong("manga", -1) - val chapter = intent.extras.getLong("chapter", -1) - - if (manga == -1L || chapter == -1L) { + val manga = intent.extras!!.getLong("manga", -1) + val chapter = intent.extras!!.getLong("chapter", -1) + val chapterUrl = intent.extras!!.getString("chapterUrl", "") + if (manga == -1L || chapterUrl == "" && chapter == -1L) { finish() return } - - presenter.init(manga, chapter) + NotificationReceiver.dismissNotification(this, manga.hashCode(), Notifications.ID_NEW_CHAPTERS) + if (chapter > -1) presenter.init(manga, chapter) + else presenter.init(manga, chapterUrl) } if (savedState != null) { @@ -147,6 +175,8 @@ class ReaderActivity : BaseRxActivity() { viewer = null config?.destroy() config = null + bottomSheet?.dismiss() + bottomSheet = null progressDialog?.dismiss() progressDialog = null } @@ -170,7 +200,10 @@ class ReaderActivity : BaseRxActivity() { override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { - setMenuVisibility(menuVisible, animate = false) + if (menuStickyVisible) + setMenuVisibility(false) + else + setMenuVisibility(menuVisible, animate = false) } } @@ -187,11 +220,13 @@ class ReaderActivity : BaseRxActivity() { * entries. */ override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_settings -> ReaderSettingsSheet(this).show() - R.id.action_custom_filter -> ReaderColorFilterSheet(this).show() + coroutine?.cancel() + bottomSheet = when (item.itemId) { + R.id.action_settings -> ReaderSettingsSheet(this) + R.id.action_custom_filter -> ReaderColorFilterSheet(this) else -> return super.onOptionsItemSelected(item) } + bottomSheet?.show() return true } @@ -259,6 +294,8 @@ class ReaderActivity : BaseRxActivity() { // Set initial visibility setMenuVisibility(menuVisible) + if (!menuVisible) + reader_menu_bottom.visibility = View.GONE } /** @@ -267,23 +304,24 @@ class ReaderActivity : BaseRxActivity() { */ private fun setMenuVisibility(visible: Boolean, animate: Boolean = true) { menuVisible = visible + if (visible) coroutine?.cancel() if (visible) { systemUi?.show() reader_menu.visibility = View.VISIBLE - + reader_menu_bottom.visibility = View.VISIBLE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.navigationBarColor = getResourceColor(R.attr.colorPrimaryDark) + } if (animate) { - val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) - toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { - override fun onAnimationStart(animation: Animation) { - // Fix status bar being translucent the first time it's opened. - if (Build.VERSION.SDK_INT >= 21) { - window.addFlags( - WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + if (!menuStickyVisible) { + val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) + toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { + override fun onAnimationStart(animation: Animation) { + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) } - } - }) - toolbar.startAnimation(toolbarAnimation) - + }) + toolbar.startAnimation(toolbarAnimation) + } val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom) reader_menu_bottom.startAnimation(bottomAnimation) } @@ -298,11 +336,15 @@ class ReaderActivity : BaseRxActivity() { } }) toolbar.startAnimation(toolbarAnimation) - - val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom) - reader_menu_bottom.startAnimation(bottomAnimation) + if (reader_menu_bottom.visibility != View.GONE) { + val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom) + reader_menu_bottom.startAnimation(bottomAnimation) + } } + else + reader_menu.visibility = View.GONE } + menuStickyVisible = false } /** @@ -473,6 +515,7 @@ class ReaderActivity : BaseRxActivity() { val intent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, stream) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + clipData = ClipData.newRawUri(null, stream) type = "image/*" } startActivity(Intent.createChooser(intent, getString(R.string.action_share))) @@ -521,6 +564,40 @@ class ReaderActivity : BaseRxActivity() { }) } + override fun onVisibilityChange(visible: Boolean) { + if (visible && !menuStickyVisible && !menuVisible) { + menuStickyVisible = visible + if (visible) { + coroutine = launchUI { + delay(2000) + if (systemUi?.isShowing == true) { + menuStickyVisible = false + setMenuVisibility(false) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.navigationBarColor = getColor(android.R.color.transparent) + } + reader_menu_bottom.visibility = View.GONE + reader_menu.visibility = View.VISIBLE + val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) + toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { + override fun onAnimationStart(animation: Animation) { + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + } + }) + toolbar.startAnimation(toolbarAnimation) + } + } + else { + if (menuStickyVisible && !menuVisible) { + setMenuVisibility(false, animate = false) + } + coroutine?.cancel() + } + } + + /** * Class that handles the user preferences of the reader. */ @@ -637,15 +714,10 @@ class ReaderActivity : BaseRxActivity() { */ private fun setFullscreen(enabled: Boolean) { systemUi = if (enabled) { - val level = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - SystemUiHelper.LEVEL_IMMERSIVE - } else { - SystemUiHelper.LEVEL_HIDE_STATUS_BAR - } - val flags = SystemUiHelper.FLAG_IMMERSIVE_STICKY or - SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES + val level = SystemUiHelper.LEVEL_IMMERSIVE + val flags = SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES - SystemUiHelper(this@ReaderActivity, level, flags) + SystemUiHelper(this@ReaderActivity, level, flags, this@ReaderActivity) } else { null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt index a0579cdccf..cffba4f8bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt @@ -1,9 +1,9 @@ package eu.kanade.tachiyomi.ui.reader import android.graphics.Color -import android.support.annotation.ColorInt -import android.support.design.widget.BottomSheetBehavior -import android.support.design.widget.BottomSheetDialog +import androidx.annotation.ColorInt +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog import android.view.View import android.view.ViewGroup import android.widget.SeekBar diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt index be465e8738..1d70ed08a7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.reader import android.os.Bundle -import android.support.design.widget.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialog import android.view.ViewGroup import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.R diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index b2aafa0a23..77877d52a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -185,6 +185,15 @@ class ReaderPresenter( }, ReaderActivity::setInitialChapterError) } + fun init(mangaId: Long, chapterUrl: String) { + if (!needsInit()) return + val context = Injekt.get() + val db = DatabaseHelper(context) + val chapterId = db.getChapter(chapterUrl, mangaId).executeAsBlocking()?.id + if (chapterId != null) + init(mangaId, chapterId) + } + /** * Initializes this presenter with the given [manga] and [initialChapterId]. This method will * set the chapter loader, view subscriptions and trigger an initial load. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSeekBar.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSeekBar.kt index c429804eb8..48aeb56d32 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSeekBar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSeekBar.kt @@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.reader import android.content.Context import android.graphics.Canvas -import android.support.v7.widget.AppCompatSeekBar +import android.graphics.Rect +import android.os.Build +import androidx.appcompat.widget.AppCompatSeekBar import android.util.AttributeSet import android.view.MotionEvent @@ -18,6 +20,8 @@ class ReaderSeekBar @JvmOverloads constructor( * Whether the seekbar should draw from right to left. */ var isRTL = false + private val boundingBox: Rect = Rect() + private val exclusions = listOf(boundingBox) /** * Draws the seekbar, translating the canvas if using a right to left reader. @@ -42,4 +46,13 @@ class ReaderSeekBar @JvmOverloads constructor( return super.onTouchEvent(event) } + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + if (Build.VERSION.SDK_INT >= 29) { + if (changed) { + boundingBox.set(left, top, right, bottom) + systemGestureExclusionRects = exclusions + } + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt index b798f3b49b..09ab52f234 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.reader import android.os.Bundle -import android.support.design.widget.BottomSheetDialog -import android.support.v4.widget.NestedScrollView +import com.google.android.material.bottomsheet.BottomSheetDialog +import androidx.core.widget.NestedScrollView import android.widget.CompoundButton import android.widget.Spinner import com.f2prateek.rx.preferences.Preference @@ -58,7 +58,7 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BottomSheetDia viewer.setSelection(activity.presenter.manga?.viewer ?: 0, false) rotation_mode.bindToPreference(preferences.rotation(), 1) - background_color.bindToPreference(preferences.readerTheme()) + background_color.bindToPreference(preferences.readerTheme(), 0, true) show_page_number.bindToPreference(preferences.showPageNumber()) fullscreen.bindToPreference(preferences.fullscreen()) keepscreen.bindToPreference(preferences.keepScreenOn()) @@ -95,9 +95,13 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BottomSheetDia /** * Binds a spinner to an int preference with an optional offset for the value. */ - private fun Spinner.bindToPreference(pref: Preference, offset: Int = 0) { + private fun Spinner.bindToPreference(pref: Preference, offset: Int = 0, shouldDismiss: + Boolean + = false) { onItemSelectedListener = IgnoreFirstSpinnerListener { position -> pref.set(position + offset) + if (shouldDismiss) + dismiss() } setSelection(pref.getOrDefault() - offset, false) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt index 87ed7ab16d..dfa24d7de8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt @@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.ui.reader import android.content.Context import android.graphics.Bitmap -import android.support.v4.app.NotificationCompat +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.glide.GlideApp @@ -57,6 +58,7 @@ class SaveImageNotifier(private val context: Context) { setStyle(NotificationCompat.BigPictureStyle().bigPicture(image)) setLargeIcon(image) setAutoCancel(true) + color = ContextCompat.getColor(context, R.color.colorAccentLight) // Clear old actions if they exist if (!mActions.isEmpty()) mActions.clear() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index af7d969305..c64947a321 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.ui.reader.loader import android.app.Application +import android.net.Uri import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.source.Source @@ -9,6 +10,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import rx.Observable import uy.kohesive.injekt.injectLazy +import java.io.InputStream /** * Loader used to load a chapter from the downloaded chapters. @@ -33,7 +35,7 @@ class DownloadPageLoader( .map { pages -> pages.map { page -> ReaderPage(page.index, page.url, page.imageUrl, { - context.contentResolver.openInputStream(page.uri) + context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!! }).apply { status = Page.READY } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index 223e9811e3..395e0f1f73 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -1,11 +1,17 @@ package eu.kanade.tachiyomi.ui.reader.loader +import android.graphics.BitmapFactory import eu.kanade.tachiyomi.data.cache.ChapterCache +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig +import eu.kanade.tachiyomi.util.ImageUtil import eu.kanade.tachiyomi.util.plusAssign +import kotlinx.coroutines.async import rx.Completable import rx.Observable import rx.schedulers.Schedulers @@ -15,6 +21,7 @@ import rx.subscriptions.CompositeSubscription import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.util.concurrent.PriorityBlockingQueue import java.util.concurrent.atomic.AtomicInteger @@ -37,6 +44,11 @@ class HttpPageLoader( */ private val subscriptions = CompositeSubscription() + /** + * Preferences helper. + */ + private val preferences by injectLazy() + init { subscriptions += Observable.defer { Observable.just(queue.take().page) } .filter { it.status == Page.QUEUE } @@ -205,6 +217,12 @@ class HttpPageLoader( } } .doOnNext { + if (preferences.readerTheme().get() == 2) { + val stream = chapterCache.getImageFile(imageUrl).inputStream() + val image = BitmapFactory.decodeStream(stream) + page.bg = ImageUtil.autoSetBackground(image) + stream.close() + } page.stream = { chapterCache.getImageFile(imageUrl).inputStream() } page.status = Page.READY } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt index 38cac38173..b0b872b6d7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.loader -import android.support.annotation.CallSuper +import androidx.annotation.CallSuper import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import rx.Observable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt index 34b415253d..4b9b242ebc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.model +import android.graphics.drawable.Drawable import eu.kanade.tachiyomi.source.model.Page import java.io.InputStream @@ -7,7 +8,8 @@ class ReaderPage( index: Int, url: String = "", imageUrl: String? = null, - var stream: (() -> InputStream)? = null + var stream: (() -> InputStream)? = null, + var bg: Drawable? = null ) : Page(index, url, imageUrl, null) { lateinit var chapter: ReaderChapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.kt index 0a0114dcc5..a256a5c6e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager import android.content.Context -import android.support.v4.view.DirectionalViewPager import android.view.HapticFeedbackConstants import android.view.KeyEvent import android.view.MotionEvent +import androidx.viewpager.widget.DirectionalViewPager import eu.kanade.tachiyomi.ui.reader.viewer.GestureDetectorWithLongTap /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerButton.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerButton.kt index 030ed80d02..f184031a40 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerButton.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerButton.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager import android.annotation.SuppressLint import android.content.Context -import android.support.v7.widget.AppCompatButton +import androidx.appcompat.widget.AppCompatButton import android.view.MotionEvent /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt index c07c7b65fb..b2ba6e9cd9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt @@ -43,6 +43,9 @@ class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelpe var doubleTapAnimDuration = 500 private set + var readerTheme = 0 + private set + init { preferences.readWithTapping() .register({ tappingEnabled = it }) @@ -70,6 +73,9 @@ class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelpe preferences.readWithVolumeKeysInverted() .register({ volumeKeysInverted = it }) + + preferences.readerTheme() + .register({ readerTheme = it }) } fun unsubscribe() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 8ec0ead814..160cfbb85c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager import android.annotation.SuppressLint import android.content.Intent +import android.graphics.BitmapFactory import android.graphics.PointF import android.graphics.drawable.Drawable import android.net.Uri @@ -35,8 +36,11 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType import eu.kanade.tachiyomi.util.ImageUtil import eu.kanade.tachiyomi.util.dpToPx import eu.kanade.tachiyomi.util.gone +import eu.kanade.tachiyomi.util.launchUI import eu.kanade.tachiyomi.util.visible import eu.kanade.tachiyomi.widget.ViewPagerAdapter +import kotlinx.coroutines.Dispatchers.Default +import kotlinx.coroutines.withContext import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -244,9 +248,33 @@ class PagerPageHolder( .observeOn(AndroidSchedulers.mainThread()) .doOnNext { isAnimated -> if (!isAnimated) { - initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!)) + if (viewer.config.readerTheme == 2) { + val imageView = initSubsamplingImageView() + if (page.bg != null) { + imageView.setImage(ImageSource.inputStream(openStream!!)) + imageView.background = page.bg + } + // if the user switches to automatic when pages are already cached, the bg needs to be loaded + else { + val bytesArray = openStream!!.readBytes() + val bytesStream = bytesArray.inputStream() + imageView.setImage(ImageSource.inputStream(bytesStream)) + bytesStream.close() + + launchUI { + imageView.background = setBG(bytesArray) + page.bg = imageView.background + } + } + } + else { + initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!)) + } } else { - initImageView().setImage(openStream!!) + val imageView = initImageView() + imageView.setImage(openStream!!) + if (viewer.config.readerTheme == 2 && page.bg != null) + imageView.background = page.bg } } // Keep the Rx stream alive to close the input stream only when unsubscribed @@ -255,6 +283,14 @@ class PagerPageHolder( .subscribe({}, {}) } + private suspend fun setBG(bytesArray: ByteArray): Drawable { + return withContext(Default) { + ImageUtil.autoSetBackground(BitmapFactory.decodeByteArray( + bytesArray, 0, bytesArray.size + )) + } + } + /** * Called when the page has an error. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt index ce33d09f07..4a44965cc6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt @@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager import android.annotation.SuppressLint import android.graphics.Typeface -import android.support.v7.widget.AppCompatTextView +import android.os.Build +import androidx.appcompat.widget.AppCompatTextView import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.StyleSpan @@ -18,6 +19,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.util.dpToPx +import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.widget.ViewPagerAdapter import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -46,6 +48,8 @@ class PagerTransitionHolder( * Text view used to display the text of the current and next/prev chapters. */ private var textView = TextView(context).apply { + //if (Build.VERSION.SDK_INT >= 23) + //setTextColor(context.getResourceColor(R.attr.)) wrapContent() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt index 8f37257a5a..1ce1b2f2c6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager -import android.support.v4.view.ViewPager +import androidx.viewpager.widget.ViewPager import android.view.InputDevice import android.view.KeyEvent import android.view.MotionEvent @@ -68,7 +68,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { pager.offscreenPageLimit = 1 pager.id = R.id.reader_pager pager.adapter = adapter - pager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { + pager.addOnPageChangeListener(object : androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { val page = adapter.items.getOrNull(position) if (page != null && currentPage != page) { @@ -81,7 +81,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { } override fun onPageScrollStateChanged(state: Int) { - isIdle = state == ViewPager.SCROLL_STATE_IDLE + isIdle = state == androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE } }) pager.tapListener = { event -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt index d03e822b0c..d0dd07874e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager -import android.support.v4.view.PagerAdapter +import androidx.viewpager.widget.PagerAdapter import android.view.View import android.view.ViewGroup import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition @@ -95,7 +95,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { Timber.d("Position for ${view.item} not found") } } - return PagerAdapter.POSITION_NONE + return androidx.viewpager.widget.PagerAdapter.POSITION_NONE } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt index e215646c1d..ddbf3c2ec2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon -import android.support.v7.util.DiffUtil -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView import android.view.ViewGroup import android.widget.FrameLayout import android.widget.LinearLayout @@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters /** * RecyclerView Adapter used by this [viewer] to where [ViewerChapters] updates are posted. */ -class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter() { +class WebtoonAdapter(val viewer: WebtoonViewer) : androidx.recyclerview.widget.RecyclerView.Adapter() { /** * List of currently set items. @@ -82,7 +82,7 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter { val view = FrameLayout(parent.context) @@ -99,7 +99,7 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter holder.bind(item as ReaderPage) @@ -110,7 +110,7 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter holder.recycle() is WebtoonTransitionHolder -> holder.recycle() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt index c9cd0712c1..34f9898a66 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt @@ -1,8 +1,8 @@ @file:Suppress("PackageDirectoryMismatch") -package android.support.v7.widget +package androidx.recyclerview.widget -import android.support.v7.widget.RecyclerView.NO_POSITION +import androidx.recyclerview.widget.RecyclerView.NO_POSITION import eu.kanade.tachiyomi.ui.reader.ReaderActivity /** @@ -27,7 +27,7 @@ class WebtoonLayoutManager(activity: ReaderActivity) : LinearLayoutManager(activ /** * Returns the custom extra layout space. */ - override fun getExtraLayoutSpace(state: RecyclerView.State): Int { + override fun getExtraLayoutSpace(state: androidx.recyclerview.widget.RecyclerView.State): Int { return extraLayoutSpace } @@ -37,7 +37,7 @@ class WebtoonLayoutManager(activity: ReaderActivity) : LinearLayoutManager(activ fun findLastEndVisibleItemPosition(): Int { ensureLayoutState() @ViewBoundsCheck.ViewBounds val preferredBoundsFlag = - (ViewBoundsCheck.FLAG_CVE_LT_PVE or ViewBoundsCheck.FLAG_CVE_EQ_PVE) + (androidx.recyclerview.widget.ViewBoundsCheck.FLAG_CVE_LT_PVE or androidx.recyclerview.widget.ViewBoundsCheck.FLAG_CVE_EQ_PVE) val fromIndex = childCount - 1 val toIndex = -1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index 754dbb7e1d..1005c85a37 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -4,8 +4,8 @@ import android.annotation.SuppressLint import android.content.Intent import android.graphics.drawable.Drawable import android.net.Uri -import android.support.v7.widget.AppCompatButton -import android.support.v7.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatButton +import androidx.appcompat.widget.AppCompatImageView import android.view.Gravity import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt index eae782c931..5d68c178e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt @@ -6,8 +6,8 @@ import android.animation.ValueAnimator import android.annotation.TargetApi import android.content.Context import android.os.Build -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import android.util.AttributeSet import android.view.HapticFeedbackConstants import android.view.MotionEvent @@ -22,7 +22,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 -) : RecyclerView(context, attrs, defStyle) { +) : androidx.recyclerview.widget.RecyclerView(context, attrs, defStyle) { private var isZooming = false private var atLastPosition = false @@ -54,7 +54,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor( super.onScrolled(dx, dy) val layoutManager = layoutManager lastVisibleItemPosition = - (layoutManager as LinearLayoutManager).findLastVisibleItemPosition() + (layoutManager as androidx.recyclerview.widget.LinearLayoutManager).findLastVisibleItemPosition() firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() } @@ -62,8 +62,8 @@ open class WebtoonRecyclerView @JvmOverloads constructor( override fun onScrollStateChanged(state: Int) { super.onScrollStateChanged(state) val layoutManager = layoutManager - val visibleItemCount = layoutManager.childCount - val totalItemCount = layoutManager.itemCount + val visibleItemCount = layoutManager?.childCount ?: 0 + val totalItemCount = layoutManager?.itemCount ?: 0 atLastPosition = visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1 atFirstPosition = firstVisibleItemPosition == 0 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt index 4a1812c2c9..f5948b8f6c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon import android.graphics.Typeface -import android.support.v7.widget.AppCompatButton -import android.support.v7.widget.AppCompatTextView +import androidx.appcompat.widget.AppCompatButton +import androidx.appcompat.widget.AppCompatTextView import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.StyleSpan diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt index 6adee83c2f..b7d6067b12 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon -import android.support.v7.widget.RecyclerView -import android.support.v7.widget.WebtoonLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.WebtoonLayoutManager import android.view.KeyEvent import android.view.MotionEvent import android.view.View @@ -66,8 +66,8 @@ class WebtoonViewer(val activity: ReaderActivity) : BaseViewer { recycler.itemAnimator = null recycler.layoutManager = layoutManager recycler.adapter = adapter - recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) { + recycler.addOnScrollListener(object : androidx.recyclerview.widget.RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: androidx.recyclerview.widget.RecyclerView, dx: Int, dy: Int) { val position = layoutManager.findLastEndVisibleItemPosition() val item = adapter.items.getOrNull(position) if (item != null && currentPage != item) { @@ -98,11 +98,13 @@ class WebtoonViewer(val activity: ReaderActivity) : BaseViewer { recycler.longTapListener = f@ { event -> if (activity.menuVisible || config.longTapEnabled) { val child = recycler.findChildViewUnder(event.x, event.y) - val position = recycler.getChildAdapterPosition(child) - val item = adapter.items.getOrNull(position) - if (item is ReaderPage) { - activity.onPageLongTap(item) - return@f true + if(child != null) { + val position = recycler.getChildAdapterPosition(child) + val item = adapter.items.getOrNull(position) + if (item is ReaderPage) { + activity.onPageLongTap(item) + return@f true + } } } false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/DateItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/DateItem.kt index a766613ad0..01965986bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/DateItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/DateItem.kt @@ -8,6 +8,8 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R import java.util.* +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible class DateItem(val date: Date) : AbstractHeaderItem() { @@ -15,11 +17,11 @@ class DateItem(val date: Date) : AbstractHeaderItem() { return R.layout.recent_chapters_section_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): Holder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: MutableList?) { holder.bind(this) } @@ -35,7 +37,7 @@ class DateItem(val date: Date) : AbstractHeaderItem() { return date.hashCode() } - class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter, true) { + class Holder(view: View, adapter: FlexibleAdapter>) : FlexibleViewHolder(view, adapter, true) { private val now = Date().time diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterItem.kt index eb983c6089..fe4e685234 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterItem.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible class RecentChapterItem(val chapter: Chapter, val manga: Manga, header: DateItem) : AbstractSectionableItem(header) { @@ -26,14 +28,14 @@ class RecentChapterItem(val chapter: Chapter, val manga: Manga, header: DateItem return R.layout.recent_chapters_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): RecentChapterHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): RecentChapterHolder { return RecentChapterHolder(view , adapter as RecentChaptersAdapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: RecentChapterHolder, position: Int, - payloads: List?) { + payloads: MutableList?) { holder.bind(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt index 1b0fef7dbd..64a1393baa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt @@ -1,9 +1,9 @@ package eu.kanade.tachiyomi.ui.recent_updates -import android.support.v7.app.AppCompatActivity -import android.support.v7.view.ActionMode -import android.support.v7.widget.DividerItemDecoration -import android.support.v7.widget.LinearLayoutManager +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager import android.view.* import com.jakewharton.rxbinding.support.v4.widget.refreshes import com.jakewharton.rxbinding.support.v7.widget.scrollStateChanges @@ -13,12 +13,16 @@ import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.library.LibraryUpdateService +import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener +import eu.kanade.tachiyomi.util.notificationManager +import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.toast import kotlinx.android.synthetic.main.recent_chapters_controller.* import timber.log.Timber @@ -66,7 +70,7 @@ class RecentChaptersController : NucleusController(), */ override fun onViewCreated(view: View) { super.onViewCreated(view) - + view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS) // Init RecyclerView and adapter val layoutManager = LinearLayoutManager(view.context) recycler.layoutManager = layoutManager @@ -85,11 +89,12 @@ class RecentChaptersController : NucleusController(), swipe_refresh.refreshes().subscribeUntilDestroy { if (!LibraryUpdateService.isRunning(view.context)) { LibraryUpdateService.start(view.context) - view.context.toast(R.string.action_update_library) + view.snack(R.string.updating_library) } - // It can be a very long operation, so we disable swipe refresh and show a toast. + // It can be a very long operation, so we disable swipe refresh and show a snackbar. swipe_refresh.isRefreshing = false } + recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) } override fun onDestroyView(view: View) { @@ -111,7 +116,7 @@ class RecentChaptersController : NucleusController(), * Called when item in list is clicked * @param position position of clicked item */ - override fun onItemClick(position: Int): Boolean { + override fun onItemClick(view: View?, position: Int): Boolean { val adapter = adapter ?: return false // Get item from position diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt index 766ce466db..9d4f2970e8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.ui.recently_read import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.source.SourceManager import uy.kohesive.injekt.injectLazy import java.text.DateFormat @@ -16,7 +17,7 @@ import java.text.DecimalFormatSymbols * @constructor creates an instance of the adapter. */ class RecentlyReadAdapter(controller: RecentlyReadController) -: FlexibleAdapter(null, controller, true) { +: FlexibleAdapter>(null, controller, true) { val sourceManager by injectLazy() @@ -43,6 +44,6 @@ class RecentlyReadAdapter(controller: RecentlyReadController) } interface OnCoverClickListener { - fun onCoverClick(position: Int) + fun onCoverClick(position: Int, lastTouchY: Float) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt index c094c39fc0..5c15a968de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.recently_read -import android.support.v7.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.catalogue.browse.ProgressItem import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.toast import kotlinx.android.synthetic.main.recently_read_controller.* @@ -22,6 +24,7 @@ import kotlinx.android.synthetic.main.recently_read_controller.* */ class RecentlyReadController : NucleusController(), FlexibleAdapter.OnUpdateListener, + FlexibleAdapter.EndlessScrollListener, RecentlyReadAdapter.OnRemoveClickListener, RecentlyReadAdapter.OnResumeClickListener, RecentlyReadAdapter.OnCoverClickListener, @@ -33,6 +36,11 @@ class RecentlyReadController : NucleusController(), var adapter: RecentlyReadAdapter? = null private set + /** + * Endless loading item. + */ + private var progressItem: ProgressItem? = null + override fun getTitle(): String? { return resources?.getString(R.string.label_recent_manga) } @@ -58,6 +66,7 @@ class RecentlyReadController : NucleusController(), adapter = RecentlyReadAdapter(this@RecentlyReadController) recycler.setHasFixedSize(true) recycler.adapter = adapter + recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) } override fun onDestroyView(view: View) { @@ -71,7 +80,14 @@ class RecentlyReadController : NucleusController(), * @param mangaHistory list of manga history */ fun onNextManga(mangaHistory: List) { - adapter?.updateDataSet(mangaHistory) + if (adapter?.itemCount ?: 0 == 0) + resetProgressItem() + adapter?.onLoadMoreComplete(mangaHistory) + } + + fun onAddPageError(error: Throwable) { + adapter?.onLoadMoreComplete(null) + adapter?.endlessTargetCount = 1 } override fun onUpdateEmptyView(size: Int) { @@ -82,9 +98,26 @@ class RecentlyReadController : NucleusController(), } } + /** + * Sets a new progress item and reenables the scroll listener. + */ + private fun resetProgressItem() { + progressItem = ProgressItem() + adapter?.endlessTargetCount = 0 + adapter?.setEndlessScrollListener(this, progressItem!!) + } + + override fun onLoadMore(lastPosition: Int, currentPage: Int) { + val adapter = adapter ?: return + presenter.requestNext(adapter.itemCount) + } + + override fun noMoreLoad(newItemsSize: Int) { } + + override fun onResumeClick(position: Int) { val activity = activity ?: return - val (manga, chapter, _) = adapter?.getItem(position)?.mch ?: return + val (manga, chapter, _) = (adapter?.getItem(position) as? RecentlyReadItem)?.mch ?: return val nextChapter = presenter.getNextChapter(chapter, manga) if (nextChapter != null) { @@ -96,13 +129,13 @@ class RecentlyReadController : NucleusController(), } override fun onRemoveClick(position: Int) { - val (manga, _, history) = adapter?.getItem(position)?.mch ?: return + val (manga, _, history) = (adapter?.getItem(position) as? RecentlyReadItem)?.mch ?: return RemoveHistoryDialog(this, manga, history).showDialog(router) } - override fun onCoverClick(position: Int) { - val manga = adapter?.getItem(position)?.mch?.manga ?: return - router.pushController(MangaController(manga).withFadeTransaction()) + override fun onCoverClick(position: Int, lastTouchY: Float) { + val manga = (adapter?.getItem(position) as? RecentlyReadItem)?.mch?.manga ?: return + router.pushController(MangaController(manga, lastTouchY).withFadeTransaction()) } override fun removeHistory(manga: Manga, history: History, all: Boolean) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt index 64eab1a9b1..0514b66155 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.ui.recently_read +import android.os.Build +import android.view.MotionEvent import android.view.View import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.kanade.tachiyomi.R @@ -8,6 +10,7 @@ import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import kotlinx.android.synthetic.main.recently_read_item.* import java.util.Date +import kotlin.math.max /** * Holder that contains recent manga item @@ -23,6 +26,7 @@ class RecentlyReadHolder( val adapter: RecentlyReadAdapter ) : BaseFlexibleViewHolder(view, adapter) { + private var lastTouchUpY = 0f init { remove.setOnClickListener { adapter.removeClickListener.onRemoveClick(adapterPosition) @@ -33,7 +37,17 @@ class RecentlyReadHolder( } cover.setOnClickListener { - adapter.coverClickListener.onCoverClick(adapterPosition) + adapter.coverClickListener.onCoverClick(adapterPosition, lastTouchUpY) + } + cover.setOnTouchListener { v, event -> + when (event?.action) { + MotionEvent.ACTION_UP -> { + val topH = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + v.rootWindowInsets.systemWindowInsetTop else 38 + lastTouchUpY = max(topH + 175f, event.rawY - topH - 154f) + } + } + false } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadItem.kt index 8faf640c6a..e7e8fb96a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadItem.kt @@ -5,6 +5,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.items.IFlexible class RecentlyReadItem(val mch: MangaChapterHistory) : AbstractFlexibleItem() { @@ -12,14 +14,14 @@ class RecentlyReadItem(val mch: MangaChapterHistory) : AbstractFlexibleItem): RecentlyReadHolder { + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): RecentlyReadHolder { return RecentlyReadHolder(view, adapter as RecentlyReadAdapter) } - override fun bindViewHolder(adapter: FlexibleAdapter<*>, + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: RecentlyReadHolder, position: Int, - payloads: List?) { + payloads: MutableList?) { holder.bind(mch) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt index 8a5e0ed8f6..7c0fb90c9f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt @@ -7,7 +7,9 @@ import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import rx.Observable +import rx.Subscription import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy import java.util.Calendar import java.util.Comparator @@ -28,24 +30,34 @@ class RecentlyReadPresenter : BasePresenter() { override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) + //pageSubscription?.let { remove(it) } // Used to get a list of recently read manga getRecentMangaObservable() - .subscribeLatestCache(RecentlyReadController::onNextManga) + .subscribeLatestCache({ view, mangas -> + view.onNextManga(mangas) + }, RecentlyReadController::onAddPageError) + } + + fun requestNext(offset: Int) { + getRecentMangaObservable((offset)) + .subscribeLatestCache({ view, mangas -> + view.onNextManga(mangas) + }, RecentlyReadController::onAddPageError) } /** * Get recent manga observable * @return list of history */ - fun getRecentMangaObservable(): Observable> { + private fun getRecentMangaObservable(offset: Int = 0): Observable> { // Set date for recent manga val cal = Calendar.getInstance() cal.time = Date() - cal.add(Calendar.MONTH, -1) + cal.add(Calendar.YEAR, -1) - return db.getRecentManga(cal.time).asRxObservable() - .map { recents -> recents.map(::RecentlyReadItem) } - .observeOn(AndroidSchedulers.mainThread()) + return db.getRecentManga(cal.time, offset).asRxObservable() + .map { recents -> recents.map(::RecentlyReadItem) } + .observeOn(AndroidSchedulers.mainThread()) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt index 6b5da186ea..7aad614e34 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.setting import android.content.Intent import android.os.Bundle -import android.support.v7.app.AppCompatActivity +import androidx.appcompat.app.AppCompatActivity import android.view.Gravity.CENTER import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.widget.FrameLayout diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/BangumiLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/BangumiLoginActivity.kt index 5654b4efa4..45a781d65c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/BangumiLoginActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/BangumiLoginActivity.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.setting import android.content.Intent import android.os.Bundle -import android.support.v7.app.AppCompatActivity +import androidx.appcompat.app.AppCompatActivity import android.view.Gravity.CENTER import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.widget.FrameLayout diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt index 6fc05d1af7..33fad5651a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.setting -import android.support.graphics.drawable.VectorDrawableCompat -import android.support.v4.graphics.drawable.DrawableCompat -import android.support.v7.preference.* +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat +import androidx.core.graphics.drawable.DrawableCompat +import androidx.preference.* import eu.kanade.tachiyomi.widget.preference.IntListPreference @DslMarker @@ -42,7 +42,9 @@ inline fun PreferenceGroup.multiSelectListPreference(block: (@DSL MultiSelectLis } inline fun PreferenceScreen.preferenceCategory(block: (@DSL PreferenceCategory).() -> Unit): PreferenceCategory { - return addThenInit(PreferenceCategory(context), block) + return addThenInit(PreferenceCategory(context).apply { + isIconSpaceReserved = false + }, block) } inline fun PreferenceScreen.preferenceScreen(block: (@DSL PreferenceScreen).() -> Unit): PreferenceScreen { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt index bbd23da2cf..4e9ce3f6b5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt @@ -4,7 +4,7 @@ import android.app.Dialog import android.content.Intent import android.net.Uri import android.os.Bundle -import android.support.v7.preference.PreferenceScreen +import androidx.preference.PreferenceScreen import android.view.View import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.BuildConfig @@ -80,7 +80,7 @@ class SettingsAboutController : SettingsController() { } preference { title = "Github" - val url = "https://github.com/inorichi/tachiyomi" + val url = "https://github.com/Jays2Kings/tachiyomi" summary = url onClick { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) @@ -150,14 +150,14 @@ class SettingsAboutController : SettingsController() { override fun onCreateDialog(savedViewState: Bundle?): Dialog { return MaterialDialog.Builder(activity!!) .title(R.string.update_check_title) - .content(args.getString(BODY_KEY)) + .content(args.getString(BODY_KEY) ?: "") .positiveText(R.string.update_check_confirm) .negativeText(R.string.update_check_ignore) .onPositive { _, _ -> val appContext = applicationContext if (appContext != null) { // Start download - val url = args.getString(URL_KEY) + val url = args.getString(URL_KEY) ?: "" UpdaterService.downloadUpdate(appContext, url) } } @@ -174,7 +174,7 @@ class SettingsAboutController : SettingsController() { try { val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US) inputDf.timeZone = TimeZone.getTimeZone("UTC") - val date = inputDf.parse(BuildConfig.BUILD_TIME) + val date = inputDf.parse(BuildConfig.BUILD_TIME) ?: return BuildConfig.BUILD_TIME val outputDf = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index 3ad78247a1..346cc1a971 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.setting import android.app.Dialog import android.os.Bundle -import android.support.v7.preference.PreferenceScreen +import androidx.preference.PreferenceScreen import android.view.View import com.afollestad.materialdialogs.MaterialDialog import com.bluelinelabs.conductor.RouterTransaction diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index 597cc4eef2..3cad1d5f6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt @@ -7,7 +7,7 @@ import android.content.* import android.net.Uri import android.os.Build import android.os.Bundle -import android.support.v7.preference.PreferenceScreen +import androidx.preference.PreferenceScreen import android.view.View import com.afollestad.materialdialogs.MaterialDialog import com.hippo.unifile.UniFile @@ -158,7 +158,7 @@ class SettingsBackupController : SettingsController() { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - + if (uri != null) activity.contentResolver.takePersistableUriPermission(uri, flags) } @@ -168,7 +168,7 @@ class SettingsBackupController : SettingsController() { CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) { val activity = activity ?: return val uri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - val dir = data.data.path + val dir = data.data?.path val file = File(dir, Backup.getDefaultFilename()) Uri.fromFile(file) @@ -177,6 +177,7 @@ class SettingsBackupController : SettingsController() { val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + if (uri != null) activity.contentResolver.takePersistableUriPermission(uri, flags) val file = UniFile.fromUri(activity, uri) @@ -188,7 +189,8 @@ class SettingsBackupController : SettingsController() { } CODE_BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) { val uri = data.data - RestoreBackupDialog(uri).showDialog(router) + if (uri != null) + RestoreBackupDialog(uri).showDialog(router) } } } @@ -311,7 +313,7 @@ class SettingsBackupController : SettingsController() { val context = applicationContext if (context != null) { RestoringBackupDialog().showDialog(router, TAG_RESTORING_BACKUP_DIALOG) - BackupRestoreService.start(context, args.getParcelable(KEY_URI)) + BackupRestoreService.start(context, args.getParcelable(KEY_URI)!!) } } .build() @@ -385,7 +387,7 @@ class SettingsBackupController : SettingsController() { .negativeText(R.string.action_open_log) .onNegative { _, _ -> val context = applicationContext ?: return@onNegative - if (!path.isEmpty()) { + if (!path!!.isEmpty()) { val destFile = File(path, file) val uri = destFile.getUriCompat(context) val sendIntent = Intent(Intent.ACTION_VIEW).apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt index 21af01ca55..ca42221667 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt @@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.ui.setting import android.content.Context import android.os.Bundle -import android.support.v7.app.AppCompatActivity -import android.support.v7.preference.PreferenceController -import android.support.v7.preference.PreferenceScreen +import androidx.appcompat.app.AppCompatActivity +import androidx.preference.PreferenceController +import androidx.preference.PreferenceScreen import android.util.TypedValue import android.view.ContextThemeWrapper import android.view.LayoutInflater @@ -15,6 +15,9 @@ import com.bluelinelabs.conductor.ControllerChangeType import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.controller.BaseController +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener +import eu.kanade.tachiyomi.util.doOnApplyWindowInsets +import eu.kanade.tachiyomi.util.updatePaddingRelative import rx.Observable import rx.Subscription import rx.subscriptions.CompositeSubscription @@ -32,7 +35,9 @@ abstract class SettingsController : PreferenceController() { if (untilDestroySubscriptions.isUnsubscribed) { untilDestroySubscriptions = CompositeSubscription() } - return super.onCreateView(inflater, container, savedInstanceState) + val view = super.onCreateView(inflater, container, savedInstanceState) + listView.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) + return view } override fun onDestroyView(view: View) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt index 1b6119b13c..988e411490 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt @@ -8,8 +8,8 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment -import android.support.v4.content.ContextCompat -import android.support.v7.preference.PreferenceScreen +import androidx.core.content.ContextCompat +import androidx.preference.PreferenceScreen import com.afollestad.materialdialogs.MaterialDialog import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R @@ -118,8 +118,8 @@ class SettingsDownloadController : SettingsController() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { DOWNLOAD_DIR_PRE_L -> if (data != null && resultCode == Activity.RESULT_OK) { - val uri = Uri.fromFile(File(data.data.path)) - preferences.downloadsDirectory().set(uri.toString()) + val uri = Uri.fromFile(File(data.data?.path)) + preferences.downloadsDirectory().set(uri?.toString() ?: "") } DOWNLOAD_DIR_L -> if (data != null && resultCode == Activity.RESULT_OK) { val context = applicationContext ?: return @@ -128,7 +128,8 @@ class SettingsDownloadController : SettingsController() { Intent.FLAG_GRANT_WRITE_URI_PERMISSION @Suppress("NewApi") - context.contentResolver.takePersistableUriPermission(uri, flags) + if (uri != null) + context.contentResolver.takePersistableUriPermission(uri, flags) val file = UniFile.fromUri(context, uri) preferences.downloadsDirectory().set(file.uri.toString()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt index 1f3e498440..e93a95f4c6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt @@ -1,13 +1,16 @@ package eu.kanade.tachiyomi.ui.setting import android.app.Dialog +import android.os.Build import android.os.Bundle import android.os.Handler -import android.support.v7.preference.PreferenceScreen +import androidx.appcompat.app.AppCompatDelegate +import androidx.preference.PreferenceScreen import android.view.View import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault @@ -53,12 +56,13 @@ class SettingsGeneralController : SettingsController() { key = Keys.theme titleRes = R.string.pref_theme entriesRes = arrayOf(R.string.light_theme, R.string.dark_theme, - R.string.amoled_theme, R.string.darkblue_theme) - entryValues = arrayOf("1", "2", "3", "4") - defaultValue = "1" + R.string.amoled_theme, R.string.darkblue_theme, + R.string.system_theme, R.string.system_amoled_theme, R.string.system_darkblue_theme) + entryValues = arrayOf("1", "2", "3", "4", "5", "6", "7") + defaultValue = "5" summary = "%s" - onChange { + onChange { newValue -> activity?.recreate() true } @@ -179,15 +183,17 @@ class SettingsGeneralController : SettingsController() { key = Keys.defaultCategory titleRes = R.string.default_category - val selectedCategory = dbCategories.find { it.id == preferences.defaultCategory() } + val categories = listOf(Category.createDefault()) + dbCategories + + val selectedCategory = categories.find { it.id == preferences.defaultCategory() } entries = arrayOf(context.getString(R.string.default_category_summary)) + - dbCategories.map { it.name }.toTypedArray() - entryValues = arrayOf("-1") + dbCategories.map { it.id.toString() }.toTypedArray() + categories.map { it.name }.toTypedArray() + entryValues = arrayOf("-1") + categories.map { it.id.toString() }.toTypedArray() defaultValue = "-1" summary = selectedCategory?.name ?: context.getString(R.string.default_category_summary) onChange { newValue -> - summary = dbCategories.find { + summary = categories.find { it.id == (newValue as String).toInt() }?.name ?: context.getString(R.string.default_category_summary) true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt index 56cfa769db..b45755054d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.ui.setting -import android.support.v7.preference.PreferenceScreen +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.util.getResourceColor diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt index ae59d13f19..71d89c6ffd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.setting import android.os.Build -import android.support.v7.preference.PreferenceScreen +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys @@ -50,8 +50,8 @@ class SettingsReaderController : SettingsController() { intListPreference { key = Keys.readerTheme titleRes = R.string.pref_reader_theme - entriesRes = arrayOf(R.string.white_background, R.string.black_background) - entryValues = arrayOf("0", "1") + entriesRes = arrayOf(R.string.white_background, R.string.black_background, R.string.zoom_start_automatic) + entryValues = arrayOf("0", "1", "2") defaultValue = "0" summary = "%s" } @@ -87,7 +87,7 @@ class SettingsReaderController : SettingsController() { switchPreference { key = Keys.trueColor titleRes = R.string.pref_true_color - defaultValue = false + defaultValue = true } } preferenceCategory { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt index 6becc8d35e..98cfd1b494 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.setting import android.graphics.drawable.Drawable -import android.support.v7.preference.PreferenceGroup -import android.support.v7.preference.PreferenceScreen +import androidx.preference.PreferenceGroup +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.source.SourceManager diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt index 55bc47059e..e8a585285c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt @@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.setting import android.app.Activity import android.content.Intent -import android.support.customtabs.CustomTabsIntent -import android.support.v7.preference.PreferenceScreen +import androidx.browser.customtabs.CustomTabsIntent +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ShikomoriLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ShikomoriLoginActivity.kt index d369896edb..0b94c5ebae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ShikomoriLoginActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ShikomoriLoginActivity.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.setting import android.content.Intent import android.os.Bundle -import android.support.v7.app.AppCompatActivity +import androidx.appcompat.app.AppCompatActivity import android.view.Gravity.CENTER import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.widget.FrameLayout diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt index 2c54b236c5..57e178cf0e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt @@ -12,12 +12,12 @@ import android.content.res.Resources import android.net.ConnectivityManager import android.net.Uri import android.os.PowerManager -import android.support.annotation.AttrRes -import android.support.annotation.StringRes -import android.support.customtabs.CustomTabsIntent -import android.support.v4.app.NotificationCompat -import android.support.v4.content.ContextCompat -import android.support.v4.content.LocalBroadcastManager +import androidx.annotation.AttrRes +import androidx.annotation.StringRes +import androidx.browser.customtabs.CustomTabsIntent +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import androidx.localbroadcastmanager.content.LocalBroadcastManager import android.widget.Toast import com.nononsenseapps.filepicker.FilePickerActivity import eu.kanade.tachiyomi.R @@ -126,7 +126,7 @@ val Context.powerManager: PowerManager * @param intent intent that contains broadcast information */ fun Context.sendLocalBroadcast(intent: Intent) { - LocalBroadcastManager.getInstance(this).sendBroadcast(intent) + androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(this).sendBroadcast(intent) } /** @@ -135,7 +135,7 @@ fun Context.sendLocalBroadcast(intent: Intent) { * @param intent intent that contains broadcast information */ fun Context.sendLocalBroadcastSync(intent: Intent) { - LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent) + androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent) } /** @@ -144,7 +144,7 @@ fun Context.sendLocalBroadcastSync(intent: Intent) { * @param receiver receiver that gets registered. */ fun Context.registerLocalReceiver(receiver: BroadcastReceiver, filter: IntentFilter) { - LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter) + androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter) } /** @@ -153,7 +153,7 @@ fun Context.registerLocalReceiver(receiver: BroadcastReceiver, filter: IntentFil * @param receiver receiver that gets unregistered. */ fun Context.unregisterLocalReceiver(receiver: BroadcastReceiver) { - LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) + androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CoroutinesExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CoroutinesExtensions.kt index a5313ac1f7..729418d0b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CoroutinesExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CoroutinesExtensions.kt @@ -1,13 +1,15 @@ package eu.kanade.tachiyomi.util -import kotlinx.coroutines.experimental.CoroutineScope -import kotlinx.coroutines.experimental.CoroutineStart -import kotlinx.coroutines.experimental.Job -import kotlinx.coroutines.experimental.android.UI -import kotlinx.coroutines.experimental.launch +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Job +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlin.coroutines.EmptyCoroutineContext fun launchUI(block: suspend CoroutineScope.() -> Unit): Job = - launch(UI, CoroutineStart.DEFAULT, null, block) + GlobalScope.launch(Dispatchers.Main,CoroutineStart.DEFAULT,block) fun launchNow(block: suspend CoroutineScope.() -> Unit): Job = - launch(UI, CoroutineStart.UNDISPATCHED, null, block) + GlobalScope.launch(Dispatchers.Main,CoroutineStart.UNDISPATCHED,block) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt index edff38614f..a93bd9ee16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt @@ -5,8 +5,8 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.os.Environment -import android.support.v4.content.ContextCompat -import android.support.v4.os.EnvironmentCompat +import androidx.core.content.ContextCompat +import androidx.core.os.EnvironmentCompat import java.io.File object DiskUtil { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/FileExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/FileExtensions.kt index 3ad0eeca05..98fe9bfbb1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/FileExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/FileExtensions.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.util import android.content.Context import android.net.Uri import android.os.Build -import android.support.v4.content.FileProvider +import androidx.core.content.FileProvider import eu.kanade.tachiyomi.BuildConfig import java.io.File diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ImageUtil.kt index fa879d4b4f..d9b0ebd81c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ImageUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ImageUtil.kt @@ -1,7 +1,11 @@ package eu.kanade.tachiyomi.util +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.drawable.* import java.io.InputStream import java.net.URLConnection +import kotlin.math.abs object ImageUtil { @@ -49,6 +53,167 @@ object ImageUtil { return null } + fun autoSetBackground(image: Bitmap): Drawable { + if (image.width < 50 || image.height < 50) + return ColorDrawable(android.R.attr.colorBackground) + val top = 5 + val bot = image.height - 5 + val left = (image.width * 0.0275).toInt() + val right = image.width - left + val midX = image.width / 2 + val midY = image.height / 2 + val offsetX = (image.width * 0.01).toInt() + val offsetY = (image.height * 0.01).toInt() + val topLeftIsDark = isDark(image.getPixel(left, top)) + val topRightIsDark = isDark(image.getPixel(right, top)) + val midLeftIsDark = isDark(image.getPixel(left, midY)) + val midRightIsDark = isDark(image.getPixel(right, midY)) + val topMidIsDark = isDark(image.getPixel(midX, top)) + val botLeftIsDark = isDark(image.getPixel(left, bot)) + val botRightIsDark = isDark(image.getPixel(right, bot)) + + var darkBG = (topLeftIsDark && (botLeftIsDark || botRightIsDark || topRightIsDark || midLeftIsDark || topMidIsDark)) + || (topRightIsDark && (botRightIsDark || botLeftIsDark || midRightIsDark || topMidIsDark)) + + if (!isWhite(image.getPixel(left, top)) && pixelIsClose(image.getPixel(left, top), image.getPixel(midX, top)) && + !isWhite(image.getPixel(midX, top)) && pixelIsClose(image.getPixel(midX, top), image.getPixel(right, top)) && + !isWhite(image.getPixel(right, top)) && pixelIsClose(image.getPixel(right, top), image.getPixel(right, bot)) && + !isWhite(image.getPixel(right, bot)) && pixelIsClose(image.getPixel(right, bot), image.getPixel(midX, bot)) && + !isWhite(image.getPixel(midX, bot)) && pixelIsClose(image.getPixel(midX, bot), image.getPixel(left, bot)) && + !isWhite(image.getPixel(left, bot)) && pixelIsClose(image.getPixel(left, bot), image.getPixel(left, top))) + return ColorDrawable(image.getPixel(left, top)) + + if (isWhite(image.getPixel(left, top)).toInt() + + isWhite(image.getPixel(right, top)).toInt() + + isWhite(image.getPixel(left, bot)).toInt() + + isWhite(image.getPixel(right, bot)).toInt() > 2) + darkBG = false + + var blackPixel = when { + topLeftIsDark -> image.getPixel(left, top) + topRightIsDark -> image.getPixel(right, top) + botLeftIsDark -> image.getPixel(left, bot) + else -> image.getPixel(right, bot) + } + + var overallWhitePixels = 0 + var overallBlackPixels = 0 + var topBlackStreak = 0 + var topWhiteStreak = 0 + var botBlackStreak = 0 + var botWhiteStreak = 0 + outer@ for (x in intArrayOf(left, right, left - offsetX, right + offsetX)) { + var whitePixelsStreak = 0 + var whitePixels = 0 + var blackPixelsStreak = 0 + var blackPixels = 0 + var blackStreak = false + var whiteStrak = false + val notOffset = x == left || x == right + for ((index, y) in (0 until image.height step image.height / 25).withIndex()) { + val pixel = image.getPixel(x, y) + val pixelOff = image.getPixel(x + (if (x < image.width/2) -offsetX else offsetX), y) + if (isWhite(pixel)) { + whitePixelsStreak++ + whitePixels++ + if (notOffset) + overallWhitePixels++ + if (whitePixelsStreak > 14) + whiteStrak = true + if (whitePixelsStreak > 6 && whitePixelsStreak >= index - 1) + topWhiteStreak = whitePixelsStreak + } else { + whitePixelsStreak = 0 + if (isDark(pixel) && isDark(pixelOff)) { + blackPixels++ + if (notOffset) + overallBlackPixels++ + blackPixelsStreak++ + if (blackPixelsStreak >= 14) + blackStreak = true + continue + } + } + if (blackPixelsStreak > 6 && blackPixelsStreak >= index - 1) + topBlackStreak = blackPixelsStreak + blackPixelsStreak = 0 + + } + if (blackPixelsStreak > 6) + botBlackStreak = blackPixelsStreak + else if (whitePixelsStreak > 6) + botWhiteStreak = whitePixelsStreak + when { + blackPixels > 22 -> { + if (x == right || x == right + offsetX) + blackPixel = when { + topRightIsDark -> image.getPixel(right, top) + botRightIsDark -> image.getPixel(right, bot) + else -> blackPixel + } + darkBG = true + overallWhitePixels = 0 + break@outer + } + blackStreak -> { + darkBG = true + if (x == right || x == right + offsetX) + blackPixel = when { + topRightIsDark -> image.getPixel(right, top) + botRightIsDark -> image.getPixel(right, bot) + else -> blackPixel + } + if (blackPixels > 18) { + overallWhitePixels = 0 + break@outer + } + } + whiteStrak || whitePixels > 22 -> darkBG = false + } + } + + val topIsBlackStreak = topBlackStreak > topWhiteStreak + val bottomIsBlackStreak = botBlackStreak > botWhiteStreak + if (overallWhitePixels > 9 && overallWhitePixels > overallBlackPixels) + darkBG = false + if (topIsBlackStreak && bottomIsBlackStreak) + darkBG = true + val whiteColor = android.R.attr.colorBackground + if (darkBG) { + return if (isWhite(image.getPixel(left, bot)) && isWhite(image.getPixel(right, bot))) GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(blackPixel, blackPixel, whiteColor, whiteColor)) + else if (isWhite(image.getPixel(left, top)) && isWhite(image.getPixel(right, top))) GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(whiteColor, whiteColor, blackPixel, blackPixel)) + else ColorDrawable(blackPixel) + } + if (topIsBlackStreak || (topLeftIsDark && topRightIsDark + && isDark(image.getPixel(left - offsetX, top)) && isDark(image.getPixel(right + offsetX, top)) + && (topMidIsDark || overallBlackPixels > 9))) + return GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(blackPixel, blackPixel, whiteColor, whiteColor)) + else if (bottomIsBlackStreak || (botLeftIsDark && botRightIsDark + && isDark(image.getPixel(left - offsetX, bot)) && isDark(image.getPixel(right + offsetX, bot)) + && (isDark(image.getPixel(midX, bot)) || overallBlackPixels > 9))) + return GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(whiteColor, whiteColor, blackPixel, blackPixel)) + return ColorDrawable(whiteColor) + } + + fun Boolean.toInt() = if (this) 1 else 0 + private fun isDark(color: Int): Boolean { + return Color.red(color) < 40 && Color.blue(color) < 40 && Color.green(color) < 40 + } + + private fun pixelIsClose(color1: Int, color2: Int): Boolean { + return abs(Color.red(color1) - Color.red(color2)) < 30 && + abs(Color.green(color1) - Color.green(color2)) < 30 && + abs(Color.blue(color1) - Color.blue(color2)) < 30 + } + + private fun isWhite(color: Int): Boolean { + return Color.red(color) + Color.blue(color) + Color.green(color) > 740 + } + private fun ByteArray.compareWith(magic: ByteArray): Boolean { for (i in 0 until magic.size) { if (this[i] != magic[i]) return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt index 0419476da5..728ce72488 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.util -import android.support.annotation.DrawableRes -import android.support.graphics.drawable.VectorDrawableCompat +import androidx.annotation.DrawableRes +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import android.widget.ImageView /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt index 30471b37c9..556f84e82a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt @@ -22,5 +22,5 @@ fun Element.attrOrText(css: String): String { * @param html the body of the response. Use only if the body was read before calling this method. */ fun Response.asJsoup(html: String? = null): Document { - return Jsoup.parse(html ?: body()!!.string(), request().url().toString()) + return Jsoup.parse(html ?: body!!.string(), request.url.toString()) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt index 14e6726e3a..0cb6715f75 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt @@ -30,7 +30,7 @@ object LocaleHelper { /** * The application's locale. When it's null, the system locale is used. */ - private var appLocale = getLocaleFromString(preferences.lang()) + private var appLocale = getLocaleFromString(preferences.lang() ?: "") /** * The currently applied locale. Used to avoid losing the selected language after a non locale @@ -136,9 +136,9 @@ object LocaleHelper { private fun updateConfigLocale(config: Configuration, locale: Locale): Configuration { val newConfig = Configuration(config) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - newConfig.locale = locale + newConfig.setLocale(locale) } else { - newConfig.locales = LocaleList(locale) + newConfig.setLocales(LocaleList(locale)) } return newConfig } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/OkioExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/OkioExtensions.kt index c4fbf2c00c..5db31230e7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/OkioExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/OkioExtensions.kt @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.util import okio.BufferedSource import okio.Okio +import okio.buffer +import okio.sink import java.io.File import java.io.OutputStream @@ -31,7 +33,7 @@ fun BufferedSource.saveTo(file: File) { */ fun BufferedSource.saveTo(stream: OutputStream) { use { input -> - Okio.buffer(Okio.sink(stream)).use { + stream.sink().buffer().use { it.writeAll(input) it.flush() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ViewExtensions.kt index af9b041e29..3a8a0c852e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ViewExtensions.kt @@ -2,14 +2,25 @@ package eu.kanade.tachiyomi.util +import android.content.Context import android.graphics.Color import android.graphics.Point import android.graphics.Typeface -import android.support.design.widget.Snackbar +import android.os.Build +import androidx.annotation.Px +import androidx.annotation.RequiresApi +import com.google.android.material.snackbar.Snackbar +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowInsets import android.widget.TextView +import androidx.coordinatorlayout.widget.CoordinatorLayout import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.util.ColorGenerator +import eu.kanade.tachiyomi.R /** * Returns coordinates of view. @@ -26,15 +37,59 @@ fun View.getCoordinates() = Point((left + right) / 2, (top + bottom) / 2) * @param length the duration of the snack. * @param f a function to execute in the snack, allowing for example to define a custom action. */ -inline fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: Snackbar.() -> Unit): Snackbar { +fun View.snack(message: String, length: Int = Snackbar.LENGTH_SHORT, f: (Snackbar.() -> +Unit)? = null): Snackbar { val snack = Snackbar.make(this, message, length) - val textView: TextView = snack.view.findViewById(android.support.design.R.id.snackbar_text) - textView.setTextColor(Color.WHITE) - snack.f() + val textView: TextView = snack.view.findViewById(com.google.android.material.R.id.snackbar_text) + textView.setTextColor(context.getResourceColor(android.R.attr.textColorPrimaryInverse)) + when { + Build.VERSION.SDK_INT >= 23 -> { + val leftM = if (this is CoordinatorLayout) 0 else rootWindowInsets.systemWindowInsetLeft + val rightM = if (this is CoordinatorLayout) 0 + else rootWindowInsets.systemWindowInsetRight + snack.config(context, rootWindowInsets + .systemWindowInsetBottom, rightM, leftM) + } + else -> snack.config(context) + } + if (f != null) { + snack.f() + } + if (Build.VERSION.SDK_INT < 23) { + val view = if (this !is CoordinatorLayout) this else snack.view + view.doOnApplyWindowInsets { _, insets, _ -> + snack.view.updateLayoutParams { + bottomMargin = 12 + insets.systemWindowInsetBottom + } + } + } + else { + snack.view.doOnApplyWindowInsets { _,_,_ -> } + } snack.show() return snack } +fun View.snack(resource: Int, length: Int = Snackbar.LENGTH_SHORT, f: (Snackbar.() -> +Unit)? = null): Snackbar { + return snack(context.getString(resource), length, f) +} + +fun Snackbar.config(context: Context, bottomMargin: Int = 0, rightMargin: Int = 0, leftMargin: +Int = 0) { + val params = this.view.layoutParams as ViewGroup.MarginLayoutParams + params.setMargins(12 + leftMargin, 12, 12 + rightMargin, 12 + bottomMargin) + this.view.layoutParams = params + this.view.background = context.getDrawable(R.drawable.bg_snackbar) + + ViewCompat.setElevation(this.view, 6f) +} + +fun Snackbar.getText(): CharSequence { + val textView: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text) + return textView.text +} + inline fun View.visible() { visibility = View.VISIBLE } @@ -68,3 +123,84 @@ fun View.getRound(text: String, random : Boolean = true): TextDrawable { .endConfig() .buildRound(text, if (random) ColorGenerator.MATERIAL.randomColor else ColorGenerator.MATERIAL.getColor(text)) } + +inline val View.marginTop: Int + get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin ?: 0 + +inline val View.marginBottom: Int + get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.bottomMargin ?: 0 + +inline val View.marginRight: Int + get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.rightMargin ?: 0 + +inline val View.marginLeft: Int + get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.leftMargin ?: 0 + +object RecyclerWindowInsetsListener : View.OnApplyWindowInsetsListener { + override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { + v.setPadding(0,0,0,insets.systemWindowInsetBottom) + //v.updatePaddingRelative(bottom = v.paddingBottom + insets.systemWindowInsetBottom) + return insets + } +} + +fun View.doOnApplyWindowInsets(f: (View, WindowInsets, ViewPaddingState) -> Unit) { + // Create a snapshot of the view's padding state + val paddingState = createStateForView(this) + setOnApplyWindowInsetsListener { v, insets -> + f(v, insets, paddingState) + insets + } + requestApplyInsetsWhenAttached() +} + +fun View.requestApplyInsetsWhenAttached() { + if (isAttachedToWindow) { + requestApplyInsets() + } else { + addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + v.requestApplyInsets() + } + + override fun onViewDetachedFromWindow(v: View) = Unit + }) + } +} + +inline fun View.updateLayoutParams(block: T.() -> Unit) { + val params = layoutParams as T + block(params) + layoutParams = params +} + +inline fun View.updatePadding( + @Px left: Int = paddingLeft, + @Px top: Int = paddingTop, + @Px right: Int = paddingRight, + @Px bottom: Int = paddingBottom +) { + setPadding(left, top, right, bottom) +} + +private fun createStateForView(view: View) = ViewPaddingState(view.paddingLeft, + view.paddingTop, view.paddingRight, view.paddingBottom, view.paddingStart, view.paddingEnd) + +data class ViewPaddingState( + val left: Int, + val top: Int, + val right: Int, + val bottom: Int, + val start: Int, + val end: Int +) + +@RequiresApi(17) +inline fun View.updatePaddingRelative( + @Px start: Int = paddingStart, + @Px top: Int = paddingTop, + @Px end: Int = paddingEnd, + @Px bottom: Int = paddingBottom +) { + setPaddingRelative(start, top, end, bottom) +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt index 21ccf5ae3a..1c22523ff2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.util -import android.support.annotation.LayoutRes +import androidx.annotation.LayoutRes import android.view.LayoutInflater import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt index 62cc4b04c3..1eb3cdbab6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt @@ -1,14 +1,14 @@ package eu.kanade.tachiyomi.widget import android.content.Context -import android.support.v7.widget.GridLayoutManager -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import android.util.AttributeSet class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - RecyclerView(context, attrs) { + androidx.recyclerview.widget.RecyclerView(context, attrs) { - private val manager = GridLayoutManager(context, 1) + private val manager = androidx.recyclerview.widget.GridLayoutManager(context, 1) private var columnWidth = -1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt index 823e1ce438..6cb136ea03 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.widget -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.ViewGroup import com.nononsenseapps.filepicker.AbstractFilePickerFragment import com.nononsenseapps.filepicker.FilePickerActivity @@ -21,7 +21,7 @@ class CustomLayoutPickerActivity : FilePickerActivity() { } class CustomLayoutFilePickerFragment : FilePickerFragment() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder { when (viewType) { LogicHandler.VIEWTYPE_DIR -> { val view = parent.inflate(R.layout.common_listitem_dir) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt index 327bb20aa5..a8fdf1b484 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.widget import android.content.Context -import android.support.annotation.StringRes +import androidx.annotation.StringRes import android.util.AttributeSet import android.widget.LinearLayout import eu.kanade.tachiyomi.R diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/DrawerSwipeCloseListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/DrawerSwipeCloseListener.kt index 078bceff44..00441c078a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/DrawerSwipeCloseListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/DrawerSwipeCloseListener.kt @@ -1,23 +1,23 @@ package eu.kanade.tachiyomi.widget -import android.support.v4.widget.DrawerLayout +import androidx.drawerlayout.widget.DrawerLayout import android.view.View import android.view.ViewGroup class DrawerSwipeCloseListener( - private val drawer: DrawerLayout, + private val drawer: androidx.drawerlayout.widget.DrawerLayout, private val navigationView: ViewGroup -) : DrawerLayout.SimpleDrawerListener() { +) : androidx.drawerlayout.widget.DrawerLayout.SimpleDrawerListener() { override fun onDrawerOpened(drawerView: View) { if (drawerView == navigationView) { - drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, drawerView) + drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED, drawerView) } } override fun onDrawerClosed(drawerView: View) { if (drawerView == navigationView) { - drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, drawerView) + drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED, drawerView) } } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt index 20c33a4fc7..d3ad7f6989 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt @@ -4,8 +4,8 @@ import android.animation.ObjectAnimator import android.animation.StateListAnimator import android.content.Context import android.os.Build -import android.support.design.R -import android.support.design.widget.AppBarLayout +import com.google.android.material.R +import com.google.android.material.appbar.AppBarLayout import android.util.AttributeSet class ElevationAppBarLayout @JvmOverloads constructor( diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt index 27db7bfbb6..04ab00a585 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt @@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.widget import android.content.Context import android.graphics.drawable.Drawable -import android.support.annotation.CallSuper -import android.support.graphics.drawable.VectorDrawableCompat -import android.support.v4.content.ContextCompat -import android.support.v7.widget.RecyclerView +import androidx.annotation.CallSuper +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView import android.util.AttributeSet import android.view.View import android.view.ViewGroup @@ -160,7 +160,7 @@ open class ExtendedNavigationView @JvmOverloads constructor( * Base adapter for the navigation view. It knows how to create and render every subclass of * [Item]. */ - abstract inner class Adapter(private val items: List) : RecyclerView.Adapter() { + abstract inner class Adapter(private val items: List) : androidx.recyclerview.widget.RecyclerView.Adapter() { private val onClick = View.OnClickListener { val pos = recycler.getChildAdapterPosition(it) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationBase.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationBase.kt index eaa4201e1b..3e30e75667 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationBase.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationBase.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.widget -import android.support.design.widget.CoordinatorLayout -import android.support.design.widget.FloatingActionButton -import android.support.v4.view.ViewCompat +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.floatingactionbutton.FloatingActionButton +import androidx.core.view.ViewCompat import android.view.View abstract class FABAnimationBase : FloatingActionButton.Behavior() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationUpDown.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationUpDown.kt index e1c4245d27..6b0853490d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationUpDown.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationUpDown.kt @@ -1,16 +1,22 @@ package eu.kanade.tachiyomi.widget import android.content.Context -import android.support.design.widget.FloatingActionButton -import android.support.v4.view.animation.FastOutSlowInInterpolator +import android.content.res.Configuration +import android.graphics.Rect import android.util.AttributeSet import android.view.View import android.view.animation.Animation import android.view.animation.AnimationUtils +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.interpolator.view.animation.FastOutSlowInInterpolator +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.R +import kotlin.math.min @Suppress("unused", "UNUSED_PARAMETER") -class FABAnimationUpDown @JvmOverloads constructor(ctx: Context, attrs: AttributeSet? = null) : FABAnimationBase() { +class FABAnimationUpDown @JvmOverloads constructor(ctx: Context, attrs: AttributeSet? = null) : + FABAnimationBase() { private val INTERPOLATOR = FastOutSlowInInterpolator() @@ -48,4 +54,34 @@ class FABAnimationUpDown @JvmOverloads constructor(ctx: Context, attrs: Attribut button.visibility = View.VISIBLE button.startAnimation(inAnimation) } + + override fun onDependentViewChanged(parent: CoordinatorLayout, child: FloatingActionButton, dependency: View): Boolean { + if (isTablet(child.context)) return true + val translationY = getFabTranslationYForSnackbar(parent, child) + child.translationY = translationY + return true + } + + private fun isTablet(context: Context): Boolean { + return (context.resources.configuration.screenLayout and Configuration + .SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE + } + + private fun getFabTranslationYForSnackbar(parent: CoordinatorLayout, fab: + FloatingActionButton): Float { + var minOffset = 0f + val dependencies = parent.getDependencies(fab) + for (i in 0 until dependencies.size) { + val view = dependencies[i] + if (view is Snackbar.SnackbarLayout) { + minOffset = min(minOffset, view.translationY - view.height) + } + } + return minOffset + } + + override fun getInsetDodgeRect(parent: CoordinatorLayout, child: FloatingActionButton, rect: Rect): Boolean { + rect.set(child.left, child.top + 100, child.right, child.bottom - 1000) + return true + } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/FABMoveBehaviour.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/FABMoveBehaviour.kt new file mode 100644 index 0000000000..0417d3ac45 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/FABMoveBehaviour.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.widget + +import android.content.Context +import android.content.res.Configuration +import android.util.AttributeSet +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.snackbar.Snackbar +import kotlin.math.min + +class FABMoveBehaviour(context: Context, attrs: AttributeSet) : + CoordinatorLayout.Behavior(context, attrs) { + + override fun layoutDependsOn(parent: CoordinatorLayout, child: FloatingActionButton, dependency: View): Boolean { + return dependency is Snackbar.SnackbarLayout + } + override fun onDependentViewChanged(parent: CoordinatorLayout, child: FloatingActionButton, dependency: View): Boolean { + if (isTablet(child.context)) return true + val translationY = getFabTranslationYForSnackbar(parent, child) + child.translationY = translationY + return true + } + + private fun isTablet(context: Context): Boolean { + return (context.resources.configuration.screenLayout and Configuration + .SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE + } + + private fun getFabTranslationYForSnackbar(parent: CoordinatorLayout, fab: FloatingActionButton): Float { + var minOffset = 0f + val dependencies = parent.getDependencies(fab) + for (i in 0 until dependencies.size) { + val view = dependencies[i] + if (view is Snackbar.SnackbarLayout) { + minOffset = min(minOffset, view.translationY - view.height) + } + } + return minOffset + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/PreCachingLayoutManager.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/PreCachingLayoutManager.kt index 82c6160984..9469c5e8cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/PreCachingLayoutManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/PreCachingLayoutManager.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.widget import android.content.Context -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView -class PreCachingLayoutManager(context: Context) : LinearLayoutManager(context) { +class PreCachingLayoutManager(context: Context) : androidx.recyclerview.widget.LinearLayoutManager(context) { init { isItemPrefetchEnabled = false @@ -16,7 +16,7 @@ class PreCachingLayoutManager(context: Context) : LinearLayoutManager(context) { var extraLayoutSpace = 0 - override fun getExtraLayoutSpace(state: RecyclerView.State): Int { + override fun getExtraLayoutSpace(state: androidx.recyclerview.widget.RecyclerView.State): Int { if (extraLayoutSpace > 0) { return extraLayoutSpace } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt index 31fcec901c..e70c12b8bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt @@ -2,13 +2,13 @@ package eu.kanade.tachiyomi.widget import android.annotation.SuppressLint import android.content.Context -import android.support.design.R -import android.support.design.internal.ScrimInsetsFrameLayout -import android.support.design.widget.TextInputLayout -import android.support.v4.view.ViewCompat -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView -import android.support.v7.widget.TintTypedArray +import com.google.android.material.R +import com.google.android.material.internal.ScrimInsetsFrameLayout +import com.google.android.material.textfield.TextInputLayout +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.appcompat.widget.TintTypedArray import android.util.AttributeSet import android.view.View import android.view.ViewGroup @@ -17,7 +17,7 @@ import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.R as TR @Suppress("LeakingThis") -@SuppressLint("PrivateResource") +@SuppressLint("PrivateResource", "RestrictedApi") open class SimpleNavigationView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @@ -32,7 +32,7 @@ open class SimpleNavigationView @JvmOverloads constructor( /** * Recycler view containing all the items. */ - protected val recycler = RecyclerView(context) + val recycler = androidx.recyclerview.widget.RecyclerView(context) init { // Custom attributes @@ -56,7 +56,8 @@ open class SimpleNavigationView @JvmOverloads constructor( a.recycle() - recycler.layoutManager = LinearLayoutManager(context) + recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) + recycler.clipToPadding = false } /** @@ -77,7 +78,7 @@ open class SimpleNavigationView @JvmOverloads constructor( /** * Base view holder. */ - abstract class Holder(view: View) : RecyclerView.ViewHolder(view) + abstract class Holder(view: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(view) /** * Separator view holder. diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt index 2917879b9c..6f1eefa263 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.widget import android.graphics.drawable.Drawable -import android.support.graphics.drawable.VectorDrawableCompat +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import android.view.View import android.widget.ImageView import android.widget.ImageView.ScaleType diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt index 3effd64ec2..6b101e17ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.widget -import android.support.v4.view.PagerAdapter +import androidx.viewpager.widget.PagerAdapter import android.view.View import android.view.ViewGroup -abstract class ViewPagerAdapter : PagerAdapter() { +abstract class ViewPagerAdapter : androidx.viewpager.widget.PagerAdapter() { protected abstract fun createView(container: ViewGroup, position: Int): View diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt index 085cfd004c..5631d50c40 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.widget.preference import android.content.Context -import android.support.v7.preference.ListPreference +import androidx.preference.ListPreference import android.util.AttributeSet class IntListPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt index ac45bd4afc..8a0412cc0d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt @@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.widget.preference import android.content.Context import android.graphics.Color -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.PreferenceViewHolder +import androidx.preference.CheckBoxPreference +import androidx.preference.PreferenceViewHolder import android.util.AttributeSet import android.view.View import eu.kanade.tachiyomi.R diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt index adc8f386a7..a778196d61 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.widget.preference import android.content.Context -import android.support.v7.preference.Preference -import android.support.v7.preference.PreferenceViewHolder +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder import android.util.AttributeSet import eu.kanade.tachiyomi.R import kotlinx.android.synthetic.main.pref_widget_imageview.view.* diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt index cff0847c10..d2be7de057 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt @@ -4,9 +4,9 @@ import android.annotation.TargetApi import android.content.Context import android.content.res.TypedArray import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH -import android.support.v7.preference.PreferenceCategory -import android.support.v7.preference.PreferenceViewHolder -import android.support.v7.widget.SwitchCompat +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceViewHolder +import androidx.appcompat.widget.SwitchCompat import android.util.AttributeSet import android.view.View import android.widget.Checkable diff --git a/app/src/main/res/drawable-hdpi/baseline_arrow_back_white_24.png b/app/src/main/res/drawable-hdpi/baseline_arrow_back_white_24.png new file mode 100644 index 0000000000..b3feb38665 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_arrow_back_white_24.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_arrow_forward_white_24.png b/app/src/main/res/drawable-hdpi/baseline_arrow_forward_white_24.png new file mode 100644 index 0000000000..b640301772 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_arrow_forward_white_24.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_close_white_24.png b/app/src/main/res/drawable-hdpi/baseline_close_white_24.png new file mode 100644 index 0000000000..c8598b6133 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_skip_next_white_24.png b/app/src/main/res/drawable-hdpi/baseline_skip_next_white_24.png new file mode 100644 index 0000000000..f7ddb5882b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_skip_next_white_24.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_18.png b/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_18.png new file mode 100644 index 0000000000..fd853dfb6d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_18.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_24.png b/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_24.png new file mode 100644 index 0000000000..baf96b8d9a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_24.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_36.png b/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_36.png new file mode 100644 index 0000000000..b36eb4ff97 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_36.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_48.png b/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_48.png new file mode 100644 index 0000000000..74584ecabd Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_swap_calls_white_48.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_tachiyomi_icon.png b/app/src/main/res/drawable-hdpi/ic_tachiyomi_icon.png new file mode 100644 index 0000000000..c7e17442b8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_tachiyomi_icon.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_arrow_back_white_24.png b/app/src/main/res/drawable-mdpi/baseline_arrow_back_white_24.png new file mode 100644 index 0000000000..854124ef7e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_arrow_back_white_24.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_arrow_forward_white_24.png b/app/src/main/res/drawable-mdpi/baseline_arrow_forward_white_24.png new file mode 100644 index 0000000000..01d3c16238 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_arrow_forward_white_24.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_white_24.png b/app/src/main/res/drawable-mdpi/baseline_close_white_24.png new file mode 100644 index 0000000000..08d8109923 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_skip_next_white_24.png b/app/src/main/res/drawable-mdpi/baseline_skip_next_white_24.png new file mode 100644 index 0000000000..736df24407 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_skip_next_white_24.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_18.png b/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_18.png new file mode 100644 index 0000000000..2d77978c6f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_18.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_24.png b/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_24.png new file mode 100644 index 0000000000..2a3dfa5b6d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_24.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_36.png b/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_36.png new file mode 100644 index 0000000000..baf96b8d9a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_36.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_48.png b/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_48.png new file mode 100644 index 0000000000..106a5f0570 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_swap_calls_white_48.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_tachiyomi_icon.png b/app/src/main/res/drawable-mdpi/ic_tachiyomi_icon.png new file mode 100644 index 0000000000..60a1e6a6e6 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_tachiyomi_icon.png differ diff --git a/app/src/main/res/drawable-v21/list_item_selector_dark.xml b/app/src/main/res/drawable-v21/list_item_selector_dark.xml index 07b9ef6d5b..c86632f6a9 100644 --- a/app/src/main/res/drawable-v21/list_item_selector_dark.xml +++ b/app/src/main/res/drawable-v21/list_item_selector_dark.xml @@ -12,7 +12,7 @@ - + diff --git a/app/src/main/res/drawable-xhdpi/baseline_arrow_back_white_24.png b/app/src/main/res/drawable-xhdpi/baseline_arrow_back_white_24.png new file mode 100644 index 0000000000..04b039174c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_arrow_back_white_24.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_arrow_forward_white_24.png b/app/src/main/res/drawable-xhdpi/baseline_arrow_forward_white_24.png new file mode 100644 index 0000000000..57359a1e33 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_arrow_forward_white_24.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png b/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png new file mode 100644 index 0000000000..4419c31ab3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_skip_next_white_24.png b/app/src/main/res/drawable-xhdpi/baseline_skip_next_white_24.png new file mode 100644 index 0000000000..203e6369a3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_skip_next_white_24.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_18.png b/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_18.png new file mode 100644 index 0000000000..baf96b8d9a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_18.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_24.png b/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_24.png new file mode 100644 index 0000000000..106a5f0570 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_24.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_36.png b/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_36.png new file mode 100644 index 0000000000..74584ecabd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_36.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_48.png b/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_48.png new file mode 100644 index 0000000000..2de9d7969d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_swap_calls_white_48.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_tachiyomi_icon.png b/app/src/main/res/drawable-xhdpi/ic_tachiyomi_icon.png new file mode 100644 index 0000000000..7cae72dda1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_tachiyomi_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_arrow_back_white_24.png b/app/src/main/res/drawable-xxhdpi/baseline_arrow_back_white_24.png new file mode 100644 index 0000000000..a40bc710b3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_arrow_back_white_24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_arrow_forward_white_24.png b/app/src/main/res/drawable-xxhdpi/baseline_arrow_forward_white_24.png new file mode 100644 index 0000000000..b9d2f3c7c7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_arrow_forward_white_24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png b/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png new file mode 100644 index 0000000000..3af97b1df7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_skip_next_white_24.png b/app/src/main/res/drawable-xxhdpi/baseline_skip_next_white_24.png new file mode 100644 index 0000000000..a0fd4505a8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_skip_next_white_24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_18.png b/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_18.png new file mode 100644 index 0000000000..b36eb4ff97 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_18.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_24.png b/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_24.png new file mode 100644 index 0000000000..74584ecabd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_36.png b/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_36.png new file mode 100644 index 0000000000..b37a98febc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_36.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_48.png b/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_48.png new file mode 100644 index 0000000000..b2521cdd1d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_swap_calls_white_48.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_tachiyomi_icon.png b/app/src/main/res/drawable-xxhdpi/ic_tachiyomi_icon.png new file mode 100644 index 0000000000..ba98a7e57e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_tachiyomi_icon.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_arrow_back_white_24.png b/app/src/main/res/drawable-xxxhdpi/baseline_arrow_back_white_24.png new file mode 100644 index 0000000000..fdabf3f879 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_arrow_back_white_24.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_arrow_forward_white_24.png b/app/src/main/res/drawable-xxxhdpi/baseline_arrow_forward_white_24.png new file mode 100644 index 0000000000..bbdf2117b2 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_arrow_forward_white_24.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_white_24.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_24.png new file mode 100644 index 0000000000..f5a903fd73 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_skip_next_white_24.png b/app/src/main/res/drawable-xxxhdpi/baseline_skip_next_white_24.png new file mode 100644 index 0000000000..01c0fbd27f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_skip_next_white_24.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_18.png b/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_18.png new file mode 100644 index 0000000000..74584ecabd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_18.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_24.png b/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_24.png new file mode 100644 index 0000000000..2de9d7969d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_24.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_36.png b/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_36.png new file mode 100644 index 0000000000..b2521cdd1d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_36.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_48.png b/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_48.png new file mode 100644 index 0000000000..da2a8c3f69 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_swap_calls_white_48.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_tachiyomi_icon.png b/app/src/main/res/drawable-xxxhdpi/ic_tachiyomi_icon.png new file mode 100644 index 0000000000..4424320bf4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_tachiyomi_icon.png differ diff --git a/app/src/main/res/drawable/baseline_arrow_back_24.xml b/app/src/main/res/drawable/baseline_arrow_back_24.xml new file mode 100644 index 0000000000..2989fdadae --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_back_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_arrow_forward_24.xml b/app/src/main/res/drawable/baseline_arrow_forward_24.xml new file mode 100644 index 0000000000..32393b8445 --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_forward_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_close_24.xml b/app/src/main/res/drawable/baseline_close_24.xml new file mode 100644 index 0000000000..3f9973200c --- /dev/null +++ b/app/src/main/res/drawable/baseline_close_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_skip_next_24.xml b/app/src/main/res/drawable/baseline_skip_next_24.xml new file mode 100644 index 0000000000..4f860dba29 --- /dev/null +++ b/app/src/main/res/drawable/baseline_skip_next_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_swap_calls_24.xml b/app/src/main/res/drawable/baseline_swap_calls_24.xml new file mode 100644 index 0000000000..a82b90e712 --- /dev/null +++ b/app/src/main/res/drawable/baseline_swap_calls_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/bg_snackbar.xml b/app/src/main/res/drawable/bg_snackbar.xml new file mode 100644 index 0000000000..ab37d237f8 --- /dev/null +++ b/app/src/main/res/drawable/bg_snackbar.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dialog_selector.xml b/app/src/main/res/drawable/dialog_selector.xml new file mode 100644 index 0000000000..831d0f5d38 --- /dev/null +++ b/app/src/main/res/drawable/dialog_selector.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/list_item_selector_dark.xml b/app/src/main/res/drawable/list_item_selector_dark.xml index 60034f8183..c3a6245050 100644 --- a/app/src/main/res/drawable/list_item_selector_dark.xml +++ b/app/src/main/res/drawable/list_item_selector_dark.xml @@ -5,6 +5,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout-land/manga_info_controller.xml b/app/src/main/res/layout-land/manga_info_controller.xml index bba645590f..07c43e6c46 100644 --- a/app/src/main/res/layout-land/manga_info_controller.xml +++ b/app/src/main/res/layout-land/manga_info_controller.xml @@ -1,5 +1,5 @@ - - + android:orientation="vertical" + android:id="@+id/manga_info_layout"> + + + + - - - + android:layout_marginEnd="16dp" + android:clipToPadding="false"> - @@ -255,10 +262,11 @@ android:layout_marginRight="64dp"/> - + - + - + - + + diff --git a/app/src/main/res/layout-land/reader_color_filter_sheet.xml b/app/src/main/res/layout-land/reader_color_filter_sheet.xml index 761c279928..9cf10846dd 100644 --- a/app/src/main/res/layout-land/reader_color_filter_sheet.xml +++ b/app/src/main/res/layout-land/reader_color_filter_sheet.xml @@ -1,5 +1,5 @@ - - - - + - + diff --git a/app/src/main/res/layout/catalogue_controller.xml b/app/src/main/res/layout/catalogue_controller.xml index 929ea22754..caf67918e2 100644 --- a/app/src/main/res/layout/catalogue_controller.xml +++ b/app/src/main/res/layout/catalogue_controller.xml @@ -1,14 +1,14 @@ - @@ -23,4 +23,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/catalogue_drawer.xml b/app/src/main/res/layout/catalogue_drawer.xml index 6a1cc344b5..cfcd725c1c 100644 --- a/app/src/main/res/layout/catalogue_drawer.xml +++ b/app/src/main/res/layout/catalogue_drawer.xml @@ -5,5 +5,10 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="end" - android:fitsSystemWindows="false"/> - + android:fitsSystemWindows="true"> + + diff --git a/app/src/main/res/layout/catalogue_global_search_controller.xml b/app/src/main/res/layout/catalogue_global_search_controller.xml index eae833eac9..debe4fa181 100644 --- a/app/src/main/res/layout/catalogue_global_search_controller.xml +++ b/app/src/main/res/layout/catalogue_global_search_controller.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content"> - - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/catalogue_global_search_controller_card_item.xml b/app/src/main/res/layout/catalogue_global_search_controller_card_item.xml index 396ff77794..d79a0975c8 100644 --- a/app/src/main/res/layout/catalogue_global_search_controller_card_item.xml +++ b/app/src/main/res/layout/catalogue_global_search_controller_card_item.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/catalogue_grid_item.xml b/app/src/main/res/layout/catalogue_grid_item.xml index 4bde878c41..3442670aa2 100644 --- a/app/src/main/res/layout/catalogue_grid_item.xml +++ b/app/src/main/res/layout/catalogue_grid_item.xml @@ -29,7 +29,7 @@ android:layout_gravity="bottom" android:background="@drawable/gradient_shape"/> - - + - - + diff --git a/app/src/main/res/layout/catalogue_main_controller.xml b/app/src/main/res/layout/catalogue_main_controller.xml index 84efdf4714..6c9c2d5843 100644 --- a/app/src/main/res/layout/catalogue_main_controller.xml +++ b/app/src/main/res/layout/catalogue_main_controller.xml @@ -5,10 +5,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - \ No newline at end of file diff --git a/app/src/main/res/layout/catalogue_main_controller_card_item.xml b/app/src/main/res/layout/catalogue_main_controller_card_item.xml index 14954bc501..071c89ad77 100644 --- a/app/src/main/res/layout/catalogue_main_controller_card_item.xml +++ b/app/src/main/res/layout/catalogue_main_controller_card_item.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - + diff --git a/app/src/main/res/layout/categories_controller.xml b/app/src/main/res/layout/categories_controller.xml index 9bc79a6c39..86427223d8 100644 --- a/app/src/main/res/layout/categories_controller.xml +++ b/app/src/main/res/layout/categories_controller.xml @@ -1,19 +1,22 @@ - + android:layout_height="match_parent" + android:clipToPadding="false" + android:orientation="vertical"> - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/chapters_controller.xml b/app/src/main/res/layout/chapters_controller.xml index 2f2d19d566..e625e2fbb4 100644 --- a/app/src/main/res/layout/chapters_controller.xml +++ b/app/src/main/res/layout/chapters_controller.xml @@ -1,10 +1,11 @@ - - - - + - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/chapters_item.xml b/app/src/main/res/layout/chapters_item.xml index 95a5c4eaf1..c164c0104a 100644 --- a/app/src/main/res/layout/chapters_item.xml +++ b/app/src/main/res/layout/chapters_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/common_view_empty.xml b/app/src/main/res/layout/common_view_empty.xml index 03e3292592..8973f40132 100644 --- a/app/src/main/res/layout/common_view_empty.xml +++ b/app/src/main/res/layout/common_view_empty.xml @@ -5,7 +5,7 @@ android:layout_gravity="center" xmlns:android="http://schemas.android.com/apk/res/android"> - - - - @@ -34,20 +34,20 @@ android:layout_width="wrap_content" /> - - diff --git a/app/src/main/res/layout/extension_card_header.xml b/app/src/main/res/layout/extension_card_header.xml index 2c2e11fc91..596b309301 100644 --- a/app/src/main/res/layout/extension_card_header.xml +++ b/app/src/main/res/layout/extension_card_header.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - @@ -20,5 +20,5 @@ android:paddingTop="8dp" tools:text="Title"/> - + \ No newline at end of file diff --git a/app/src/main/res/layout/extension_card_item.xml b/app/src/main/res/layout/extension_card_item.xml index 23ee4e5e79..15266ac40b 100644 --- a/app/src/main/res/layout/extension_card_item.xml +++ b/app/src/main/res/layout/extension_card_item.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - + diff --git a/app/src/main/res/layout/extension_controller.xml b/app/src/main/res/layout/extension_controller.xml index 8f4bb3d527..fb91bb1662 100644 --- a/app/src/main/res/layout/extension_controller.xml +++ b/app/src/main/res/layout/extension_controller.xml @@ -1,15 +1,16 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/extension_detail_controller.xml b/app/src/main/res/layout/extension_detail_controller.xml index a22c526969..126b092bce 100644 --- a/app/src/main/res/layout/extension_detail_controller.xml +++ b/app/src/main/res/layout/extension_detail_controller.xml @@ -1,5 +1,5 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/library_category.xml b/app/src/main/res/layout/library_category.xml index df6b7ce5ac..8c431ae09b 100644 --- a/app/src/main/res/layout/library_category.xml +++ b/app/src/main/res/layout/library_category.xml @@ -4,11 +4,11 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + \ No newline at end of file diff --git a/app/src/main/res/layout/library_controller.xml b/app/src/main/res/layout/library_controller.xml index b0bed0ca56..e24b4ec9a9 100644 --- a/app/src/main/res/layout/library_controller.xml +++ b/app/src/main/res/layout/library_controller.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/app/src/main/res/layout/library_drawer.xml b/app/src/main/res/layout/library_drawer.xml index 659d66bb9a..1289498950 100644 --- a/app/src/main/res/layout/library_drawer.xml +++ b/app/src/main/res/layout/library_drawer.xml @@ -5,4 +5,11 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="end" - android:fitsSystemWindows="false" /> + android:fitsSystemWindows="true" + android:clipToPadding="true"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/library_list_recycler.xml b/app/src/main/res/layout/library_list_recycler.xml index bb0806f088..1d8db6c66b 100644 --- a/app/src/main/res/layout/library_list_recycler.xml +++ b/app/src/main/res/layout/library_list_recycler.xml @@ -1,8 +1,9 @@ - diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index cbc9d2a6f4..9721268475 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -1,8 +1,14 @@ - + + @@ -11,6 +17,7 @@ android:orientation="vertical" android:id="@+id/main_content" android:layout_width="match_parent" + android:fitsSystemWindows="true" android:layout_height="match_parent"> - - - - + + \ No newline at end of file diff --git a/app/src/main/res/layout/manga_controller.xml b/app/src/main/res/layout/manga_controller.xml index f0b5e594de..ff3e76c669 100644 --- a/app/src/main/res/layout/manga_controller.xml +++ b/app/src/main/res/layout/manga_controller.xml @@ -1,5 +1,5 @@ - - - - + android:orientation="vertical" + android:id="@+id/manga_info_layout"> + + - - - - @@ -237,9 +241,9 @@ app:layout_constraintLeft_toRightOf="@+id/manga_source_label" app:layout_constraintRight_toRightOf="parent"/> - + - + - - + - + - + + diff --git a/app/src/main/res/layout/manga_info_web_controller.xml b/app/src/main/res/layout/manga_info_web_controller.xml deleted file mode 100644 index 6d52f5e22a..0000000000 --- a/app/src/main/res/layout/manga_info_web_controller.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/migration_controller.xml b/app/src/main/res/layout/migration_controller.xml index 643832cd0a..1ba1c2b47a 100644 --- a/app/src/main/res/layout/migration_controller.xml +++ b/app/src/main/res/layout/migration_controller.xml @@ -1,6 +1,7 @@ - + android:layout_height="match_parent" + android:clipToPadding="false"/> diff --git a/app/src/main/res/layout/navigation_view_text.xml b/app/src/main/res/layout/navigation_view_text.xml index 5a3ce8e5eb..31c22748e7 100644 --- a/app/src/main/res/layout/navigation_view_text.xml +++ b/app/src/main/res/layout/navigation_view_text.xml @@ -8,14 +8,14 @@ android:background="?attr/selectableItemBackground" android:focusable="true"> - - - + diff --git a/app/src/main/res/layout/reader_activity.xml b/app/src/main/res/layout/reader_activity.xml index 5c1dadaf0d..2a1ea590d1 100644 --- a/app/src/main/res/layout/reader_activity.xml +++ b/app/src/main/res/layout/reader_activity.xml @@ -44,7 +44,7 @@ android:visibility="invisible" tools:visibility="visible"> - - - - - - - - - + diff --git a/app/src/main/res/layout/reader_color_filter_sheet.xml b/app/src/main/res/layout/reader_color_filter_sheet.xml index 3155bc15cb..c4d4da941b 100644 --- a/app/src/main/res/layout/reader_color_filter_sheet.xml +++ b/app/src/main/res/layout/reader_color_filter_sheet.xml @@ -3,13 +3,14 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:background="?android:colorBackground"> + android:background="?android:colorBackground" + android:forceDarkAllowed="false"> - - - + diff --git a/app/src/main/res/layout/reader_settings_sheet.xml b/app/src/main/res/layout/reader_settings_sheet.xml index d28155d704..ed8e79551c 100644 --- a/app/src/main/res/layout/reader_settings_sheet.xml +++ b/app/src/main/res/layout/reader_settings_sheet.xml @@ -1,5 +1,5 @@ - + android:padding="@dimen/material_component_dialogs_padding_around_content_area" + android:forceDarkAllowed="false"> @@ -32,7 +33,7 @@ app:layout_constraintLeft_toRightOf="@id/general_prefs" app:layout_constraintTop_toTopOf="@id/general_prefs" /> - - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/recent_chapters_controller.xml b/app/src/main/res/layout/recent_chapters_controller.xml index 6dcca143ed..cea9d5f895 100644 --- a/app/src/main/res/layout/recent_chapters_controller.xml +++ b/app/src/main/res/layout/recent_chapters_controller.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/recent_chapters_item.xml b/app/src/main/res/layout/recent_chapters_item.xml index 2bf1b92d87..6d90a4f327 100644 --- a/app/src/main/res/layout/recent_chapters_item.xml +++ b/app/src/main/res/layout/recent_chapters_item.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/recently_read_controller.xml b/app/src/main/res/layout/recently_read_controller.xml index e0f4da99b1..ca92b48550 100644 --- a/app/src/main/res/layout/recently_read_controller.xml +++ b/app/src/main/res/layout/recently_read_controller.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent" android:orientation="vertical"> - - + - @@ -69,4 +69,4 @@ android:text="@string/action_resume"/> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/track_controller.xml b/app/src/main/res/layout/track_controller.xml index 2d3dd35e97..811ccb347c 100644 --- a/app/src/main/res/layout/track_controller.xml +++ b/app/src/main/res/layout/track_controller.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/track_item.xml b/app/src/main/res/layout/track_item.xml index 07c05ebcc2..f3e831c5fc 100644 --- a/app/src/main/res/layout/track_item.xml +++ b/app/src/main/res/layout/track_item.xml @@ -1,12 +1,12 @@ - - @@ -186,6 +186,6 @@ - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/track_search_item.xml b/app/src/main/res/layout/track_search_item.xml index acab56c218..a38bddb7d0 100644 --- a/app/src/main/res/layout/track_search_item.xml +++ b/app/src/main/res/layout/track_search_item.xml @@ -1,12 +1,12 @@ - - - - - + + diff --git a/app/src/main/res/layout/webview_activity.xml b/app/src/main/res/layout/webview_activity.xml new file mode 100644 index 0000000000..6b2bc7f3b7 --- /dev/null +++ b/app/src/main/res/layout/webview_activity.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/catalogue_list.xml b/app/src/main/res/menu/catalogue_list.xml index 1b75d4ae75..80d8739fd7 100644 --- a/app/src/main/res/menu/catalogue_list.xml +++ b/app/src/main/res/menu/catalogue_list.xml @@ -7,7 +7,7 @@ android:title="@string/action_search" android:icon="@drawable/ic_search_white_24dp" app:showAsAction="collapseActionView|ifRoom" - app:actionViewClass="android.support.v7.widget.SearchView"/> + app:actionViewClass="androidx.appcompat.widget.SearchView"/> + app:actionViewClass="androidx.appcompat.widget.SearchView"/> + app:actionViewClass="androidx.appcompat.widget.SearchView"/> diff --git a/app/src/main/res/menu/extension_main.xml b/app/src/main/res/menu/extension_main.xml index dd634ce737..994ae2fbc2 100644 --- a/app/src/main/res/menu/extension_main.xml +++ b/app/src/main/res/menu/extension_main.xml @@ -6,6 +6,6 @@ android:title="@string/action_search" android:icon="@drawable/ic_search_white_24dp" app:showAsAction="collapseActionView|ifRoom" - app:actionViewClass="android.support.v7.widget.SearchView"/> + app:actionViewClass="androidx.appcompat.widget.SearchView"/> diff --git a/app/src/main/res/menu/library.xml b/app/src/main/res/menu/library.xml index b415e339d5..ad53ae03a1 100644 --- a/app/src/main/res/menu/library.xml +++ b/app/src/main/res/menu/library.xml @@ -7,7 +7,7 @@ android:id="@+id/action_search" android:icon="@drawable/ic_search_white_24dp" android:title="@string/action_search" - app:actionViewClass="android.support.v7.widget.SearchView" + app:actionViewClass="androidx.appcompat.widget.SearchView" app:showAsAction="collapseActionView|ifRoom"/> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/webview.xml b/app/src/main/res/menu/webview.xml new file mode 100644 index 0000000000..29b7063b5e --- /dev/null +++ b/app/src/main/res/menu/webview.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 62a445a59c..3a30a0c1af 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -116,6 +116,10 @@ سمة التطبيق السمة الرئيسية السمة الليلية + أزرق غامق + النظام الافتراضي + النظام الافتراضي (AMOLED) + النظام الافتراضي (أزرق غامق) سمة شاشات AMOLED شاشة البداية اللغة @@ -424,7 +428,6 @@ نسخ الترحيل… لا توجد لديك أي فئات. اضغط زر اﻹضافة لإنشاء واحد لتنظيم المكتبة الخاصة بك. - أزرق غامق تمَّ الانتهاء من: الحالي: التّالي: diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index db692909f1..8d86bfb5e6 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -105,6 +105,11 @@ Тема на приложението Главна тема Тъмна тема + Тъмно син + AMOLED тема + Система по подразбиране + Система по подразбиране (AMOLED) + Система по подразбиране(Тъмно син) Начален екран Език По подразбиране @@ -350,7 +355,6 @@ Обновления Ежеседмично Ежемесечно - AMOLED тема Категория по подразбиране Питай всеки път Изрязвай границите @@ -464,7 +468,6 @@ Няма предишна глава Зареждане на страниците… Не бяха заредени успешно: %1$s страници - Тъмносиня тема Показване на прозорец след натискане Издърпайте за повече опции Отвори онлайн diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 14e86b5c54..a0fb01f750 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -120,6 +120,9 @@ প্রধান থিম কালো থিম এমোলেড থিম + সিস্টেমের ডিফল্ট + সিস্টেমের ডিফল্ট (এমোলেড) + সিস্টেমের ডিফল্ট (কালো থিম) আরম্ভ স্ক্রিন ভাষা প্রথম অবস্থা diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index c14566d570..9fbf3ccffc 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -198,6 +198,9 @@ G B A + Per defecte del sistema + Per defecte del sistema (Blau fosc) + Per defecte del sistema (AMOLED) Directori de descàrregues Descarrega només amb WIFI Elimina quan es marqui com a llegit diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index e34cf03e56..6ad2c60741 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -433,4 +433,7 @@ Běžný Stahovač Vytáhnout pro více možností + Výchozí + Výchozí (Tmavě modrá) + Výchozí (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 74b4fe94e1..32211a3cfb 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -445,4 +445,7 @@ Hilfe Reihenfolge der Bibliotheksaktualisierung Bestimme die Reihenfolge in der Tachiyomi nach Updates sucht + Systemstandard + Systemstandard (Dunkelblau) + Systemstandard (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 6f6fb3d290..bf85383016 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -444,4 +444,7 @@ Βοήθεια Συχνότητα ανανέωσης της Βιβλιοθήκης Διαλέξτε την σύχνοτητα που το Tachiyomi ελένχει για διαθέσιμη ενημέρωση + Προεπιλογή συστήματος + Προεπιλογή συστήματος (Σκούρο μπλε) + Προεπιλογή συστήματος (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d48b5eea8d..50bbe9b3c5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -476,4 +476,7 @@ También asegúrese de haber iniciado sesión en las fuentes que lo requieren an Ayuda Orden de actualización de la biblioteca Selecciona el orden en el que Tachiyomi buscará actualizaciones + Predefinido del sistema + Predefinido del sistema (Azul oscuro) + Predefinido del sistema (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 4056982a4e..45c1c8d0bc 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -447,4 +447,7 @@ Apu Kirjaston päivitys järjestys Valitse järjestys jossa päivitykset tarkistetaan + Järjestelmän oletus + Järjestelmän oletus (Tumman sininen) + Järjestelmän oletus (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c4acaf8764..676d4705b9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -363,7 +363,7 @@ Que voulez-vous sauvegarder ? Le défaut ne peut pas être selectionné avec des autres catégories Supprimer les chapitres téléchargés ? - Pour %1$s chapitres + Pour %1$d chapitres Suivi Nombre de chapitres Actualisations @@ -479,4 +479,7 @@ Assurez-vous que vous êtes connecté à des sources qui le demande avant de com Aide Ordre de mise à jour de la librairie Sélectionnez l\'ordre auquel Tachiyomi doit vérifier les mises à jour + Par défaut système + Par défaut système (Bleu foncé) + Par défaut système (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 1465306024..3942e71c3f 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -445,4 +445,7 @@ मदद लाइब्रेरी अद्यतन क्रम उस क्रम का चयन करें जिसमें टचियोमी अद्यतन की जांच करे + डिफ़ॉल्ट प्रणाली + डिफ़ॉल्ट प्रणाली (गहरा नीला) + डिफ़ॉल्ट प्रणाली (अलौकिक) \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 0d2823aa92..a4ce6c6c68 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -172,4 +172,7 @@ Beállítások Sötétkék Összes + Rendszer alapértelmezése + Rendszer alapértelmezése (Sötétkék) + Rendszer alapértelmezése (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 23bcb0a103..1840484302 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -445,4 +445,7 @@ Bantuan Urutan perbarui perpustakaan Pilih urutan perpustakaan yang akan Tachiyomi perbarui terlebih dahulu + Bawaan sistem + Bawaan sistem (Biru gelap) + Bawaan sistem (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 8b8f395ebd..f4a5dcd30f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -480,4 +480,7 @@ Aiuto Ordine di aggiornamento della libreria Seleziona l\'ordine in cui vuoi che Tachiyomi controlli gli aggiornamenti + Predefinita di sistema + Predefinita di sistema (Blu scuro) + Predefinita di sistema (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ce02b5613b..24fd3fcfc2 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -209,4 +209,7 @@ パスワードを表示 ログイン ログイン成功 + システム標準 + システム標準 (ダークブルー) + システム標準 (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b3aab32c7c..aa6796da90 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -445,4 +445,7 @@ 닷지 / 밝게하기 번 / 어둡게하기 도움 + 시스템 기본값 + 시스템 기본값 (다크 블루) + 시스템 기본값 (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 5f21d2cf18..5af891d2e6 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -447,4 +447,7 @@ Skrin Dodge / Lighten Burn / Darken + Tetapan sistem + Tetapan sistem (Biru gelap) + Tetapan sistem (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 9f35fae59e..357b45e6b2 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -447,4 +447,7 @@ Brenn / mørkere Rekkefølge for biblioteksoppdatering Velg rekkefølgen Tachiyomi ser etter oppdateringer + Systemforvalg + Systemforvalg (Mørkeblå) + Systemforvalg (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-night-v29/themes.xml b/app/src/main/res/values-night-v29/themes.xml new file mode 100644 index 0000000000..71bbc144ab --- /dev/null +++ b/app/src/main/res/values-night-v29/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000000..e8658d3fc4 --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,11 @@ + + + @color/md_white_1000_12 + @color/colorAccentDark + #B3000000 + #FFFFFF + @color/colorDarkPrimary + @color/md_white_1000_20 + @color/md_grey_800 + #3399FF + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000000..d4252f2faf --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index d0a9e48672..c3c119984e 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -435,4 +435,7 @@ Zorg ook dat je ingelogd bent voor bronnen die dit vereisen alvorens je het teru Kopiëren Aan het migreren… Standaard + Systeemstandaard + Systeemstandaard (Tumman sininen) + Systeemstandaard (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b4edcbf623..67f20c3cea 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -446,4 +446,7 @@ Nie znaleziono źródła %1$s Pomoc Kolejność aktualizacji biblioteki Ustaw kolejność, w jakiej Tachiyomi ma sprawdzać aktualizacje + Systemowy + Systemowy (Ciemny niebieski) + Systemowy (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 64a1bac3db..85663e4b69 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -447,4 +447,7 @@ Além disso, verifique se as fontes que requerem uma conta foram configuradas co Escurecer Ordem de atualização da biblioteca Selecione a ordem que o Tachiyomi verificará por atualizações + Padrão do sistema + Padrão do sistema (Azul escuro) + Padrão do sistema (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index e4410e2896..30fa3d38aa 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -473,4 +473,7 @@ Ajuda Ordem de atualização da biblioteca Selecione a ordem em que Tachiyomi verifica a atualização + Predefinição do sistema + Predefinição do sistema (Preto/Azul) + Predefinição do sistema (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 12d630f40f..cac058d018 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -444,4 +444,7 @@ Dialog prin apasare lunga Trage în sus pentru mai multe opțiuni Ajutor + Prestabilită de sistem + Prestabilită de sistem (Albastru închis) + Prestabilită de sistem (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8464346189..dc54814d8a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -439,4 +439,7 @@ Помощь Выберите порядок, в котором Tachiyomi проверяет наличие обновлений Порядок обновления библиотеки + Системный + Системный (Темно-синяя тема) + Системный (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index f59117ece2..dfe6217446 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -447,4 +447,7 @@ Agiudu Òrdine de agiornamentu de sa biblioteca Ischerta s\'òrdine chi Tachiyomi at a impreare pro sos agiornamentos + Predefinida de su sistema + Predefinida de su sistema (Biaitu iscuru) + Predefinida de su sistema (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index fbd6e52708..e351acbdd0 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -339,4 +339,7 @@ Preuzmi određeno Preuzmi sve Preuzmi nepročitano + Podrazumevani sistem + Podrazumevani sistem (Tamno plava) + Podrazumevani sistem (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 1145ec7670..9411fd08ab 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -447,4 +447,7 @@ Hjälp Bibliotekets uppdateringsordning Välj i vilken ordning Tachiyomi söker efter uppdateringar + System standard + System standard (Mörkblå) + System standard (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-sw360dp-v13/values-preference.xml b/app/src/main/res/values-sw360dp-v13/values-preference.xml new file mode 100644 index 0000000000..8ed48ca096 --- /dev/null +++ b/app/src/main/res/values-sw360dp-v13/values-preference.xml @@ -0,0 +1,6 @@ + + + + false + 0dp + \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 3fc6aa1939..2179fff83a 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -93,4 +93,5 @@ สีพื้นหลัง ขาว ดำ + ค่าเริ่มต้นของระบบ \ No newline at end of file diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index f793e384ac..1c0b5696e6 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -447,4 +447,7 @@ Karaniwan Aklatan Tagapag-download + Default ng sistema + Default ng sistema (Madilim na bughaw) + Default ng sistema (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a5bf5ccb3c..2f798d2f98 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -447,4 +447,7 @@ Yardım Kitaplık güncelleme sırası Tachiyomi\'nin güncelleme denetleme sırasını seçin + Sistem öntanımlı + Sistem öntanımlı (Koyu mavi) + Sistem öntanımlı (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 1925f12fee..a3ceaf73d8 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -447,4 +447,7 @@ Допомога Порядок оновлення бібліотеки Оберіть порядок у якому Tachiyomi перевірятиме оновлення + Системний + Системний (Темно-синя) + Системний (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-v21/themes.xml b/app/src/main/res/values-v21/themes.xml deleted file mode 100644 index a7ff1d5c4b..0000000000 --- a/app/src/main/res/values-v21/themes.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values-v26/themes.xml b/app/src/main/res/values-v26/themes.xml new file mode 100644 index 0000000000..566da8539c --- /dev/null +++ b/app/src/main/res/values-v26/themes.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml new file mode 100644 index 0000000000..734630a9ad --- /dev/null +++ b/app/src/main/res/values-v27/styles.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v29/themes.xml b/app/src/main/res/values-v29/themes.xml new file mode 100644 index 0000000000..056509289b --- /dev/null +++ b/app/src/main/res/values-v29/themes.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index e545080103..e33e97011c 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -500,4 +500,7 @@ Thường gặp Thư viện Trình tải xuống + Mặc định hệ thống + Mặc định hệ thống (Xanh đậm) + Mặc định hệ thống (AMOLED) \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 015fa2d2e9..a23e8901fc 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -435,6 +435,9 @@ 暗蓝色主题 长按菜单 上拉显示更多选项 + 系统默认 + 系统默认 (暗蓝色主题) + 系统默认 (AMOLED) 用外部浏览器打开 32位色彩 跳过已读章节 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 2c598f74c8..ad0a84027c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -385,4 +385,7 @@ 已閱畢: 下一章: 發現新章節 + 系统默认 + 系统默认 (深色帶藍) + 系统默认 (全黑) \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 76bd9430ce..632566da09 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -25,6 +25,7 @@ @string/white_background @string/black_background + @string/auto_background diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index eab6d88749..10c8cde17a 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,10 +2,17 @@ #54759E - #435E7E + #54759E + @color/md_black_1000_12 + @color/colorPrimary + #FFFFFF + #323232 + @color/md_white_1000 + @color/md_black_1000_12 + @color/md_blue_A400 #212121 - #1C1C1D + #212121 @color/md_black_1000 @@ -15,6 +22,7 @@ @color/md_black_1000_38 @color/md_black_1000_12 @color/md_black_1000_12 + #B3FFFFFF @color/colorAccentLight @@ -32,12 +40,12 @@ @color/md_white_1000_70 @color/md_white_1000_50 @android:color/transparent - @color/md_white_1000_20- + @color/md_white_1000_20 @color/md_black_1000 @color/md_grey_900 - @color/colorDarkPrimaryDark - @color/colorDarkPrimary + #1C1C1D + @color/md_grey_800 @color/colorDarkPrimaryDark @color/md_blue_A200_50 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f72686b4f3..72fd2e9ed1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,8 @@ Selected: %1$d Backup Source migration + Hide title + Show title Extensions Extension info Help @@ -40,6 +42,7 @@ Last read Last updated Search + Skip manga Global search Select all Mark as read @@ -62,6 +65,7 @@ Sort down Downloaded Next unread + View chapters Start Stop Pause @@ -95,6 +99,8 @@ Restore Open Log in + Back + Forward Deleting… @@ -145,6 +151,9 @@ Dark theme AMOLED theme Dark blue + System default + System default (AMOLED) + System default (Dark blue) Start screen Language System default @@ -198,6 +207,7 @@ Background color White Black + Automatic Default viewer Default Left to right @@ -312,6 +322,7 @@ Title or author… + Updating library Updating category Local Are you sure you want to remove selected manga? @@ -467,6 +478,7 @@ Update progress: %1$d/%2$d New chapters found For %1$d titles + and %1$d more. Failed to update cover Please add the manga to your library before doing this Sync canceled @@ -519,5 +531,7 @@ Common Library Downloader + Updating Library + New Chapters diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 585e0e7ddf..dbd4611730 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -35,13 +35,12 @@ - + @@ -147,6 +146,17 @@ eu.kanade.tachiyomi.widget.FABAnimationUpDown + + - - - - - - - - - - - - - - - - - - - - - - + @color/colorAccentLight + @color/textColorPrimaryLight + @color/textColorSecondaryLight + @color/textColorHintLight + @color/textColorPrimaryDark + @color/textColorSecondaryDark + @color/textColorHintDark + ?colorAccent + @color/dividerLight + @drawable/line_divider_light - + + true + @style/Theme.ActionBar.Light + @style/PreferenceThemeOverlay.v14.Material + @style/Theme.AlertDialog.Light - + + @style/Theme.Widget.NavigationView + @drawable/list_item_selector_light + @drawable/library_item_selector_light + @color/textColorPrimaryLight + @color/dialogLight + @color/iconColorLight + + true + ?colorPrimaryDark + ?colorPrimaryDark + diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000000..f18e1f040a --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5e5f210476..493af30669 100644 --- a/build.gradle +++ b/build.gradle @@ -7,10 +7,10 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' - classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' + classpath 'com.android.tools.build:gradle:3.5.2' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.22.0' classpath 'com.github.zellius:android-shortcut-gradle-plugin:0.1.2' - classpath 'com.google.gms:google-services:3.2.0' + classpath 'com.google.gms:google-services:4.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/gradle.properties b/gradle.properties index 1d3591c8a4..915f0e66f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,6 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bccb59f891..56fac75514 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 25 08:47:56 CEST 2018 +#Sat Oct 26 14:20:49 PDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/j2k-preference/build.gradle b/j2k-preference/build.gradle new file mode 100644 index 0000000000..dba8bf87e0 --- /dev/null +++ b/j2k-preference/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 29 + buildToolsVersion '29.0.2' + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 29 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + encoding = 'UTF-8' + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation 'androidx.preference:preference:1.1.0' + compileOnly 'com.bluelinelabs:conductor:2.1.5' +} diff --git a/j2k-preference/proguard-rules.pro b/j2k-preference/proguard-rules.pro new file mode 100644 index 0000000000..bb141e2a64 --- /dev/null +++ b/j2k-preference/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\len\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/j2k-preference/src/main/AndroidManifest.xml b/j2k-preference/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..32e1b7866a --- /dev/null +++ b/j2k-preference/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/j2k-preference/src/main/java/androidx/preference/EditTextPreferenceDialogController.java b/j2k-preference/src/main/java/androidx/preference/EditTextPreferenceDialogController.java new file mode 100644 index 0000000000..c4933e2a82 --- /dev/null +++ b/j2k-preference/src/main/java/androidx/preference/EditTextPreferenceDialogController.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package androidx.preference; + +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +public class EditTextPreferenceDialogController extends PreferenceDialogController { + + private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogController.text"; + + private EditText mEditText; + + private CharSequence mText; + + public static EditTextPreferenceDialogController newInstance(String key) { + EditTextPreferenceDialogController + controller = new EditTextPreferenceDialogController(); + controller.getArgs().putString(ARG_KEY, key); + return controller; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + mText = getEditTextPreference().getText(); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putCharSequence(SAVE_STATE_TEXT, mText); + } + + @Override + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mText = savedInstanceState.getCharSequence(SAVE_STATE_TEXT); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + mEditText = view.findViewById(android.R.id.edit); + mEditText.requestFocus(); + + if (mEditText == null) { + throw new IllegalStateException("Dialog view must contain an EditText with id" + + " @android:id/edit"); + } + + mEditText.setText(mText); + // Place cursor at the end + mEditText.setSelection(mEditText.getText().length()); + } + + @Override + protected void onDestroyView(@NonNull View view) { + super.onDestroyView(view); + mEditText = null; + } + + private EditTextPreference getEditTextPreference() { + return (EditTextPreference) getPreference(); + } + + /** @hide */ + @RestrictTo(LIBRARY_GROUP) + @Override + protected boolean needInputMethod() { + // We want the input method to show, if possible, when dialog is displayed + return true; + } + + @Override + public void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + String value = mEditText.getText().toString(); + if (getEditTextPreference().callChangeListener(value)) { + getEditTextPreference().setText(value); + } + } + } + +} diff --git a/j2k-preference/src/main/java/androidx/preference/ListPreferenceDialogController.java b/j2k-preference/src/main/java/androidx/preference/ListPreferenceDialogController.java new file mode 100644 index 0000000000..92c1d09066 --- /dev/null +++ b/j2k-preference/src/main/java/androidx/preference/ListPreferenceDialogController.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package androidx.preference; + +import android.content.DialogInterface; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +public class ListPreferenceDialogController extends PreferenceDialogController { + + private static final String SAVE_STATE_INDEX = "ListPreferenceDialogController.index"; + private static final String SAVE_STATE_ENTRIES = "ListPreferenceDialogController.entries"; + private static final String SAVE_STATE_ENTRY_VALUES = + "ListPreferenceDialogController.entryValues"; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + private int mClickedDialogEntryIndex; + private CharSequence[] mEntries; + private CharSequence[] mEntryValues; + + public static ListPreferenceDialogController newInstance(String key) { + ListPreferenceDialogController controller = + new ListPreferenceDialogController(); + controller.getArgs().putString(ARG_KEY, key); + return controller; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + final ListPreference preference = getListPreference(); + + if (preference.getEntries() == null || preference.getEntryValues() == null) { + throw new IllegalStateException( + "ListPreference requires an entries array and an entryValues array."); + } + + mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue()); + mEntries = preference.getEntries(); + mEntryValues = preference.getEntryValues(); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex); + outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries); + outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues); + } + + @Override + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + + mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0); + mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES); + mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES); + } + + private ListPreference getListPreference() { + return (ListPreference) getPreference(); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + + builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mClickedDialogEntryIndex = which; + + /* + * Clicking on an item simulates the positive button + * click, and dismisses the dialog. + */ + ListPreferenceDialogController.this.onClick(dialog, + DialogInterface.BUTTON_POSITIVE); + dialog.dismiss(); + } + }); + + /* + * The typical interaction for list-based dialogs is to have + * click-on-an-item dismiss the dialog instead of the user having to + * press 'Ok'. + */ + builder.setPositiveButton(null, null); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + final ListPreference preference = getListPreference(); + if (positiveResult && mClickedDialogEntryIndex >= 0) { + String value = mEntryValues[mClickedDialogEntryIndex].toString(); + if (preference.callChangeListener(value)) { + preference.setValue(value); + } + } + } + +} diff --git a/j2k-preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogController.java b/j2k-preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogController.java new file mode 100644 index 0000000000..ff72e48437 --- /dev/null +++ b/j2k-preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogController.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package androidx.preference; + +import android.content.DialogInterface; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.MultiSelectListPreference; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +@SuppressWarnings("RestrictedApi") +public class MultiSelectListPreferenceDialogController extends PreferenceDialogController { + + private static final String SAVE_STATE_VALUES = + "MultiSelectListPreferenceDialogController.values"; + private static final String SAVE_STATE_CHANGED = + "MultiSelectListPreferenceDialogController.changed"; + private static final String SAVE_STATE_ENTRIES = + "MultiSelectListPreferenceDialogController.entries"; + private static final String SAVE_STATE_ENTRY_VALUES = + "MultiSelectListPreferenceDialogController.entryValues"; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + Set mNewValues = new HashSet<>(); + @SuppressWarnings("WeakerAccess") /* synthetic access */ + boolean mPreferenceChanged; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + CharSequence[] mEntries; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + CharSequence[] mEntryValues; + + public static MultiSelectListPreferenceDialogController newInstance(String key) { + MultiSelectListPreferenceDialogController controller = + new MultiSelectListPreferenceDialogController(); + controller.getArgs().putString(ARG_KEY, key); + return controller; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + final MultiSelectListPreference preference = getListPreference(); + + if (preference.getEntries() == null || preference.getEntryValues() == null) { + throw new IllegalStateException( + "MultiSelectListPreference requires an entries array and " + + "an entryValues array."); + } + + mNewValues.clear(); + mNewValues.addAll(preference.getValues()); + mPreferenceChanged = false; + mEntries = preference.getEntries(); + mEntryValues = preference.getEntryValues(); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putStringArrayList(SAVE_STATE_VALUES, new ArrayList<>(mNewValues)); + outState.putBoolean(SAVE_STATE_CHANGED, mPreferenceChanged); + outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries); + outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues); + } + + @Override + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mNewValues.clear(); + mNewValues.addAll(savedInstanceState.getStringArrayList(SAVE_STATE_VALUES)); + mPreferenceChanged = savedInstanceState.getBoolean(SAVE_STATE_CHANGED, false); + mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES); + mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES); + } + + private MultiSelectListPreference getListPreference() { + return (MultiSelectListPreference) getPreference(); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + + final int entryCount = mEntryValues.length; + final boolean[] checkedItems = new boolean[entryCount]; + for (int i = 0; i < entryCount; i++) { + checkedItems[i] = mNewValues.contains(mEntryValues[i].toString()); + } + builder.setMultiChoiceItems(mEntries, checkedItems, + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + if (isChecked) { + mPreferenceChanged |= mNewValues.add( + mEntryValues[which].toString()); + } else { + mPreferenceChanged |= mNewValues.remove( + mEntryValues[which].toString()); + } + } + }); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + final MultiSelectListPreference preference = getListPreference(); + if (positiveResult && mPreferenceChanged) { + final Set values = mNewValues; + if (preference.callChangeListener(values)) { + preference.setValues(values); + } + } + mPreferenceChanged = false; + } +} diff --git a/j2k-preference/src/main/java/androidx/preference/PreferenceController.java b/j2k-preference/src/main/java/androidx/preference/PreferenceController.java new file mode 100644 index 0000000000..0352ab8cc7 --- /dev/null +++ b/j2k-preference/src/main/java/androidx/preference/PreferenceController.java @@ -0,0 +1,813 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package androidx.preference; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; +import androidx.annotation.XmlRes; +import androidx.fragment.app.Fragment; +import androidx.preference.MultiSelectListPreference; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.bluelinelabs.conductor.RestoreViewOnCreateController; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +/** + * Shows a hierarchy of {@link Preference} objects as + * lists. These preferences will + * automatically save to {@link android.content.SharedPreferences} as the user interacts with + * them. To retrieve an instance of {@link android.content.SharedPreferences} that the + * preference hierarchy in this fragment will use, call + * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)} + * with a context in the same package as this fragment. + *

+ * Furthermore, the preferences shown will follow the visual style of system + * preferences. It is easy to create a hierarchy of preferences (that can be + * shown on multiple screens) via XML. For these reasons, it is recommended to + * use this fragment (as a superclass) to deal with preferences in applications. + *

+ * A {@link PreferenceScreen} object should be at the top of the preference + * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy + * denote a screen break--that is the preferences contained within subsequent + * {@link PreferenceScreen} should be shown on another screen. The preference + * framework handles this by calling {@link #onNavigateToScreen(PreferenceScreen)}. + *

+ * The preference hierarchy can be formed in multiple ways: + *

  • From an XML file specifying the hierarchy + *
  • From different {@link android.app.Activity Activities} that each specify its own + * preferences in an XML file via {@link android.app.Activity} meta-data + *
  • From an object hierarchy rooted with {@link PreferenceScreen} + *

    + * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The + * root element should be a {@link PreferenceScreen}. Subsequent elements can point + * to actual {@link Preference} subclasses. As mentioned above, subsequent + * {@link PreferenceScreen} in the hierarchy will result in the screen break. + *

    + * To specify an object hierarchy rooted with {@link PreferenceScreen}, use + * {@link #setPreferenceScreen(PreferenceScreen)}. + *

    + * As a convenience, this fragment implements a click listener for any + * preference in the current hierarchy, see + * {@link #onPreferenceTreeClick(Preference)}. + * + *

    + *

    Developer Guides

    + *

    For information about using {@code PreferenceFragment}, + * read the Settings + * guide.

    + *
    + * + * + *

    Sample Code

    + * + *

    The following sample code shows a simple preference fragment that is + * populated from a resource. The resource it loads is:

    + * + * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences} + * + *

    The fragment implementation itself simply populates the preferences + * when created. Note that the preferences framework takes care of loading + * the current values out of the app preferences and writing them when changed:

    + * + * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesCompat.java + * support_fragment_compat} + * + * @see Preference + * @see PreferenceScreen + */ +@SuppressWarnings({"WeakerAccess", "unused", "HandlerLeak", "JavaDoc", "RestrictedApi"}) +public abstract class PreferenceController extends RestoreViewOnCreateController implements + PreferenceManager.OnDisplayPreferenceDialogListener, + DialogPreference.TargetFragment { + + /** + * Fragment argument used to specify the tag of the desired root + * {@link androidx.preference.PreferenceScreen} object. + */ + public static final String ARG_PREFERENCE_ROOT = + "androidx.preference.PreferenceFragmentCompat.PREFERENCE_ROOT"; + + private static final String PREFERENCES_TAG = "android:preferences"; + + private static final String DIALOG_FRAGMENT_TAG = + "androidx.preference.PreferenceFragment.DIALOG"; + + private PreferenceManager mPreferenceManager; + RecyclerView mList; + private boolean mHavePrefs; + private boolean mInitDone; + + private Context mStyledContext; + + private int mLayoutResId = R.layout.preference_list_fragment; + + private DividerDecoration mDividerDecoration = null; + + private static final int MSG_BIND_PREFERENCES = 1; + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + + case MSG_BIND_PREFERENCES: + bindPreferences(); + break; + } + } + }; + + final private Runnable mRequestFocus = new Runnable() { + @Override + public void run() { + mList.focusableViewAvailable(mList); + } + }; + + private Runnable mSelectPreferenceRunnable; + + /** + * Interface that PreferenceFragment's containing activity should + * implement to be able to process preference items that wish to + * switch to a specified fragment. + */ + public interface OnPreferenceStartFragmentCallback { + /** + * Called when the user has clicked on a Preference that has + * a fragment class name associated with it. The implementation + * should instantiate and switch to an instance of the given + * fragment. + * @param caller The fragment requesting navigation. + * @param pref The preference requesting the fragment. + * @return true if the fragment creation has been handled + */ + boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref); + } + + /** + * Interface that PreferenceFragment's containing activity should + * implement to be able to process preference items that wish to + * switch to a new screen of preferences. + */ + public interface OnPreferenceStartScreenCallback { + /** + * Called when the user has clicked on a PreferenceScreen item in order to navigate to a new + * screen of preferences. + * @param caller The fragment requesting navigation. + * @param pref The preference screen to navigate to. + * @return true if the screen navigation has been handled + */ + boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref); + } + + public interface OnPreferenceDisplayDialogCallback { + + /** + * @param caller The fragment containing the preference requesting the dialog. + * @param pref The preference requesting the dialog. + * @return true if the dialog creation has been handled. + */ + boolean onPreferenceDisplayDialog(PreferenceController caller, Preference pref); + } + + /** + * Convenience constructor for use when no arguments are needed. + */ + public PreferenceController() { + super(null); + } + + /** + * Constructor that takes arguments that need to be retained across restarts. + * + * @param args Any arguments that need to be retained. + */ + public PreferenceController(@Nullable Bundle args) { + super(args); + } + + /** + * Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment. + * Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either + * directly or via helper methods such as {@link #addPreferencesFromResource(int)}. + * + * @param savedInstanceState If the fragment is being re-created from + * a previous saved state, this is the state. + * @param rootKey If non-null, this preference fragment should be rooted at the + * {@link androidx.preference.PreferenceScreen} with this key. + */ + public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey); + + @Override + @NonNull + public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, + @Nullable Bundle savedInstanceState) { + mInitDone = false; + mHavePrefs = false; + + final TypedValue tv = new TypedValue(); + getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true); + int theme = tv.resourceId; + if (theme == 0) { + // Fallback to default theme. + theme = R.style.PreferenceThemeOverlay; + } + mStyledContext = new ContextThemeWrapper(getActivity(), theme); + + mPreferenceManager = new PreferenceManager(mStyledContext); + final String rootKey = getArgs().getString(ARG_PREFERENCE_ROOT); + onCreatePreferences(savedInstanceState, rootKey); + + TypedArray a = mStyledContext.obtainStyledAttributes(null, + R.styleable.PreferenceFragmentCompat, + R.attr.preferenceFragmentCompatStyle, + 0); + + mLayoutResId = a.getResourceId(R.styleable.PreferenceFragmentCompat_android_layout, + mLayoutResId); + + mDividerDecoration = new DividerDecoration(); + final Drawable divider = a.getDrawable( + R.styleable.PreferenceFragmentCompat_android_divider); + final int dividerHeight = a.getDimensionPixelSize( + R.styleable.PreferenceFragmentCompat_android_dividerHeight, -1); + final boolean allowDividerAfterLastItem = a.getBoolean( + R.styleable.PreferenceFragmentCompat_allowDividerAfterLastItem, true); + + a.recycle(); + + final LayoutInflater themedInflater = inflater.cloneInContext(mStyledContext); + + final View view = themedInflater.inflate(mLayoutResId, container, false); + + final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER); + if (!(rawListContainer instanceof ViewGroup)) { + throw new RuntimeException("Content has view with id attribute " + + "'android.R.id.list_container' that is not a ViewGroup class"); + } + + final ViewGroup listContainer = (ViewGroup) rawListContainer; + + final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer, + savedInstanceState); + if (listView == null) { + throw new RuntimeException("Could not create RecyclerView"); + } + + mList = listView; + + listView.addItemDecoration(mDividerDecoration); + setDivider(divider); + if (dividerHeight != -1) { + setDividerHeight(dividerHeight); + } + mDividerDecoration.setAllowDividerAfterLastItem(allowDividerAfterLastItem); + + // If mList isn't present in the view hierarchy, add it. mList is automatically inflated + // on an Auto device so don't need to add it. + if (mList.getParent() == null) { + listContainer.addView(mList); + } + mHandler.post(mRequestFocus); + + onViewCreated(view, savedInstanceState); + + return view; + } + + /** + * Sets the drawable that will be drawn between each item in the list. + *

    + * Note: If the drawable does not have an intrinsic + * height, you should also call {@link #setDividerHeight(int)}. + * + * @param divider the drawable to use + * @attr ref R.styleable#PreferenceFragmentCompat_android_divider + */ + public void setDivider(Drawable divider) { + if (mDividerDecoration != null) { + mDividerDecoration.setDivider(divider); + } + } + + /** + * Sets the height of the divider that will be drawn between each item in the list. Calling + * this will override the intrinsic height as set by {@link #setDivider(Drawable)} + * + * @param height The new height of the divider in pixels. + * @attr ref R.styleable#PreferenceFragmentCompat_android_dividerHeight + */ + public void setDividerHeight(int height) { + if (mDividerDecoration != null) { + mDividerDecoration.setDividerHeight(height); + } + } + + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState != null) { + Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); + if (container != null) { + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + if (preferenceScreen != null) { + preferenceScreen.restoreHierarchyState(container); + } + } + } + + if (mHavePrefs) { + bindPreferences(); + if (mSelectPreferenceRunnable != null) { + mSelectPreferenceRunnable.run(); + mSelectPreferenceRunnable = null; + } + } + + mInitDone = true; + } + + @Override + public void onAttach(@NonNull View view) { + super.onAttach(view); + mPreferenceManager.setOnDisplayPreferenceDialogListener(this); + } + + @Override + public void onDetach(@NonNull View view) { + super.onDetach(view); + mPreferenceManager.setOnDisplayPreferenceDialogListener(null); + } + + @Override + public void onDestroyView(@NonNull View view) { + mHandler.removeCallbacks(mRequestFocus); + mHandler.removeMessages(MSG_BIND_PREFERENCES); + if (mHavePrefs) { + unbindPreferences(); + } + mList = null; + mPreferenceManager = null; + mStyledContext = null; + mDividerDecoration = null; + + super.onDestroyView(view); + } + + @Override + protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) { + super.onSaveViewState(view, outState); + + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + if (preferenceScreen != null) { + Bundle container = new Bundle(); + preferenceScreen.saveHierarchyState(container); + outState.putBundle(PREFERENCES_TAG, container); + } + } + + /** + * Returns the {@link PreferenceManager} used by this fragment. + * @return The {@link PreferenceManager}. + */ + public PreferenceManager getPreferenceManager() { + return mPreferenceManager; + } + + /** + * Sets the root of the preference hierarchy that this fragment is showing. + * + * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. + */ + public void setPreferenceScreen(PreferenceScreen preferenceScreen) { + if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { + onUnbindPreferences(); + mHavePrefs = true; + if (mInitDone) { + postBindPreferences(); + } + } + } + + /** + * Gets the root of the preference hierarchy that this fragment is showing. + * + * @return The {@link PreferenceScreen} that is the root of the preference + * hierarchy. + */ + public PreferenceScreen getPreferenceScreen() { + return mPreferenceManager.getPreferenceScreen(); + } + + /** + * Inflates the given XML resource and adds the preference hierarchy to the current + * preference hierarchy. + * + * @param preferencesResId The XML resource ID to inflate. + */ + public void addPreferencesFromResource(@XmlRes int preferencesResId) { + requirePreferenceManager(); + + setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext, + preferencesResId, getPreferenceScreen())); + } + + /** + * Inflates the given XML resource and replaces the current preference hierarchy (if any) with + * the preference hierarchy rooted at {@code key}. + * + * @param preferencesResId The XML resource ID to inflate. + * @param key The preference key of the {@link androidx.preference.PreferenceScreen} + * to use as the root of the preference hierarchy, or null to use the root + * {@link androidx.preference.PreferenceScreen}. + */ + public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) { + requirePreferenceManager(); + + final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(mStyledContext, + preferencesResId, null); + + final Preference root; + if (key != null) { + root = xmlRoot.findPreference(key); + if (!(root instanceof PreferenceScreen)) { + throw new IllegalArgumentException("Preference object with key " + key + + " is not a PreferenceScreen"); + } + } else { + root = xmlRoot; + } + + setPreferenceScreen((PreferenceScreen) root); + } + + /** + * Finds a {@link Preference} based on its key. + * + * @param key The key of the preference to retrieve. + * @return The {@link Preference} with the key, or null. + * @see androidx.preference.PreferenceGroup#findPreference(CharSequence) + */ + @Override + public Preference findPreference(CharSequence key) { + if (mPreferenceManager == null) { + return null; + } + return mPreferenceManager.findPreference(key); + } + + private void requirePreferenceManager() { + if (mPreferenceManager == null) { + throw new RuntimeException("This should be called after super.onCreate."); + } + } + + private void postBindPreferences() { + if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; + mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); + } + + private void bindPreferences() { + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + if (preferenceScreen != null) { + getListView().setAdapter(onCreateAdapter(preferenceScreen)); + preferenceScreen.onAttached(); + } + onBindPreferences(); + } + + private void unbindPreferences() { + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + if (preferenceScreen != null) { + preferenceScreen.onDetached(); + } + onUnbindPreferences(); + } + + /** @hide */ + @RestrictTo(LIBRARY_GROUP) + protected void onBindPreferences() { + } + + /** @hide */ + @RestrictTo(LIBRARY_GROUP) + protected void onUnbindPreferences() { + } + + public final RecyclerView getListView() { + return mList; + } + + /** + * Creates the {@link RecyclerView} used to display the preferences. + * Subclasses may override this to return a customized + * {@link RecyclerView}. + * @param inflater The LayoutInflater object that can be used to inflate the + * {@link RecyclerView}. + * @param parent The parent {@link android.view.View} that the RecyclerView will be attached to. + * This method should not add the view itself, but this can be used to generate + * the LayoutParams of the view. + * @param savedInstanceState If non-null, this view is being re-constructed from a previous + * saved state as given here + * @return A new RecyclerView object to be placed into the view hierarchy + */ + public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, + Bundle savedInstanceState) { + // If device detected is Auto, use Auto's custom layout that contains a custom ViewGroup + // wrapping a RecyclerView + if (mStyledContext.getPackageManager().hasSystemFeature(PackageManager + .FEATURE_AUTOMOTIVE)) { + RecyclerView recyclerView = parent.findViewById(R.id.recycler_view); + if (recyclerView != null) { + return recyclerView; + } + } + RecyclerView recyclerView = (RecyclerView) inflater + .inflate(R.layout.preference_recyclerview, parent, false); + + recyclerView.setLayoutManager(onCreateLayoutManager()); + recyclerView.setAccessibilityDelegateCompat( + new PreferenceRecyclerViewAccessibilityDelegate(recyclerView)); + + return recyclerView; + } + + /** + * Called from {@link #onCreateRecyclerView} to create the + * {@link RecyclerView.LayoutManager} for the created + * {@link RecyclerView}. + * @return A new {@link RecyclerView.LayoutManager} instance. + */ + public RecyclerView.LayoutManager onCreateLayoutManager() { + return new LinearLayoutManager(getActivity()); + } + + /** + * Creates the root adapter. + * + * @param preferenceScreen Preference screen object to create the adapter for. + * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}. + */ + protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { + return new PreferenceGroupAdapter(preferenceScreen); + } + + /** + * Called when a preference in the tree requests to display a dialog. Subclasses should + * override this method to display custom dialogs or to handle dialogs for custom preference + * classes. + * + * @param preference The Preference object requesting the dialog. + */ + @Override + public void onDisplayPreferenceDialog(Preference preference) { + boolean handled = false; + if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) { + handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment()) + .onPreferenceDisplayDialog(this, preference); + } + if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) { + handled = ((OnPreferenceDisplayDialogCallback) getActivity()) + .onPreferenceDisplayDialog(this, preference); + } + + if (handled) { + return; + } + + // check if dialog is already showing + if (getRouter().getControllerWithTag(DIALOG_FRAGMENT_TAG) != null) { + return; + } + + final PreferenceDialogController f; + if (preference instanceof EditTextPreference) { + f = EditTextPreferenceDialogController.newInstance(preference.getKey()); + } else if (preference instanceof ListPreference) { + f = ListPreferenceDialogController.newInstance(preference.getKey()); + } else if (preference instanceof MultiSelectListPreference) { + f = MultiSelectListPreferenceDialogController.newInstance(preference.getKey()); + } else { + throw new IllegalArgumentException("Tried to display dialog for unknown " + + "preference type. Did you forget to override onDisplayPreferenceDialog()?"); + } + f.setTargetController(this); + f.showDialog(getRouter(), DIALOG_FRAGMENT_TAG); + } + + /** + * Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib. + * @return Fragment to possibly use as a callback + * @hide + */ + @RestrictTo(LIBRARY_GROUP) + public Fragment getCallbackFragment() { + return null; + } + + public void scrollToPreference(final String key) { + scrollToPreferenceInternal(null, key); + } + + public void scrollToPreference(final Preference preference) { + scrollToPreferenceInternal(preference, null); + } + + private void scrollToPreferenceInternal(final Preference preference, final String key) { + final Runnable r = new Runnable() { + @Override + public void run() { + final RecyclerView.Adapter adapter = mList.getAdapter(); + if (!(adapter instanceof + PreferenceGroup.PreferencePositionCallback)) { + if (adapter != null) { + throw new IllegalStateException("Adapter must implement " + + "PreferencePositionCallback"); + } else { + // Adapter was set to null, so don't scroll I guess? + return; + } + } + final int position; + if (preference != null) { + position = ((PreferenceGroup.PreferencePositionCallback) adapter) + .getPreferenceAdapterPosition(preference); + } else { + position = ((PreferenceGroup.PreferencePositionCallback) adapter) + .getPreferenceAdapterPosition(key); + } + if (position != RecyclerView.NO_POSITION) { + mList.scrollToPosition(position); + } else { + // Item not found, wait for an update and try again + adapter.registerAdapterDataObserver( + new ScrollToPreferenceObserver(adapter, mList, preference, key)); + } + } + }; + if (mList == null) { + mSelectPreferenceRunnable = r; + } else { + r.run(); + } + } + + private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver { + private final RecyclerView.Adapter mAdapter; + private final RecyclerView mList; + private final Preference mPreference; + private final String mKey; + + public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list, + Preference preference, String key) { + mAdapter = adapter; + mList = list; + mPreference = preference; + mKey = key; + } + + private void scrollToPreference() { + mAdapter.unregisterAdapterDataObserver(this); + final int position; + if (mPreference != null) { + position = ((PreferenceGroup.PreferencePositionCallback) mAdapter) + .getPreferenceAdapterPosition(mPreference); + } else { + position = ((PreferenceGroup.PreferencePositionCallback) mAdapter) + .getPreferenceAdapterPosition(mKey); + } + if (position != RecyclerView.NO_POSITION) { + mList.scrollToPosition(position); + } + } + + @Override + public void onChanged() { + scrollToPreference(); + } + + @Override + public void onItemRangeChanged(int positionStart, int itemCount) { + scrollToPreference(); + } + + @Override + public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { + scrollToPreference(); + } + + @Override + public void onItemRangeInserted(int positionStart, int itemCount) { + scrollToPreference(); + } + + @Override + public void onItemRangeRemoved(int positionStart, int itemCount) { + scrollToPreference(); + } + + @Override + public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + scrollToPreference(); + } + } + + private class DividerDecoration extends RecyclerView.ItemDecoration { + + private Drawable mDivider; + private int mDividerHeight; + private boolean mAllowDividerAfterLastItem = true; + + DividerDecoration() { + } + + @Override + public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (mDivider == null) { + return; + } + final int childCount = parent.getChildCount(); + final int width = parent.getWidth(); + for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { + final View view = parent.getChildAt(childViewIndex); + if (shouldDrawDividerBelow(view, parent)) { + int top = (int) view.getY() + view.getHeight(); + mDivider.setBounds(0, top, width, top + mDividerHeight); + mDivider.draw(c); + } + } + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + if (shouldDrawDividerBelow(view, parent)) { + outRect.bottom = mDividerHeight; + } + } + + private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { + final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); + final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder + && ((PreferenceViewHolder) holder).isDividerAllowedBelow(); + if (!dividerAllowedBelow) { + return false; + } + boolean nextAllowed = mAllowDividerAfterLastItem; + int index = parent.indexOfChild(view); + if (index < parent.getChildCount() - 1) { + final View nextView = parent.getChildAt(index + 1); + final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView); + nextAllowed = nextHolder instanceof PreferenceViewHolder + && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove(); + } + return nextAllowed; + } + + public void setDivider(Drawable divider) { + if (divider != null) { + mDividerHeight = divider.getIntrinsicHeight(); + } else { + mDividerHeight = 0; + } + mDivider = divider; + mList.invalidateItemDecorations(); + } + + public void setDividerHeight(int dividerHeight) { + mDividerHeight = dividerHeight; + mList.invalidateItemDecorations(); + } + + public void setAllowDividerAfterLastItem(boolean allowDividerAfterLastItem) { + mAllowDividerAfterLastItem = allowDividerAfterLastItem; + } + } +} diff --git a/j2k-preference/src/main/java/androidx/preference/PreferenceDialogController.java b/j2k-preference/src/main/java/androidx/preference/PreferenceDialogController.java new file mode 100644 index 0000000000..c50b606d91 --- /dev/null +++ b/j2k-preference/src/main/java/androidx/preference/PreferenceDialogController.java @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package androidx.preference; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.*; +import android.widget.TextView; +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; +import androidx.appcompat.app.AlertDialog; +import com.bluelinelabs.conductor.Controller; +import com.bluelinelabs.conductor.RestoreViewOnCreateController; +import com.bluelinelabs.conductor.Router; +import com.bluelinelabs.conductor.RouterTransaction; +import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +/** + * Abstract base class which presents a dialog associated with a + * {@link androidx.preference.DialogPreference}. Since the preference object may + * not be available during fragment re-creation, the necessary information for displaying the dialog + * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved + * instance state. Custom subclasses should also follow this pattern. + */ +public abstract class PreferenceDialogController extends RestoreViewOnCreateController implements + DialogInterface.OnClickListener { + + protected static final String ARG_KEY = "key"; + + private static final String SAVE_DIALOG_STATE_TAG = "android:savedDialogState"; + private static final String SAVE_STATE_TITLE = "PreferenceDialogController.title"; + private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogController.positiveText"; + private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogController.negativeText"; + private static final String SAVE_STATE_MESSAGE = "PreferenceDialogController.message"; + private static final String SAVE_STATE_LAYOUT = "PreferenceDialogController.layout"; + private static final String SAVE_STATE_ICON = "PreferenceDialogController.icon"; + + private DialogPreference mPreference; + + private CharSequence mDialogTitle; + private CharSequence mPositiveButtonText; + private CharSequence mNegativeButtonText; + private CharSequence mDialogMessage; + private @LayoutRes int mDialogLayoutRes; + + private BitmapDrawable mDialogIcon; + + /** Which button was clicked. */ + private int mWhichButtonClicked; + + private Dialog dialog; + private boolean dismissed; + + @NonNull + @Override + final protected View onCreateView(@NonNull LayoutInflater inflater, + @NonNull ViewGroup container, + @Nullable Bundle savedViewState) { + + onCreate(savedViewState); + + dialog = onCreateDialog(savedViewState); + //noinspection ConstantConditions + dialog.setOwnerActivity(getActivity()); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + PreferenceDialogController.this.dismissDialog(); + } + }); + if (savedViewState != null) { + Bundle dialogState = savedViewState.getBundle(SAVE_DIALOG_STATE_TAG); + if (dialogState != null) { + dialog.onRestoreInstanceState(dialogState); + } + } + return new View(getActivity());//stub view + } + + public void onCreate(Bundle savedInstanceState) { + final Controller rawController = getTargetController(); + if (!(rawController instanceof DialogPreference.TargetFragment)) { + throw new IllegalStateException("Target controller must implement TargetFragment" + + " interface"); + } + + final DialogPreference.TargetFragment controller = + (DialogPreference.TargetFragment) rawController; + + final String key = getArgs().getString(ARG_KEY); + if (savedInstanceState == null) { + mPreference = (DialogPreference) controller.findPreference(key); + mDialogTitle = mPreference.getDialogTitle(); + mPositiveButtonText = mPreference.getPositiveButtonText(); + mNegativeButtonText = mPreference.getNegativeButtonText(); + mDialogMessage = mPreference.getDialogMessage(); + mDialogLayoutRes = mPreference.getDialogLayoutResource(); + + final Drawable icon = mPreference.getDialogIcon(); + if (icon == null || icon instanceof BitmapDrawable) { + mDialogIcon = (BitmapDrawable) icon; + } else { + final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), + icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + icon.draw(canvas); + mDialogIcon = new BitmapDrawable(getResources(), bitmap); + } + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle); + outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText); + outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText); + outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage); + outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes); + if (mDialogIcon != null) { + outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap()); + } + } + + @Override + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + + mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE); + mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT); + mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT); + mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE); + mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0); + final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON); + if (bitmap != null) { + mDialogIcon = new BitmapDrawable(getResources(), bitmap); + } + } + + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; + + final AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setTitle(mDialogTitle) + .setIcon(mDialogIcon) + .setPositiveButton(mPositiveButtonText, this) + .setNegativeButton(mNegativeButtonText, this); + + View contentView = onCreateDialogView(context); + if (contentView != null) { + onBindDialogView(contentView); + builder.setView(contentView); + } else { + builder.setMessage(mDialogMessage); + } + + onPrepareDialogBuilder(builder); + + // Create the dialog + final Dialog dialog = builder.create(); + if (needInputMethod()) { + requestInputMethod(dialog); + } + + return dialog; + } + + @Override + protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) { + super.onSaveViewState(view, outState); + Bundle dialogState = dialog.onSaveInstanceState(); + outState.putBundle(SAVE_DIALOG_STATE_TAG, dialogState); + } + + @Override + protected void onAttach(@NonNull View view) { + super.onAttach(view); + dialog.show(); + } + + @Override + protected void onDetach(@NonNull View view) { + super.onDetach(view); + dialog.hide(); + } + + @Override + protected void onDestroyView(@NonNull View view) { + super.onDestroyView(view); + dialog.setOnDismissListener(null); + dialog.dismiss(); + dialog = null; + mPreference = null; + } + + /** + * Display the dialog, create a transaction and pushing the controller. + * + * @param router The router on which the transaction will be applied + */ + public void showDialog(@NonNull Router router) { + showDialog(router, null); + } + + /** + * Display the dialog, create a transaction and pushing the controller. + * + * @param router The router on which the transaction will be applied + * @param tag The tag for this controller + */ + public void showDialog(@NonNull Router router, @Nullable String tag) { + dismissed = false; + router.pushController(RouterTransaction.with(this) + .pushChangeHandler(new SimpleSwapChangeHandler(false)) + .popChangeHandler(new SimpleSwapChangeHandler(false)) + .tag(tag)); + } + + /** + * Dismiss the dialog and pop this controller + */ + public void dismissDialog() { + if (dismissed) { + return; + } + onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE); + getRouter().popController(this); + dismissed = true; + } + + @Nullable + protected Dialog getDialog() { + return dialog; + } + + /** + * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has + * been called on the {@link PreferenceFragmentCompat} which launched this dialog. + * + * @return The {@link DialogPreference} associated with this + * dialog. + */ + public DialogPreference getPreference() { + if (mPreference == null) { + final String key = getArgs().getString(ARG_KEY); + final DialogPreference.TargetFragment controller = + (DialogPreference.TargetFragment) getTargetController(); + mPreference = (DialogPreference) controller.findPreference(key); + } + return mPreference; + } + + /** + * Prepares the dialog builder to be shown when the preference is clicked. + * Use this to set custom properties on the dialog. + *

    + * Do not {@link AlertDialog.Builder#create()} or + * {@link AlertDialog.Builder#show()}. + */ + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + } + + /** + * Returns whether the preference needs to display a soft input method when the dialog + * is displayed. Default is false. Subclasses should override this method if they need + * the soft input method brought up automatically. + * + * @hide + */ + @RestrictTo(LIBRARY_GROUP) + protected boolean needInputMethod() { + return false; + } + + /** + * Sets the required flags on the dialog window to enable input method window to show up. + */ + private void requestInputMethod(Dialog dialog) { + Window window = dialog.getWindow(); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + } + + /** + * Creates the content view for the dialog (if a custom content view is + * required). By default, it inflates the dialog layout resource if it is + * set. + * + * @return The content View for the dialog. + * @see DialogPreference#setLayoutResource(int) + */ + protected View onCreateDialogView(Context context) { + final int resId = mDialogLayoutRes; + if (resId == 0) { + return null; + } + + LayoutInflater inflater = LayoutInflater.from(context); + return inflater.inflate(resId, null); + } + + /** + * Binds views in the content View of the dialog to data. + *

    + * Make sure to call through to the superclass implementation. + * + * @param view The content View of the dialog, if it is custom. + */ + protected void onBindDialogView(View view) { + View dialogMessageView = view.findViewById(android.R.id.message); + + if (dialogMessageView != null) { + final CharSequence message = mDialogMessage; + int newVisibility = View.GONE; + + if (!TextUtils.isEmpty(message)) { + if (dialogMessageView instanceof TextView) { + ((TextView) dialogMessageView).setText(message); + } + + newVisibility = View.VISIBLE; + } + + if (dialogMessageView.getVisibility() != newVisibility) { + dialogMessageView.setVisibility(newVisibility); + } + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + mWhichButtonClicked = which; + } + + public abstract void onDialogClosed(boolean positiveResult); +} diff --git a/settings.gradle b/settings.gradle index e7b4def49c..f340a35e17 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app', ':j2k-preference'