diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
old mode 100644
new mode 100755
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
old mode 100644
new mode 100755
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
index af291a578..2b4add534
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,6 @@
.idea/
*iml
*.iml
-*/build
\ No newline at end of file
+*/build
+/mainframer.sh
+*.apk
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
old mode 100644
new mode 100755
diff --git a/LICENSE b/LICENSE
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index 6c4ed5e34..9af91415f
--- a/README.md
+++ b/README.md
@@ -1,24 +1,52 @@
-| Build | Download | F-Droid |
-|-------|----------|-------------|
-| [](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-dev-versions) |
+
+
-## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md)
+TachiyomiEH is a free and open source E-Hentai, ExHentai and PervEden galleries reader for Android.
-**Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened issues.**
+TachiyomiEH is a fork of the [original Tachiyomi app](https://github.com/inorichi/tachiyomi).
+### E-Hentai Thread
+[https://forums.e-hentai.org/index.php?showtopic=185421](https://forums.e-hentai.org/index.php?showtopic=185421)
-Tachiyomi is a free and open source manga reader for Android.
-
-Keep in mind it's still a beta, so expect it to crash sometimes.
+# Download
+[](https://github.com/NerdNumber9/TachiyomiEH/releases)
# Features
* Online and offline reading
* Configurable reader with multiple viewers and settings
* MyAnimeList support
-* Resume from the next unread chapter
+* Track your reading position
* Chapter filtering
* Schedule searching for updates
* Categories to organize your library
+* Log into ExHentai
+* Read both NSFW and SFW manga/doujinshi
+* Full offline tag/namespace searching support
+* Batch import galleries
+* Automatically open E-Hentai/ExHentai links
+* Lock the app with a PIN code
+
+### Built-in manga sources
+##### SFW
+* Batoto
+* Mangahere
+* Mangafox
+* Kissmanga
+* Readmanga
+* Mintmanga
+* Mangachan
+* Readmangatoday
+* Mangasee
+* Wiemanga
+
+##### NSFW
+* E-Hentai
+* ExHentai
+* PervEden
+* nhentai
+
+TachiyomiEH is fully compatible with Tachiyomi source extensions.
+Backups from Tachiyomi are also compatible with TachiyomiEH (and vice versa).
## License
diff --git a/app/.gitignore b/app/.gitignore
old mode 100644
new mode 100755
index 90de2b9c8..012bccc6a
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1,4 +1,5 @@
/build
*iml
*.iml
-custom.gradle
\ No newline at end of file
+custom.gradle
+google-services.json
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
old mode 100644
new mode 100755
index f8db49c32..4368b0753
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -33,18 +33,22 @@ android {
buildToolsVersion "25.0.2"
publishNonDefault true
+ dexOptions {
+ javaMaxHeapSize "4g"
+ }
+
defaultConfig {
- applicationId "eu.kanade.tachiyomi"
+ applicationId "eu.kanade.tachiyomi.eh2"
minSdkVersion 16
targetSdkVersion 25
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- versionCode 22
- versionName "0.5.2"
+ versionCode 5003
+ versionName "v5.0.3-EH"
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
- buildConfigField "boolean", "INCLUDE_UPDATER", "false"
+ buildConfigField "boolean", "INCLUDE_UPDATER", "true"
vectorDrawables.useSupportLibrary = true
@@ -198,15 +202,31 @@ dependencies {
compile 'me.zhanghai.android.systemuihelper:library:1.0.0'
compile 'de.hdodenhof:circleimageview:2.1.0'
+ //Firebase (EH)
+ final firebase_version = '10.0.1'
+ releaseCompile "com.google.firebase:firebase-core:$firebase_version"
+ releaseCompile "com.google.firebase:firebase-messaging:$firebase_version"
+ releaseCompile "com.google.firebase:firebase-crash:$firebase_version"
+
+ //SnappyDB (EH)
+ compile 'io.paperdb:paperdb:2.0'
+
+ //JVE (Regex) (EH)
+ compile 'ru.lanwen.verbalregex:java-verbal-expressions:1.4'
+
+ //Pin lock view
+ compile 'com.andrognito.pinlockview:pinlockview:1.0.1'
+
// Tests
- testCompile 'junit:junit:4.12'
+ //Paper DB screws up tests
+ /*testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:1.7.1'
testCompile 'org.mockito:mockito-core:1.10.19'
final robolectric_version = '3.1.4'
testCompile "org.robolectric:robolectric:$robolectric_version"
testCompile "org.robolectric:shadows-multidex:$robolectric_version"
- testCompile "org.robolectric:shadows-play-services:$robolectric_version"
+ testCompile "org.robolectric:shadows-play-services:$robolectric_version"*/
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
@@ -270,4 +290,6 @@ afterEvaluate {
}
}
}
-}
\ No newline at end of file
+}
+//Firebase (EH)
+apply plugin: 'com.google.gms.google-services'
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
old mode 100644
new mode 100755
index f07a94d09..ffe90a1ee
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -96,4 +96,11 @@
-dontwarn org.yaml.snakeyaml.**
# Duktape
--keep class com.squareup.duktape.** { *; }
\ No newline at end of file
+-keep class com.squareup.duktape.** { *; }
+
+# [EH]
+-keep class exh.** { *; }
+
+# Keep google stuff
+-dontwarn com.google.android.gms.**
+-dontwarn com.google.firebase.**
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
old mode 100644
new mode 100755
index 21627600d..fef49b7ab
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,6 +13,9 @@
android:name="android.permission.READ_PHONE_STATE"
tools:node="remove" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/fonts/PTSans-Narrow.ttf b/app/src/main/assets/fonts/PTSans-Narrow.ttf
old mode 100644
new mode 100755
diff --git a/app/src/main/assets/fonts/PTSans-NarrowBold.ttf b/app/src/main/assets/fonts/PTSans-NarrowBold.ttf
old mode 100644
new mode 100755
diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt
old mode 100644
new mode 100755
index 9fd73b878..af89227b6
--- a/app/src/main/java/eu/kanade/tachiyomi/App.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt
@@ -9,20 +9,12 @@ import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
import eu.kanade.tachiyomi.util.LocaleHelper
-import org.acra.ACRA
-import org.acra.annotation.ReportsCrashes
+import io.paperdb.Paper
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.InjektScope
import uy.kohesive.injekt.registry.default.DefaultRegistrar
-@ReportsCrashes(
- formUri = "http://tachiyomi.kanade.eu/crash_report",
- reportType = org.acra.sender.HttpSender.Type.JSON,
- httpMethod = org.acra.sender.HttpSender.Method.PUT,
- buildConfigClass = BuildConfig::class,
- excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*")
-)
open class App : Application() {
override fun onCreate() {
@@ -32,8 +24,8 @@ open class App : Application() {
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
- setupAcra()
setupJobManager()
+ Paper.init(this) //Setup metadata DB (EH)
LocaleHelper.updateConfiguration(this, resources.configuration)
}
@@ -50,10 +42,6 @@ open class App : Application() {
LocaleHelper.updateConfiguration(this, newConfig, true)
}
- protected open fun setupAcra() {
- ACRA.init(this)
- }
-
protected open fun setupJobManager() {
JobManager.create(this).addJobCreator { tag ->
when (tag) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/Constants.kt b/app/src/main/java/eu/kanade/tachiyomi/Constants.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenHelper.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaFileFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaFileFetcher.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
index de56700f4..0d6568058
--- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
@@ -9,6 +9,7 @@ import com.f2prateek.rx.preferences.RxSharedPreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.Source
+import exh.ui.migration.MigrationStatus
import java.io.File
fun Preference.getOrDefault(): T = get() ?: defaultValue()!!
@@ -88,7 +89,7 @@ class PreferencesHelper(val context: Context) {
fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false)
- fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("en"))
+ fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("all"))
fun sourceUsername(source: Source) = prefs.getString(keys.sourceUsername(source.id), "")
@@ -160,4 +161,38 @@ class PreferencesHelper(val context: Context) {
fun defaultCategory() = prefs.getInt(keys.defaultCategory, -1)
+ //EH
+ fun enableExhentai() = rxPrefs.getBoolean("enable_exhentai", false)
+
+ fun secureEXH() = rxPrefs.getBoolean("secure_exh", true)
+
+ fun imageQuality() = rxPrefs.getString("ehentai_quality", "auto")
+
+ fun useHentaiAtHome() = rxPrefs.getBoolean("enable_hah", true)
+
+ fun useJapaneseTitle() = rxPrefs.getBoolean("use_jp_title", false)
+
+ fun ehSearchSize() = rxPrefs.getString("ex_search_size", "rc_0")
+
+ fun thumbnailRows() = rxPrefs.getString("ex_thumb_rows", "tr_2")
+
+ fun migrateLibraryAsked() = rxPrefs.getBoolean("ex_migrate_library", false)
+
+ fun migrationStatus() = rxPrefs.getInteger("migration_status", MigrationStatus.NOT_INITIALIZED)
+
+ fun hasPerformedURLMigration() = rxPrefs.getBoolean("performed_url_migration", false)
+
+ fun hasPerformedSourceMigration() = rxPrefs.getBoolean("performed_source_migration", false)
+
+ //EH Cookies
+ fun memberIdVal() = rxPrefs.getString("eh_ipb_member_id", null)
+ fun passHashVal() = rxPrefs.getString("eh_ipb_pass_hash", null)
+ fun igneousVal() = rxPrefs.getString("eh_igneous", null)
+
+ //Lock
+ fun lockHash() = rxPrefs.getString("lock_hash", null)
+
+ fun lockSalt() = rxPrefs.getString("lock_salt", null)
+
+ fun lockLength() = rxPrefs.getInteger("lock_length", -1)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistInterceptor.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/OAuth.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubRelease.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubRelease.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt
old mode 100644
new mode 100755
index 42ff97324..5e0aa932e
--- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt
@@ -23,7 +23,7 @@ interface GithubService {
}
}
- @GET("/repos/inorichi/tachiyomi/releases/latest")
+ @GET("/repos/NerdNumber9/tachiyomi/releases/latest")
fun getLatestVersion(): Observable
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt
old mode 100644
new mode 100755
index 8d6210845..c8a029acc
--- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt
@@ -12,7 +12,7 @@ class GithubUpdateChecker() {
*/
fun checkForUpdate(): Observable {
return service.getLatestVersion().map { release ->
- val newVersion = release.version.replace("[^\\d.]".toRegex(), "")
+ val newVersion = release.version
// Check if latest version is different from current version
if (newVersion != BuildConfig.VERSION_NAME) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerJob.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderReceiver.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieJar.kt b/app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieJar.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieStore.kt b/app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieStore.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt b/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt
old mode 100644
new mode 100755
index 0b31a27a3..497d43b8f
--- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt
@@ -7,24 +7,48 @@ import android.content.pm.PackageManager
import android.os.Environment
import dalvik.system.PathClassLoader
import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.source.online.all.EHentai
+import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.YamlHttpSource
+import eu.kanade.tachiyomi.source.online.all.NHentai
+import eu.kanade.tachiyomi.source.online.all.PervEden
import eu.kanade.tachiyomi.source.online.english.*
import eu.kanade.tachiyomi.source.online.german.WieManga
import eu.kanade.tachiyomi.source.online.russian.Mangachan
import eu.kanade.tachiyomi.source.online.russian.Mintmanga
import eu.kanade.tachiyomi.source.online.russian.Readmanga
import eu.kanade.tachiyomi.util.hasPermission
+import exh.*
import org.yaml.snakeyaml.Yaml
+import rx.Observable
import timber.log.Timber
+import uy.kohesive.injekt.injectLazy
import java.io.File
open class SourceManager(private val context: Context) {
+ private val prefs: PreferencesHelper by injectLazy()
+
private val sourcesMap = mutableMapOf()
init {
- createSources()
+ //Recreate sources when they change
+ val prefEntries = arrayOf(
+ prefs.enableExhentai(),
+ prefs.imageQuality(),
+ prefs.useHentaiAtHome(),
+ prefs.useJapaneseTitle(),
+ prefs.ehSearchSize(),
+ prefs.thumbnailRows()
+ ).map { it.asObservable() }
+
+ Observable.merge(prefEntries).skip(prefEntries.size - 1).subscribe {
+ sourcesMap.clear()
+ createSources()
+ }
}
open fun get(sourceKey: Long): Source? {
@@ -39,6 +63,8 @@ open class SourceManager(private val context: Context) {
createExtensionSources().forEach { registerSource(it) }
createYamlSources().forEach { registerSource(it) }
createInternalSources().forEach { registerSource(it) }
+ //EH
+ createEHSources().forEach { registerSource(it) }
}
private fun registerSource(source: Source, overwrite: Boolean = false) {
@@ -61,6 +87,21 @@ open class SourceManager(private val context: Context) {
WieManga()
)
+ private fun createEHSources(): List {
+ val exSrcs = mutableListOf(
+ EHentai(EH_SOURCE_ID, false, context),
+ EHentaiMetadata(EH_METADATA_SOURCE_ID, false, context)
+ )
+ if(prefs.enableExhentai().getOrDefault()) {
+ exSrcs += EHentai(EXH_SOURCE_ID, true, context)
+ exSrcs += EHentaiMetadata(EXH_METADATA_SOURCE_ID, true, context)
+ }
+ exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, "en")
+ exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, "it")
+ exSrcs += NHentai(context)
+ return exSrcs
+ }
+
private fun createYamlSources(): List {
val sources = mutableListOf()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt
old mode 100644
new mode 100755
index 16a76b96b..618684d11
--- a/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt
@@ -1,47 +1,47 @@
-package eu.kanade.tachiyomi.source.model
-
-import android.net.Uri
-import eu.kanade.tachiyomi.network.ProgressListener
-import eu.kanade.tachiyomi.ui.reader.ReaderChapter
-import rx.subjects.Subject
-
-class Page(
- val index: Int,
- val url: String = "",
- var imageUrl: String? = null,
- @Transient var uri: Uri? = null
-) : ProgressListener {
-
- val number: Int
- get() = index + 1
-
- @Transient lateinit var chapter: ReaderChapter
-
- @Transient @Volatile var status: Int = 0
- set(value) {
- field = value
- statusSubject?.onNext(value)
- }
-
- @Transient @Volatile var progress: Int = 0
-
- @Transient private var statusSubject: Subject? = null
-
- override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
- progress = (100 * bytesRead / contentLength).toInt()
- }
-
- fun setStatusSubject(subject: Subject?) {
- this.statusSubject = subject
- }
-
- companion object {
-
- const val QUEUE = 0
- const val LOAD_PAGE = 1
- const val DOWNLOAD_IMAGE = 2
- const val READY = 3
- const val ERROR = 4
- }
-
-}
+package eu.kanade.tachiyomi.source.model
+
+import android.net.Uri
+import eu.kanade.tachiyomi.network.ProgressListener
+import eu.kanade.tachiyomi.ui.reader.ReaderChapter
+import rx.subjects.Subject
+
+class Page(
+ val index: Int,
+ var url: String = "",
+ var imageUrl: String? = null,
+ @Transient var uri: Uri? = null
+) : ProgressListener {
+
+ val number: Int
+ get() = index + 1
+
+ @Transient lateinit var chapter: ReaderChapter
+
+ @Transient @Volatile var status: Int = 0
+ set(value) {
+ field = value
+ statusSubject?.onNext(value)
+ }
+
+ @Transient @Volatile var progress: Int = 0
+
+ @Transient private var statusSubject: Subject? = null
+
+ override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
+ progress = (100 * bytesRead / contentLength).toInt()
+ }
+
+ fun setStatusSubject(subject: Subject?) {
+ this.statusSubject = subject
+ }
+
+ companion object {
+
+ const val QUEUE = 0
+ const val LOAD_PAGE = 1
+ const val DOWNLOAD_IMAGE = 2
+ const val READY = 3
+ const val ERROR = 4
+ }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/LoginSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/LoginSource.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/YamlHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/YamlHttpSource.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/YamlHttpSourceMappings.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/YamlHttpSourceMappings.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt
new file mode 100755
index 000000000..3c7080380
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt
@@ -0,0 +1,476 @@
+package eu.kanade.tachiyomi.source.online.all
+
+import android.content.Context
+import android.net.Uri
+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.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.source.model.*
+import eu.kanade.tachiyomi.source.online.HttpSource
+import eu.kanade.tachiyomi.util.asJsoup
+import exh.metadata.*
+import exh.metadata.models.ExGalleryMetadata
+import exh.metadata.models.Tag
+import okhttp3.Response
+import org.jsoup.nodes.Element
+import rx.Observable
+import uy.kohesive.injekt.injectLazy
+import java.net.URLEncoder
+import java.util.*
+import exh.ui.login.LoginActivity
+import exh.util.UriFilter
+import exh.util.UriGroup
+import okhttp3.CacheControl
+import okhttp3.Headers
+import okhttp3.Request
+import org.jsoup.nodes.Document
+
+class EHentai(override val id: Long,
+ val exh: Boolean,
+ val context: Context) : HttpSource() {
+
+ val schema: String
+ get() = if(prefs.secureEXH().getOrDefault())
+ "https"
+ else
+ "http"
+
+ override val baseUrl: String
+ get() = if(exh)
+ "$schema://exhentai.org"
+ else
+ "$schema://e-hentai.org"
+
+ override val lang = "all"
+ override val supportsLatest = true
+
+ val prefs: PreferencesHelper by injectLazy()
+
+ val metadataHelper = MetadataHelper()
+
+ /**
+ * Gallery list entry
+ */
+ data class ParsedManga(val fav: String?, val manga: Manga)
+
+ fun extendedGenericMangaParse(doc: Document)
+ = with(doc) {
+ //Parse mangas
+ val parsedMangas = select(".gtr0,.gtr1").map {
+ ParsedManga(
+ fav = it.select(".itd .it3 > .i[id]").first()?.attr("title"),
+ manga = Manga.create(id).apply {
+ //Get title
+ it.select(".itd .it5 a").first()?.apply {
+ title = text()
+ setUrlWithoutDomain(addParam(attr("href"), "nw", "always"))
+ }
+ //Get image
+ it.select(".itd .it2").first()?.apply {
+ children().first()?.let {
+ thumbnail_url = it.attr("src")
+ } ?: let {
+ text().split("~").apply {
+ thumbnail_url = "http://${this[1]}/${this[2]}"
+ }
+ }
+ }
+ })
+
+ }
+ //Add to page if required
+ val hasNextPage = select("a[onclick=return false]").last()?.let {
+ it.text() == ">"
+ } ?: false
+ Pair(parsedMangas, hasNextPage)
+ }
+
+ /**
+ * Parse a list of galleries
+ */
+ fun genericMangaParse(response: Response)
+ = extendedGenericMangaParse(response.asJsoup()).let {
+ MangasPage(it.first.map { it.manga }, it.second)
+ }
+
+ override fun fetchChapterList(manga: SManga): Observable>
+ = Observable.just(listOf(SChapter.create().apply {
+ url = manga.url
+ name = "Chapter"
+ chapter_number = 1f
+ }))
+
+ override fun fetchPageList(chapter: SChapter)
+ = fetchChapterPage(chapter, "$baseUrl/${chapter.url}").map {
+ it.mapIndexed { i, s ->
+ Page(i, s)
+ }
+ }!!
+
+ private fun fetchChapterPage(chapter: SChapter, np: String,
+ pastUrls: List = emptyList()): Observable> {
+ val urls = ArrayList(pastUrls)
+ return chapterPageCall(np).flatMap {
+ val jsoup = it.asJsoup()
+ urls += parseChapterPage(jsoup)
+ val nextUrl = nextPageUrl(jsoup)
+ if(nextUrl != null) {
+ fetchChapterPage(chapter, nextUrl, urls)
+ } else {
+ Observable.just(urls)
+ }
+ }
+ }
+ private fun parseChapterPage(response: Element)
+ = with(response) {
+ select(".gdtm a").map {
+ Pair(it.child(0).attr("alt").toInt(), it.attr("href"))
+ }.sortedBy(Pair::first).map { it.second }
+ }
+ private fun chapterPageCall(np: String) = client.newCall(chapterPageRequest(np)).asObservableSuccess()
+ private fun chapterPageRequest(np: String) = exGet(np, null, headers)
+
+ private fun nextPageUrl(element: Element): String?
+ = element.select("a[onclick=return false]").last()?.let {
+ return if (it.text() == ">") it.attr("href") else null
+ }
+
+ override fun popularMangaRequest(page: Int) = if(exh)
+ latestUpdatesRequest(page)
+ else
+ exGet("$baseUrl/toplist.php?tl=15", page)
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
+ uri.appendQueryParameter("f_search", query)
+ filters.forEach {
+ if(it is UriFilter) it.addToUri(uri)
+ }
+ return exGet(uri.toString(), page)
+ }
+
+ override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page)
+
+ override fun popularMangaParse(response: Response) = genericMangaParse(response)
+ override fun searchMangaParse(response: Response) = genericMangaParse(response)
+ override fun latestUpdatesParse(response: Response) = genericMangaParse(response)
+
+ fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true)
+ = GET(page?.let {
+ addParam(url, "page", Integer.toString(page - 1))
+ } ?: url, additionalHeaders?.let {
+ val headers = headers.newBuilder()
+ it.toMultimap().forEach { t, u ->
+ u.forEach {
+ headers.add(t, it)
+ }
+ }
+ headers.build()
+ } ?: headers).let {
+ if(!cache)
+ it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
+ else
+ it
+ }!!
+
+ /**
+ * Parse gallery page to metadata model
+ */
+ override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) {
+ val metdata = ExGalleryMetadata()
+ with(metdata) {
+ url = response.request().url().encodedPath()
+ exh = this@EHentai.exh
+ title = select("#gn").text().nullIfBlank()?.trim()
+
+ altTitle = select("#gj").text().nullIfBlank()?.trim()
+
+ thumbnailUrl = select("#gd1 div").attr("style").nullIfBlank()?.let {
+ it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
+ }
+
+ genre = select(".ic").parents().attr("href").nullIfBlank()?.trim()?.substringAfterLast('/')
+
+ uploader = select("#gdn").text().nullIfBlank()?.trim()
+
+ //Parse the table
+ select("#gdd tr").forEach {
+ it.select(".gdt1")
+ .text()
+ .nullIfBlank()
+ ?.trim()
+ ?.let { left ->
+ it.select(".gdt2")
+ .text()
+ .nullIfBlank()
+ ?.trim()
+ ?.let { right ->
+ ignore {
+ when (left.removeSuffix(":")
+ .toLowerCase()) {
+ "posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
+ "visible" -> visible = right.nullIfBlank()
+ "language" -> {
+ language = right.removeSuffix(TR_SUFFIX).trim().nullIfBlank()
+ translated = right.endsWith(TR_SUFFIX, true)
+ }
+ "file size" -> size = parseHumanReadableByteCount(right)?.toLong()
+ "length" -> length = right.removeSuffix("pages").trim().nullIfBlank()?.toInt()
+ "favorited" -> favorites = right.removeSuffix("times").trim().nullIfBlank()?.toInt()
+ }
+ }
+ }
+ }
+ }
+
+ //Parse ratings
+ ignore {
+ averageRating = select("#rating_label")
+ .text()
+ .removePrefix("Average:")
+ .trim()
+ .nullIfBlank()
+ ?.toDouble()
+ ratingCount = select("#rating_count")
+ .text()
+ .trim()
+ .nullIfBlank()
+ ?.toInt()
+ }
+
+ //Parse tags
+ tags.clear()
+ select("#taglist tr").forEach {
+ val namespace = it.select(".tc").text().removeSuffix(":")
+ val currentTags = it.select("div").map {
+ Tag(it.text().trim(),
+ it.hasClass("gtl"))
+ }
+ tags.put(namespace, ArrayList(currentTags))
+ }
+
+ //Save metadata
+ metadataHelper.writeGallery(this, id)
+
+ //Copy metadata to manga
+ SManga.create().let {
+ copyTo(it)
+ it
+ }
+ }
+ }
+
+ override fun chapterListParse(response: Response)
+ = throw UnsupportedOperationException("Unused method was called somehow!")
+
+ override fun pageListParse(response: Response)
+ = throw UnsupportedOperationException("Unused method was called somehow!")
+
+ override fun fetchImageUrl(page: Page): Observable {
+ return client.newCall(imageUrlRequest(page))
+ .asObservableSuccess()
+ .map { realImageUrlParse(it, page) }
+ }
+
+ fun realImageUrlParse(response: Response, page: Page): String {
+ with(response.asJsoup()) {
+ val currentImage = getElementById("img").attr("src")
+ //Each press of the retry button will choose another server
+ select("#loadfail").attr("onclick").nullIfBlank()?.let {
+ page.url = addParam(page.url, "nl", it.substring(it.indexOf('\'') + 1 .. it.lastIndexOf('\'') - 1))
+ }
+ return currentImage
+ }
+ }
+
+ override fun imageUrlParse(response: Response): String {
+ throw UnsupportedOperationException("Unused method was called somehow!")
+ }
+
+ //Too lazy to write return type
+ fun fetchFavorites() = {
+ //Used to get "s" cookie
+ val favoriteUrl = "$baseUrl/favorites.php"
+ val result = mutableListOf()
+ var page = 1
+
+ var favNames: List? = null
+
+ do {
+ val response2 = client.newCall(exGet(favoriteUrl,
+ page = page,
+ cache = false)).execute()
+ val doc = response2.asJsoup()
+
+ //Parse favorites
+ val parsed = extendedGenericMangaParse(doc)
+ result += parsed.first
+
+ //Parse fav names
+ if (favNames == null)
+ favNames = doc.getElementsByClass("nosel").first().children().filter {
+ it.children().size >= 3
+ }.map { it.child(2).text() }.filterNotNull()
+
+ //Next page
+ page++
+ } while (parsed.second)
+ Pair(result as List, favNames!!)
+ }()
+
+ val cookiesHeader by lazy {
+ val cookies: MutableMap = mutableMapOf()
+ if(prefs.enableExhentai().getOrDefault()) {
+ cookies.put(LoginActivity.MEMBER_ID_COOKIE, prefs.memberIdVal().get()!!)
+ cookies.put(LoginActivity.PASS_HASH_COOKIE, prefs.passHashVal().get()!!)
+ cookies.put(LoginActivity.IGNEOUS_COOKIE, prefs.igneousVal().get()!!)
+ }
+
+ //Setup settings
+ val settings = mutableListOf()
+ //Image quality
+ settings.add(when(prefs.imageQuality()
+ .getOrDefault()
+ .toLowerCase()) {
+ "ovrs_2400" -> "xr_2400"
+ "ovrs_1600" -> "xr_1600"
+ "high" -> "xr_1280"
+ "med" -> "xr_980"
+ "low" -> "xr_780"
+ "auto" -> null
+ else -> null
+ })
+ //Use Hentai@Home
+ settings.add(if(prefs.useHentaiAtHome().getOrDefault())
+ null
+ else
+ "uh_n")
+ //Japanese titles
+ settings.add(if(prefs.useJapaneseTitle().getOrDefault())
+ "tl_j"
+ else
+ null)
+ //Do not show popular right now pane as we can't parse it
+ settings.add("prn_n")
+ //Paging size
+ settings.add(prefs.ehSearchSize().getOrDefault())
+ //Thumbnail rows
+ settings.add(prefs.thumbnailRows().getOrDefault())
+
+ cookies.put("uconfig", buildSettings(settings))
+
+ buildCookies(cookies)
+ }
+
+ //Headers
+ override fun headersBuilder()
+ = super.headersBuilder().add("Cookie", cookiesHeader)!!
+
+ fun buildSettings(settings: List): String {
+ return settings.filterNotNull().joinToString(separator = "-")
+ }
+
+ fun buildCookies(cookies: Map)
+ = cookies.entries.map {
+ "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
+ }.joinToString(separator = "; ", postfix = ";")
+
+ fun addParam(url: String, param: String, value: String)
+ = Uri.parse(url)
+ .buildUpon()
+ .appendQueryParameter(param, value)
+ .toString()
+
+ override val client = network.client.newBuilder()
+ .addInterceptor { chain ->
+ val newReq = chain
+ .request()
+ .newBuilder()
+ .addHeader("Cookie", cookiesHeader)
+ .build()
+
+ chain.proceed(newReq)
+ }.build()!!
+
+ //Filters
+ override fun getFilterList() = FilterList(
+ GenreGroup(),
+ AdvancedGroup()
+ )
+
+ class GenreOption(name: String, val genreId: String): Filter.CheckBox(name, false), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ builder.appendQueryParameter("f_" + genreId, if(state) "1" else "0")
+ }
+ }
+ class GenreGroup : UriGroup("Genres", listOf(
+ GenreOption("DÅjinshi", "doujinshi"),
+ GenreOption("Manga", "manga"),
+ GenreOption("Artist CG", "artistcg"),
+ GenreOption("Game CG", "gamecg"),
+ GenreOption("Western", "western"),
+ GenreOption("Non-H", "non-h"),
+ GenreOption("Image Set", "imageset"),
+ GenreOption("Cosplay", "cosplay"),
+ GenreOption("Asian Porn", "asianporn"),
+ GenreOption("Misc", "misc")
+ ))
+
+ class AdvancedOption(name: String, val param: String, defValue: Boolean = false): Filter.CheckBox(name, defValue), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ if(state)
+ builder.appendQueryParameter(param, "on")
+ }
+ }
+ class RatingOption : Filter.Select("Minimum Rating", arrayOf(
+ "Any",
+ "2 stars",
+ "3 stars",
+ "4 stars",
+ "5 stars"
+ )), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ if(state > 0) builder.appendQueryParameter("f_srdd", Integer.toString(state + 1))
+ }
+ }
+
+ //Explicit type arg for listOf() to workaround this: KT-16570
+ class AdvancedGroup : UriGroup>("Advanced Options", listOf>(
+ AdvancedOption("Search Gallery Name", "f_sname", true),
+ AdvancedOption("Search Gallery Tags", "f_stags", true),
+ AdvancedOption("Search Gallery Description", "f_sdesc"),
+ AdvancedOption("Search Torrent Filenames", "f_storr"),
+ AdvancedOption("Only Show Galleries With Torrents", "f_sto"),
+ AdvancedOption("Search Low-Power Tags", "f_sdt1"),
+ AdvancedOption("Search Downvoted Tags", "f_sdt2"),
+ AdvancedOption("Show Expunged Galleries", "f_sh"),
+ RatingOption()
+ ))
+
+ override val name = if(exh)
+ "ExHentai"
+ else
+ "E-Hentai"
+
+ companion object {
+ val QUERY_PREFIX = "?f_apply=Apply+Filter"
+ val TR_SUFFIX = "TR"
+
+ fun getCookies(cookies: String): Map? {
+ val foundCookies = HashMap()
+ for (cookie in cookies.split(";".toRegex()).dropLastWhile(String::isEmpty).toTypedArray()) {
+ val splitCookie = cookie.split("=".toRegex()).dropLastWhile(String::isEmpty).toTypedArray()
+ if (splitCookie.size < 2) {
+ return null
+ }
+ val trimmedKey = splitCookie[0].trim { it <= ' ' }
+ if (!foundCookies.containsKey(trimmedKey)) {
+ foundCookies.put(trimmedKey, splitCookie[1].trim { it <= ' ' })
+ }
+ }
+ return foundCookies
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentaiMetadata.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentaiMetadata.kt
new file mode 100755
index 000000000..d053dc9d4
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentaiMetadata.kt
@@ -0,0 +1,127 @@
+package eu.kanade.tachiyomi.source.online.all
+
+import android.content.Context
+import eu.kanade.tachiyomi.data.database.models.Chapter
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.source.model.*
+import eu.kanade.tachiyomi.source.online.HttpSource
+import exh.metadata.MetadataHelper
+import exh.metadata.copyTo
+import exh.metadata.models.ExGalleryMetadata
+import exh.search.SearchEngine
+import okhttp3.Response
+import rx.Observable
+
+/**
+ * Offline metadata store source
+ *
+ * TODO This no longer fakes an online source because of technical reasons.
+ * If we still want offline search, we must find out a way to rearchitecture the source system so it supports
+ * online source faking again.
+ */
+
+class EHentaiMetadata(override val id: Long,
+ val exh: Boolean,
+ val context: Context) : HttpSource() {
+ override fun popularMangaRequest(page: Int)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun popularMangaParse(response: Response)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun searchMangaParse(response: Response)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun latestUpdatesRequest(page: Int)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun latestUpdatesParse(response: Response)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun mangaDetailsParse(response: Response)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun chapterListParse(response: Response)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun pageListParse(response: Response)
+ = throw UnsupportedOperationException("Unused method called!")
+ override fun imageUrlParse(response: Response)
+ = throw UnsupportedOperationException("Unused method called!")
+
+ val metadataHelper = MetadataHelper()
+
+ val internalEx = EHentai(id - 2, exh, context)
+
+ val searchEngine = SearchEngine()
+
+ override val baseUrl: String
+ get() = throw UnsupportedOperationException()
+ override val lang: String
+ get() = "advanced"
+ override val supportsLatest: Boolean
+ get() = true
+
+ override fun fetchChapterList(manga: SManga): Observable>
+ = Observable.just(listOf(Chapter.create().apply {
+ url = manga.url
+ name = "ONLINE - Chapter"
+ chapter_number = 1f
+ }))
+
+ override fun fetchPageList(chapter: SChapter) = internalEx.fetchPageList(chapter)
+
+ override fun fetchImageUrl(page: Page) = internalEx.fetchImageUrl(page)
+
+ fun List.mapToManga() = filter { it.exh == exh }
+ .map {
+ Manga.create(id).apply {
+ it.copyTo(this)
+ source = this@EHentaiMetadata.id
+ }
+ }
+
+ fun sortedByTimeGalleries() = metadataHelper.getAllGalleries().sortedByDescending {
+ it.datePosted ?: 0
+ }
+
+ override fun fetchPopularManga(page: Int)
+ = Observable.fromCallable {
+ MangasPage(metadataHelper.getAllGalleries().sortedByDescending {
+ it.ratingCount ?: 0
+ }.mapToManga(), false)
+ }!!
+
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList)
+ = Observable.fromCallable {
+ val genreGroup = filters.find {
+ it is EHentai.GenreGroup
+ }!! as EHentai.GenreGroup
+ val disableGenreFilter = genreGroup.state.find(EHentai.GenreOption::state) == null
+
+ val parsed = searchEngine.parseQuery(query)
+ MangasPage(sortedByTimeGalleries().filter { manga ->
+ disableGenreFilter || genreGroup.state.find {
+ it.state && it.genreId == manga.genre
+ } != null
+ }.filter {
+ searchEngine.matches(it, parsed)
+ }.mapToManga(), false)
+ }!!
+
+ override fun fetchLatestUpdates(page: Int)
+ = Observable.fromCallable {
+ MangasPage(sortedByTimeGalleries().mapToManga(), false)
+ }!!
+
+ override fun fetchMangaDetails(manga: SManga) = Observable.fromCallable {
+ //Hack to convert the gallery into an online gallery when favoriting it or reading it
+ metadataHelper.fetchEhMetadata(manga.url, exh)?.copyTo(manga)
+ manga
+ }!!
+
+ override fun getFilterList() = FilterList(EHentai.GenreGroup())
+
+ override val name: String
+ get() = if(exh) {
+ "ExHentai"
+ } else {
+ "E-Hentai"
+ } + " - METADATA"
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt
new file mode 100755
index 000000000..35cbb90e3
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt
@@ -0,0 +1,237 @@
+package eu.kanade.tachiyomi.source.online.all
+
+import android.content.Context
+import android.net.Uri
+import com.github.salomonbrys.kotson.get
+import com.github.salomonbrys.kotson.int
+import com.github.salomonbrys.kotson.long
+import com.github.salomonbrys.kotson.string
+import com.google.gson.JsonElement
+import com.google.gson.JsonNull
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import eu.kanade.tachiyomi.BuildConfig
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.source.model.*
+import eu.kanade.tachiyomi.source.online.HttpSource
+import exh.NHENTAI_SOURCE_ID
+import exh.metadata.MetadataHelper
+import exh.metadata.copyTo
+import exh.metadata.models.NHentaiMetadata
+import exh.metadata.models.Tag
+import okhttp3.Request
+import okhttp3.Response
+import rx.Observable
+import timber.log.Timber
+
+/**
+ * NHentai source
+ */
+
+class NHentai(context: Context) : HttpSource() {
+ override fun fetchPopularManga(page: Int): Observable {
+ //TODO There is currently no way to get the most popular mangas
+ //TODO Instead, we delegate this to the latest updates thing to avoid confusing users with an empty screen
+ return fetchLatestUpdates(page)
+ }
+
+ override fun popularMangaRequest(page: Int): Request {
+ TODO("Currently unavailable!")
+ }
+
+ override fun popularMangaParse(response: Response): MangasPage {
+ TODO("Currently unavailable!")
+ }
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ //Currently we have no filters
+ //TODO Filter builder
+ val uri = Uri.parse("$baseUrl/api/galleries/search").buildUpon()
+ uri.appendQueryParameter("query", query)
+ uri.appendQueryParameter("page", page.toString())
+ return nhGet(uri.toString(), page)
+ }
+
+ override fun searchMangaParse(response: Response)
+ = parseResultPage(response)
+
+ override fun latestUpdatesRequest(page: Int): Request {
+ val uri = Uri.parse("$baseUrl/api/galleries/all").buildUpon()
+ uri.appendQueryParameter("page", page.toString())
+ return nhGet(uri.toString(), page)
+ }
+
+ override fun latestUpdatesParse(response: Response)
+ = parseResultPage(response)
+
+ override fun mangaDetailsParse(response: Response)
+ = parseGallery(jsonParser.parse(response.body().string()).asJsonObject)
+
+ //Used so we can use a different URL for fetching manga details and opening the details in the browser
+ override fun fetchMangaDetails(manga: SManga): Observable {
+ return client.newCall(urlToDetailsRequest(manga.url))
+ .asObservableSuccess()
+ .map { response ->
+ mangaDetailsParse(response).apply { initialized = true }
+ }
+ }
+
+ override fun mangaDetailsRequest(manga: SManga)
+ = nhGet(manga.url)
+
+ fun urlToDetailsRequest(url: String)
+ = nhGet(baseUrl + "/api/gallery/" + url.split("/").last())
+
+ fun parseResultPage(response: Response): MangasPage {
+ val res = jsonParser.parse(response.body().string()).asJsonObject
+
+ val error = res.get("error")
+ if(error == null) {
+ val results = res.getAsJsonArray("result")?.map {
+ parseGallery(it.asJsonObject)
+ }
+ val numPages = res.get("num_pages")?.int
+ if(results != null && numPages != null)
+ return MangasPage(results, numPages > response.request().tag() as Int)
+ } else {
+ Timber.w("An error occurred while performing the search: $error")
+ }
+ return MangasPage(emptyList(), false)
+ }
+
+ fun rawParseGallery(obj: JsonObject) = NHentaiMetadata().apply {
+ uploadDate = obj.get("upload_date")?.notNull()?.long
+
+ favoritesCount = obj.get("num_favorites")?.notNull()?.long
+
+ mediaId = obj.get("media_id")?.notNull()?.string
+
+ obj.get("title")?.asJsonObject?.let {
+ japaneseTitle = it.get("japanese")?.notNull()?.string
+ shortTitle = it.get("pretty")?.notNull()?.string
+ englishTitle = it.get("english")?.notNull()?.string
+ }
+
+ obj.get("images")?.asJsonObject?.let {
+ coverImageType = it.get("cover")?.get("t")?.notNull()?.asString
+ it.get("pages")?.asJsonArray?.map {
+ it?.asJsonObject?.get("t")?.notNull()?.asString
+ }?.filterNotNull()?.let {
+ pageImageTypes.clear()
+ pageImageTypes.addAll(it)
+ }
+ thumbnailImageType = it.get("thumbnail")?.get("t")?.notNull()?.asString
+ }
+
+ scanlator = obj.get("scanlator")?.notNull()?.asString
+
+ id = obj.get("id")?.asLong
+
+ obj.get("tags")?.asJsonArray?.map {
+ val asObj = it.asJsonObject
+ Pair(asObj.get("type")?.string, asObj.get("name")?.string)
+ }?.apply {
+ tags.clear()
+ }?.forEach {
+ if(it.first != null && it.second != null)
+ tags.getOrPut(it.first!!, { ArrayList() }).add(Tag(it.second!!, false))
+ }
+ }
+
+ fun parseGallery(obj: JsonObject) = rawParseGallery(obj).let {
+ metadataHelper.writeGallery(it, id)
+
+ SManga.create().apply {
+ it.copyTo(this)
+ }
+ }
+
+ fun lazyLoadMetadata(url: String) =
+ Observable.fromCallable {
+ metadataHelper.fetchNhentaiMetadata(url)
+ ?: client.newCall(urlToDetailsRequest(url))
+ .asObservableSuccess()
+ .map {
+ rawParseGallery(jsonParser.parse(it.body().string()).asJsonObject)
+ }.toBlocking().first()
+ }!!
+
+ override fun fetchChapterList(manga: SManga)
+ = lazyLoadMetadata(manga.url).map {
+ listOf(SChapter.create().apply {
+ url = manga.url
+ name = "Chapter"
+ //TODO Get this working later
+// date_upload = it.uploadDate ?: 0
+ chapter_number = 1f
+ })
+ }!!
+
+ override fun fetchPageList(chapter: SChapter)
+ = lazyLoadMetadata(chapter.url).map { metadata ->
+ if(metadata.mediaId == null) emptyList()
+ else
+ metadata.pageImageTypes.mapIndexed { index, s ->
+ val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s)
+ Page(index, imageUrl!!, imageUrl)
+ }
+ }!!
+
+ override fun fetchImageUrl(page: Page) = Observable.just(page.imageUrl!!)!!
+
+ fun imageUrlFromType(mediaId: String, page: Int, t: String) = NHentaiMetadata.typeToExtension(t)?.let {
+ "https://i.nhentai.net/galleries/$mediaId/$page.$it"
+ }
+
+ override fun chapterListParse(response: Response): List {
+ throw NotImplementedError("Unused method called!")
+ }
+
+ override fun pageListParse(response: Response): List {
+ throw NotImplementedError("Unused method called!")
+ }
+
+ override fun imageUrlParse(response: Response): String {
+ throw NotImplementedError("Unused method called!")
+ }
+
+ val appName by lazy {
+ context.getString(R.string.app_name)!!
+ }
+ fun nhGet(url: String, tag: Any? = null) = GET(url)
+ .newBuilder()
+ .header("User-Agent",
+ "Mozilla/5.0 (X11; Linux x86_64) " +
+ "AppleWebKit/537.36 (KHTML, like Gecko) " +
+ "Chrome/56.0.2924.87 " +
+ "Safari/537.36 " +
+ "$appName/${BuildConfig.VERSION_CODE}")
+ .tag(tag).build()!!
+
+ override val id = NHENTAI_SOURCE_ID
+
+ override val lang = "all"
+
+ override val name = "nhentai"
+
+ override val baseUrl = NHentaiMetadata.BASE_URL
+
+ override val supportsLatest = true
+
+ companion object {
+ val jsonParser by lazy {
+ JsonParser()
+ }
+
+ val metadataHelper by lazy {
+ MetadataHelper()
+ }
+ }
+
+ fun JsonElement.notNull() =
+ if(this is JsonNull)
+ null
+ else this
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/PervEden.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/PervEden.kt
new file mode 100755
index 000000000..c0448207b
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/PervEden.kt
@@ -0,0 +1,282 @@
+package eu.kanade.tachiyomi.source.online.all
+
+import android.net.Uri
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.source.model.*
+import eu.kanade.tachiyomi.source.online.ParsedHttpSource
+import eu.kanade.tachiyomi.util.ChapterRecognition
+import eu.kanade.tachiyomi.util.asJsoup
+import exh.metadata.MetadataHelper
+import exh.metadata.copyTo
+import exh.metadata.models.PervEdenGalleryMetadata
+import exh.metadata.models.Tag
+import exh.util.UriFilter
+import exh.util.UriGroup
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import org.jsoup.nodes.TextNode
+import timber.log.Timber
+import java.text.SimpleDateFormat
+import java.util.*
+
+class PervEden(override val id: Long, override val lang: String) : ParsedHttpSource() {
+
+ override val supportsLatest = true
+ override val name = "Perv Eden"
+ override val baseUrl = "http://www.perveden.com"
+
+ val metadataHelper by lazy { MetadataHelper() }
+
+ override fun popularMangaSelector() = "#topManga > ul > li"
+
+ override fun popularMangaFromElement(element: Element): SManga {
+ val manga = SManga.create()
+ manga.thumbnail_url = "http:" + element.select(".hottestImage > img").attr("data-src")
+
+ val titleElement = element.getElementsByClass("hottestInfo").first().child(0)
+ manga.url = titleElement.attr("href")
+ manga.title = titleElement.text()
+
+ return manga
+ }
+
+ override fun popularMangaNextPageSelector(): String? = null
+
+ override fun searchMangaSelector() = "#mangaList > tbody > tr"
+
+ override fun searchMangaFromElement(element: Element): SManga {
+ val manga = SManga.create()
+ val titleElement = element.child(0).child(0)
+ manga.url = titleElement.attr("href")
+ manga.title = titleElement.text().trim()
+ return manga
+ }
+
+ override fun searchMangaNextPageSelector() = ".next"
+
+ override fun popularMangaRequest(page: Int): Request {
+ val urlLang = if(lang == "en")
+ "eng"
+ else "it"
+ return GET("$baseUrl/$urlLang/")
+ }
+
+ override fun latestUpdatesSelector() = ".newsManga"
+
+ override fun latestUpdatesFromElement(element: Element): SManga {
+ val manga = SManga.create()
+ val header = element.getElementsByClass("manga_tooltop_header").first()
+ val titleElement = header.child(0)
+ manga.url = titleElement.attr("href")
+ manga.title = titleElement.text().trim()
+ manga.thumbnail_url = "http:" + titleElement.getElementsByClass("mangaImage").first().attr("tmpsrc")
+ return manga
+ }
+
+ override fun latestUpdatesParse(response: Response): MangasPage {
+ val document = response.asJsoup()
+
+ val mangas = document.select(latestUpdatesSelector()).map { element ->
+ latestUpdatesFromElement(element)
+ }
+
+ return MangasPage(mangas, mangas.isNotEmpty())
+ }
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val uri = Uri.parse("$baseUrl/$lang/$lang-directory/").buildUpon()
+ uri.appendQueryParameter("page", page.toString())
+ filters.forEach {
+ if(it is UriFilter) it.addToUri(uri)
+ }
+ return GET(uri.toString())
+ }
+
+ override fun latestUpdatesNextPageSelector(): String? {
+ throw NotImplementedError("Unused method called!")
+ }
+
+ override fun mangaDetailsParse(document: Document): SManga {
+ val metadata = PervEdenGalleryMetadata()
+ with(metadata) {
+ url = document.location()
+
+ lang = this@PervEden.lang
+
+ title = document.getElementsByClass("manga-title").first()?.text()
+
+ thumbnailUrl = "http:" + document.getElementsByClass("mangaImage2").first()?.child(0)?.attr("src")
+
+ val rightBoxElement = document.select(".rightBox:not(.info)").first()
+
+ tags.clear()
+ var inStatus: String? = null
+ rightBoxElement.childNodes().forEach {
+ if(it is Element && it.tagName().toLowerCase() == "h4") {
+ inStatus = it.text().trim()
+ } else {
+ when(inStatus) {
+ "Alternative name(s)" -> {
+ if(it is TextNode) {
+ val text = it.text().trim()
+ if(!text.isBlank())
+ altTitles.add(text)
+ }
+ }
+ "Artist" -> {
+ if(it is Element && it.tagName() == "a") {
+ artist = it.text()
+ tags.getOrPut("artist", {
+ ArrayList()
+ }).add(Tag(it.text().toLowerCase(), false))
+ }
+ }
+ "Genres" -> {
+ if(it is Element && it.tagName() == "a")
+ tags.getOrPut("genre", {
+ ArrayList()
+ }).add(Tag(it.text().toLowerCase(), false))
+ }
+ "Type" -> {
+ if(it is TextNode) {
+ val text = it.text().trim()
+ if(!text.isBlank())
+ type = text
+ }
+ }
+ "Status" -> {
+ if(it is TextNode) {
+ val text = it.text().trim()
+ if(!text.isBlank())
+ status = text
+ }
+ }
+ }
+ }
+ }
+
+ rating = document.getElementById("rating-score")?.attr("value")?.toFloat()
+
+ //Save metadata
+ Timber.d("LNG: " + metadata.lang)
+ metadataHelper.writeGallery(this, id)
+
+ return SManga.create().apply {
+ copyTo(this)
+ }
+ }
+ }
+
+ override fun latestUpdatesRequest(page: Int): Request {
+ val num = if(lang == "en") "0"
+ else if(lang == "it") "1"
+ else throw NotImplementedError("Unimplemented language!")
+
+ return GET("$baseUrl/ajax/news/$page/$num/0/")
+ }
+
+ override fun chapterListSelector() = "#leftContent > table > tbody > tr"
+
+ override fun chapterFromElement(element: Element) = SChapter.create().apply {
+ val linkElement = element.getElementsByClass("chapterLink").first()
+
+ setUrlWithoutDomain(linkElement.attr("href"))
+ name = "Chapter " + linkElement.getElementsByTag("b").text()
+
+ ChapterRecognition.parseChapterNumber(
+ this,
+ SManga.create().apply {
+ title = ""
+ })
+
+ try {
+ date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim()).time
+ } catch(ignored: Exception) {}
+ }
+
+ override fun pageListParse(document: Document)
+ = document.getElementById("pageSelect").getElementsByTag("option").map {
+ Page(it.attr("data-page").toInt() - 1, baseUrl + it.attr("value"))
+ }
+
+ override fun imageUrlParse(document: Document)
+ = "http:" + document.getElementById("mainImg").attr("src")!!
+
+ override fun getFilterList() = FilterList (
+ AuthorFilter(),
+ ArtistFilter(),
+ TypeFilterGroup(),
+ ReleaseYearGroup(),
+ StatusFilterGroup()
+ )
+
+ class StatusFilterGroup : UriGroup("Status", listOf(
+ StatusFilter("Ongoing", 1),
+ StatusFilter("Completed", 2),
+ StatusFilter("Suspended", 0)
+ ))
+
+ class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ if(state)
+ builder.appendQueryParameter("status", id.toString())
+ }
+ }
+
+ //Explicit type arg for listOf() to workaround this: KT-16570
+ class ReleaseYearGroup : UriGroup>("Release Year", listOf>(
+ ReleaseYearRangeFilter(),
+ ReleaseYearYearFilter()
+ ))
+
+ class ReleaseYearRangeFilter : Filter.Select("Range", arrayOf(
+ "on",
+ "after",
+ "before"
+ )), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ builder.appendQueryParameter("releasedType", state.toString())
+ }
+ }
+
+ class ReleaseYearYearFilter : Filter.Text("Year"), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ builder.appendQueryParameter("released", state)
+ }
+ }
+
+ class AuthorFilter : Filter.Text("Author"), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ builder.appendQueryParameter("author", state)
+ }
+ }
+
+ class ArtistFilter : Filter.Text("Artist"), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ builder.appendQueryParameter("artist", state)
+ }
+ }
+
+ class TypeFilterGroup : UriGroup("Type", listOf(
+ TypeFilter("Japanese Manga", 0),
+ TypeFilter("Korean Manhwa", 1),
+ TypeFilter("Chinese Manhua", 2),
+ TypeFilter("Comic", 3),
+ TypeFilter("Doujinshi", 4)
+ ))
+
+ class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ if(state)
+ builder.appendQueryParameter("type", id.toString())
+ }
+ }
+
+ companion object {
+ val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply {
+ timeZone = TimeZone.getTimeZone("GMT")
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Kissmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Kissmanga.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangafox.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangafox.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangahere.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangahere.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangasee.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangasee.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Readmangatoday.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Readmangatoday.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/german/WieManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/german/WieManga.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mangachan.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mangachan.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mintmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mintmanga.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Readmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Readmanga.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/ActivityMixin.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/ActivityMixin.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
index 38a4568d0..59a2c317a
--- 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
@@ -2,6 +2,15 @@ package eu.kanade.tachiyomi.ui.base.activity
import android.support.v7.app.AppCompatActivity
import eu.kanade.tachiyomi.util.LocaleHelper
+import exh.ui.lock.lockEnabled
+import exh.ui.lock.showLockActivity
+import android.app.ActivityManager
+import android.app.Service
+import android.app.usage.UsageStats
+import android.app.usage.UsageStatsManager
+import android.os.Build
+import java.util.*
+
abstract class BaseActivity : AppCompatActivity(), ActivityMixin {
@@ -23,4 +32,48 @@ abstract class BaseActivity : AppCompatActivity(), ActivityMixin {
super.onPause()
}
+ var willLock = false
+ var disableLock = false
+ override fun onRestart() {
+ super.onRestart()
+ if(willLock && lockEnabled() && !disableLock) {
+ showLockActivity(this)
+ }
+
+ willLock = false
+ }
+
+ override fun onStop() {
+ super.onStop()
+ tryLock()
+ }
+
+ fun tryLock() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ val mUsageStatsManager = getSystemService("usagestats") as UsageStatsManager
+ val time = System.currentTimeMillis()
+ // We get usage stats for the last 20 seconds
+ val stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 20, time)
+ // Sort the stats by the last time used
+ if (stats != null) {
+ val mySortedMap = TreeMap()
+ for (usageStats in stats) {
+ mySortedMap.put(usageStats.lastTimeUsed, usageStats)
+ }
+ if (!mySortedMap.isEmpty()) {
+ if(mySortedMap[mySortedMap.lastKey()]?.packageName != packageName) {
+ willLock = true
+ }
+ }
+ }
+ } else {
+ val am = getSystemService(Service.ACTIVITY_SERVICE) as ActivityManager
+ val tasks: List
+ tasks = am.getRunningTasks(1)
+ val running = tasks[0]
+ if (running.topActivity.packageName != packageName) {
+ willLock = true
+ }
+ }
+ }
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/SmartFragmentStatePagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/SmartFragmentStatePagerAdapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/FragmentMixin.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/FragmentMixin.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueHolder.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueItem.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/NoResultsException.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/NoResultsException.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/ProgressItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/ProgressItem.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SectionItems.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SectionItems.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadActivity.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPager.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
index 8aab567e8..5aad6f4cf
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
@@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.ui.library
+import android.os.Handler
+import android.os.Looper
import android.view.Gravity
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@@ -7,9 +9,16 @@ import android.widget.FrameLayout
import eu.davidea.flexibleadapter4.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.source.online.all.EHentai
+import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
+import exh.isLewdSource
+import exh.metadata.MetadataHelper
+import exh.search.SearchEngine
import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
+import uy.kohesive.injekt.injectLazy
import java.util.*
/**
@@ -25,6 +34,13 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
*/
private var mangas: List = emptyList()
+ private val sourceManager: SourceManager by injectLazy()
+
+ private val searchEngine = SearchEngine()
+ private val metadataHelper = MetadataHelper()
+
+ var asyncSearchText: String? = null
+
init {
setHasStableIds(true)
}
@@ -58,8 +74,17 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
* @param param the filter. Not used.
*/
override fun updateDataSet(param: String?) {
- filterItems(mangas)
- notifyDataSetChanged()
+ //Async search filter (EH)
+ val filtered = asyncSearchText?.let { search ->
+ mangas.filter {
+ filterObject(it, search)
+ }
+ } ?: mangas
+ //The rest of the filters run on the main loop
+ Handler(Looper.getMainLooper()).post {
+ filterItems(filtered)
+ notifyDataSetChanged()
+ }
}
/**
@@ -70,8 +95,17 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
* @return true if the manga should be included, false otherwise.
*/
override fun filterObject(manga: Manga, query: String): Boolean = with(manga) {
- title.toLowerCase().contains(query) ||
- author != null && author!!.toLowerCase().contains(query)
+ if(!isLewdSource(manga.source)) {
+ //Regular searching for normal manga
+ title.toLowerCase().contains(query) ||
+ author != null && author!!.toLowerCase().contains(query)
+ } else {
+ //Use gallery search engine for EH manga
+ val metadata = metadataHelper.fetchMetadata(manga.url, manga.source)
+ metadata?.let {
+ searchEngine.matches(it, searchEngine.parseQuery(query))
+ } ?: title.contains(query, ignoreCase = true) //Use regular searching when the metadata is not set up for this gallery
+ }
}
/**
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
old mode 100644
new mode 100755
index bdafd0bc0..dd7d2eaf2
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt
@@ -19,7 +19,9 @@ import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.item_library_category.view.*
import rx.Subscription
+import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.injectLazy
+import java.util.concurrent.TimeUnit
/**
* Fragment containing the library manga for a certain category.
@@ -114,8 +116,11 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
val presenter = fragment.presenter
- searchSubscription = presenter.searchSubject.subscribe { text ->
- adapter.searchText = text
+ searchSubscription = presenter
+ .searchSubject
+ .debounce(10L, TimeUnit.MILLISECONDS)
+ .subscribe { text -> //Debounce search (EH)
+ adapter.asyncSearchText = text?.trim()?.toLowerCase()
adapter.updateDataSet()
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt
old mode 100644
new mode 100755
index 0b6d92fe4..2d334506f
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt
@@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.DialogCheckboxView
+import exh.FavoritesSyncHelper
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_library.*
import nucleus.factory.RequiresPresenter
@@ -267,6 +268,11 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback
val intent = CategoryActivity.newIntent(activity)
startActivity(intent)
}
+ R.id.action_sync -> {
+ FavoritesSyncHelper(this.activity).guiSyncFavorites({
+ //Do we even need stuff in here?
+ })
+ }
else -> return super.onOptionsItemSelected(item)
}
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogFragment.kt
old mode 100644
new mode 100755
index 08ab144b1..1c6c35861
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogFragment.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogFragment.kt
@@ -25,7 +25,6 @@ class ChangelogDialogFragment : DialogFragment() {
preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
ChangelogDialogFragment().show(fm, "changelog")
- // TODO better upgrades management
if (oldVersion == 0) return
if (oldVersion < 14) {
@@ -51,6 +50,7 @@ class ChangelogDialogFragment : DialogFragment() {
}
}
}
+ //TODO Review any other changes below
}
}
}
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
old mode 100644
new mode 100755
index e40a8144e..ef457c843
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
@@ -16,6 +16,10 @@ import eu.kanade.tachiyomi.ui.library.LibraryFragment
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersFragment
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadFragment
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
+import exh.ui.batchadd.BatchAddFragment
+import exh.ui.lock.lockEnabled
+import exh.ui.lock.notifyLockSecurity
+import exh.ui.lock.showLockActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.toolbar.*
import uy.kohesive.injekt.injectLazy
@@ -65,6 +69,7 @@ class MainActivity : BaseActivity() {
R.id.nav_drawer_recently_read -> setFragment(RecentlyReadFragment.newInstance(), id)
R.id.nav_drawer_catalogues -> setFragment(CatalogueFragment.newInstance(), id)
R.id.nav_drawer_latest_updates -> setFragment(LatestUpdatesFragment.newInstance(), id)
+ R.id.nav_drawer_batch_add -> setFragment(BatchAddFragment.newInstance(), id)
R.id.nav_drawer_downloads -> startActivity(Intent(this, DownloadActivity::class.java))
R.id.nav_drawer_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
@@ -88,6 +93,16 @@ class MainActivity : BaseActivity() {
// Show changelog if needed
ChangelogDialogFragment.show(this, preferences, supportFragmentManager)
+
+ //Show lock
+ val lockEnabled = lockEnabled(preferences)
+ if(lockEnabled) {
+ showLockActivity(this)
+
+ //Check lock security
+ notifyLockSecurity(this)
+ }
+
}
@@ -126,6 +141,10 @@ class MainActivity : BaseActivity() {
nav_view.post { recreate() }
} else if (resultCode and SettingsActivity.FLAG_LANG_CHANGED != 0) {
nav_view.post { recreate() }
+ } else if (resultCode and SettingsActivity.FLAG_EH_RECREATE != 0) {
+ TaskStackBuilder.create(this)
+ .addNextIntent(Intent(this, MainActivity::class.java))
+ .startActivities()
}
} else {
super.onActivityResult(requestCode, resultCode, data)
@@ -156,5 +175,6 @@ class MainActivity : BaseActivity() {
private const val SHORTCUT_RECENTLY_UPDATED = "eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
private const val SHORTCUT_RECENTLY_READ = "eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
private const val SHORTCUT_CATALOGUES = "eu.kanade.tachiyomi.SHOW_CATALOGUES"
+ const val FINALIZE_MIGRATION = "finalize_migration"
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaEvent.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/ChapterCountEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/ChapterCountEvent.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaFavoriteEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaFavoriteEvent.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
index f0f896346..846f8fd88
--- 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
@@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.MangaEvent
import eu.kanade.tachiyomi.util.SharedData
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed
+import eu.kanade.tachiyomi.util.toast
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
@@ -69,7 +70,10 @@ class MangaInfoPresenter : BasePresenter() {
super.onCreate(savedState)
manga = SharedData.get(MangaEvent::class.java)?.manga ?: return
- source = sourceManager.get(manga.source)!!
+ source = sourceManager.get(manga.source) ?: run {
+ context.toast("Could not find manga source!")
+ return
+ }
sendMangaToView()
// Update chapter count
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackItem.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackPresenter.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
index 11b27c623..289ea0c14
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
@@ -264,6 +264,10 @@ class ReaderActivity : BaseRxActivity() {
fun onChapterReady(chapter: ReaderChapter) {
please_wait.visibility = View.GONE
val pages = chapter.pages ?: run { onChapterError(Exception("Null pages")); return }
+ if(pages.isEmpty()) {
+ onChapterError(Exception("Page list empty!"))
+ return
+ }
val activePage = pages.getOrElse(chapter.requestedPage) { pages.first() }
viewer?.onPageListReady(chapter, activePage)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderEvent.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
index 96f0184ee..38345e9f2
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
@@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
+import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.RetryWithDelay
@@ -354,6 +355,11 @@ class ReaderPresenter : BasePresenter() {
if (uri != null && !page.chapter.isDownloaded) {
chapterCache.removeFileFromCache(uri.encodedPath.substringAfterLast('/'))
}
+
+ //If we are using EHentai/ExHentai, get a new image URL
+ if(source is EHentai)
+ page.imageUrl = null
+
loader.retryPage(page)
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.java
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalViewPagerImpl.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalViewPagerImpl.java
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadFragment.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt
old mode 100644
new mode 100755
index 21304dae5..2abc7de8c
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt
@@ -68,6 +68,7 @@ class SettingsActivity : BaseActivity(),
"backup_screen" -> SettingsBackupFragment.newInstance(key)
"advanced_screen" -> SettingsAdvancedFragment.newInstance(key)
"about_screen" -> SettingsAboutFragment.newInstance(key)
+ "eh_screen" -> SettingsEhFragment.newInstance(key) //EH
else -> SettingsFragment.newInstance(key)
}
}
@@ -81,6 +82,7 @@ class SettingsActivity : BaseActivity(),
const val FLAG_THEME_CHANGED = 0x1
const val FLAG_DATABASE_CLEARED = 0x2
const val FLAG_LANG_CHANGED = 0x4
+ const val FLAG_EH_RECREATE = 0x8
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadsFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadsFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhFragment.kt
new file mode 100755
index 000000000..e7df22076
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhFragment.kt
@@ -0,0 +1,73 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.content.Intent
+import android.os.Bundle
+import android.support.v7.preference.XpPreferenceFragment
+import android.view.View
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.util.plusAssign
+import exh.ui.migration.MetadataFetchDialog
+import exh.ui.login.LoginActivity
+import net.xpece.android.support.preference.Preference
+import net.xpece.android.support.preference.SwitchPreference
+import uy.kohesive.injekt.injectLazy
+
+/**
+ * EH Settings fragment
+ */
+
+class SettingsEhFragment : SettingsFragment() {
+ companion object {
+ fun newInstance(rootKey: String): SettingsEhFragment {
+ val args = Bundle()
+ args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
+ return SettingsEhFragment().apply { arguments = args }
+ }
+ }
+
+ private val preferences: PreferencesHelper by injectLazy()
+
+ val enableExhentaiPref by lazy {
+ findPreference("enable_exhentai") as SwitchPreference
+ }
+
+ val migrateLibraryPref by lazy {
+ findPreference("ex_migrate_library") as Preference
+ }
+
+ val useJpTitlePref by lazy {
+ findPreference("use_jp_title") as SwitchPreference
+ }
+
+ override fun onViewCreated(view: View, savedState: Bundle?) {
+ super.onViewCreated(view, savedState)
+
+ subscriptions += preferences
+ .enableExhentai()
+ .asObservable().subscribe {
+ enableExhentaiPref.isChecked = it
+ }
+
+ enableExhentaiPref.setOnPreferenceChangeListener { preference, newVal ->
+ newVal as Boolean
+ (activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_EH_RECREATE
+ if(!newVal) {
+ preferences.enableExhentai().set(false)
+ true
+ } else {
+ startActivity(Intent(context, LoginActivity::class.java))
+ false
+ }
+ }
+
+ migrateLibraryPref.setOnPreferenceClickListener {
+ MetadataFetchDialog().askMigration(activity)
+ true
+ }
+
+ useJpTitlePref.setOnPreferenceChangeListener { preference, any ->
+ (activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_EH_RECREATE
+ true
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt
old mode 100644
new mode 100755
index 291db4179..29bf38fad
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt
@@ -30,6 +30,7 @@ open class SettingsFragment : XpPreferenceFragment() {
addPreferencesFromResource(R.xml.pref_sources)
addPreferencesFromResource(R.xml.pref_tracking)
addPreferencesFromResource(R.xml.pref_backup)
+ addPreferencesFromResource(R.xml.eh_pref_eh) //EH
addPreferencesFromResource(R.xml.pref_advanced)
addPreferencesFromResource(R.xml.pref_about)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingFragment.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/AndroidComponentUtil.java b/app/src/main/java/eu/kanade/tachiyomi/util/AndroidComponentUtil.java
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ChapterRecognition.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ChapterRecognition.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DynamicConcurrentMergeOperator.java b/app/src/main/java/eu/kanade/tachiyomi/util/DynamicConcurrentMergeOperator.java
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/FileExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/FileExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/GLUtil.java b/app/src/main/java/eu/kanade/tachiyomi/util/GLUtil.java
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ImageViewExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/OkioExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/OkioExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RarContentProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RarContentProvider.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RxExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RxExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/SharedData.kt b/app/src/main/java/eu/kanade/tachiyomi/util/SharedData.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/StringExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/StringExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ViewExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/DeletingChaptersDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/DeletingChaptersDialog.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationBase.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationBase.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationUpDown.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/FABAnimationUpDown.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/IgnoreFirstSpinnerListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/IgnoreFirstSpinnerListener.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/MinMaxNumberPicker.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/MinMaxNumberPicker.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/PTSansTextView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/PTSansTextView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/PreCachingLayoutManager.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/PreCachingLayoutManager.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleAnimationListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleAnimationListener.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleTextWatcher.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleTextWatcher.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LibraryColumnsDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LibraryColumnsDialog.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SimpleDialogPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SimpleDialogPreference.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt
old mode 100644
new mode 100755
diff --git a/app/src/main/java/exh/EHSourceHelpers.kt b/app/src/main/java/exh/EHSourceHelpers.kt
new file mode 100755
index 000000000..bcccc1ded
--- /dev/null
+++ b/app/src/main/java/exh/EHSourceHelpers.kt
@@ -0,0 +1,29 @@
+package exh
+
+/**
+ * Source helpers
+ */
+
+val LEWD_SOURCE_SERIES = 6900L
+val EH_SOURCE_ID = LEWD_SOURCE_SERIES + 1
+val EXH_SOURCE_ID = LEWD_SOURCE_SERIES + 2
+val EH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 3
+val EXH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 4
+
+val PERV_EDEN_EN_SOURCE_ID = LEWD_SOURCE_SERIES + 5
+val PERV_EDEN_IT_SOURCE_ID = LEWD_SOURCE_SERIES + 6
+
+val NHENTAI_SOURCE_ID = LEWD_SOURCE_SERIES + 7
+
+fun isLewdSource(source: Long) = source in 6900..6999
+
+fun isEhSource(source: Long) = source == EH_SOURCE_ID
+ || source == EH_METADATA_SOURCE_ID
+
+fun isExSource(source: Long) = source == EXH_SOURCE_ID
+ || source == EXH_METADATA_SOURCE_ID
+
+fun isPervEdenSource(source: Long) = source == PERV_EDEN_IT_SOURCE_ID
+|| source == PERV_EDEN_EN_SOURCE_ID
+
+fun isNhentaiSource(source: Long) = source == NHENTAI_SOURCE_ID
diff --git a/app/src/main/java/exh/FavoritesSyncHelper.kt b/app/src/main/java/exh/FavoritesSyncHelper.kt
new file mode 100755
index 000000000..35a41df44
--- /dev/null
+++ b/app/src/main/java/exh/FavoritesSyncHelper.kt
@@ -0,0 +1,135 @@
+package exh
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import com.afollestad.materialdialogs.MaterialDialog
+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.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.source.online.all.EHentai
+import eu.kanade.tachiyomi.util.syncChaptersWithSource
+import timber.log.Timber
+import uy.kohesive.injekt.injectLazy
+import kotlin.concurrent.thread
+
+class FavoritesSyncHelper(val activity: Activity) {
+
+ val db: DatabaseHelper by injectLazy()
+
+ val sourceManager: SourceManager by injectLazy()
+
+ val prefs: PreferencesHelper by injectLazy()
+
+ fun guiSyncFavorites(onComplete: () -> Unit) {
+ //ExHentai must be enabled/user must be logged in
+ if (!prefs.enableExhentai().getOrDefault()) {
+ AlertDialog.Builder(activity).setTitle("Error")
+ .setMessage("You are not logged in! Please log in and try again!")
+ .setPositiveButton("Ok") { dialog, _ -> dialog.dismiss() }.show()
+ return
+ }
+ val dialog = MaterialDialog.Builder(activity)
+ .progress(true, 0)
+ .title("Downloading favorites")
+ .content("Please wait...")
+ .cancelable(false)
+ .show()
+ thread {
+ var error = false
+ try {
+ syncFavorites()
+ } catch (e: Exception) {
+ error = true
+ Timber.e(e, "Could not sync favorites!")
+ }
+
+ dialog.dismiss()
+
+ activity.runOnUiThread {
+ if (error)
+ MaterialDialog.Builder(activity)
+ .title("Error")
+ .content("There was an error downloading your favorites, please try again later!")
+ .positiveText("Ok")
+ .show()
+ onComplete()
+ }
+ }
+ }
+
+ fun syncFavorites() {
+ val onlineSources = sourceManager.getOnlineSources()
+ var ehSource: EHentai? = null
+ var exSource: EHentai? = null
+ onlineSources.forEach {
+ if(it.id == EH_SOURCE_ID)
+ ehSource = it as EHentai
+ else if(it.id == EXH_SOURCE_ID)
+ exSource = it as EHentai
+ }
+
+ (exSource ?: ehSource)?.let { source ->
+ val favResponse = source.fetchFavorites()
+ val ourCategories = ArrayList(db.getCategories().executeAsBlocking())
+ val ourMangas = ArrayList(db.getMangas().executeAsBlocking())
+ //Add required categories (categories do not sync upwards)
+ favResponse.second.filter { theirCategory ->
+ ourCategories.find {
+ it.name.endsWith(theirCategory)
+ } == null
+ }.map {
+ Category.create(it)
+ }.let {
+ db.inTransaction {
+ //Insert new categories
+ db.insertCategories(it).executeAsBlocking().results().entries.filter {
+ it.value.wasInserted()
+ }.forEach { it.key.id = it.value.insertedId()!!.toInt() }
+
+ val categoryMap = (it + ourCategories).associateBy { it.name }
+
+ //Insert new mangas
+ val mangaToInsert = java.util.ArrayList()
+ favResponse.first.map {
+ val category = categoryMap[it.fav]!!
+ var manga = it.manga
+ val alreadyHaveManga = ourMangas.find {
+ it.url == manga.url
+ }?.apply {
+ manga = this
+ } != null
+ if (!alreadyHaveManga) {
+ ourMangas.add(manga)
+ mangaToInsert.add(manga)
+ }
+ manga.favorite = true
+ Pair(manga, category)
+ }.apply {
+ //Insert mangas
+ db.insertMangas(mangaToInsert).executeAsBlocking().results().entries.filter {
+ it.value.wasInserted()
+ }.forEach { manga ->
+ manga.key.id = manga.value.insertedId()
+ try {
+ source.fetchChapterList(manga.key).map {
+ syncChaptersWithSource(db, it, manga.key, source)
+ }.toBlocking().first()
+ } catch (e: Exception) {
+ Timber.w(e, "Failed to update chapters for gallery: ${manga.key.title}!")
+ }
+ }
+
+ //Set categories
+ val categories = map { MangaCategory.create(it.first, it.second) }
+ val mangas = map { it.first }
+ db.setMangaCategories(categories, mangas)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/FavoritesSyncManager.java b/app/src/main/java/exh/FavoritesSyncManager.java
new file mode 100755
index 000000000..badc70704
--- /dev/null
+++ b/app/src/main/java/exh/FavoritesSyncManager.java
@@ -0,0 +1,192 @@
+package exh;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.v7.app.AlertDialog;
+
+import com.pushtorefresh.storio.sqlite.operations.put.PutResult;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.source.online.all.EHentai;
+import kotlin.Pair;
+//import eu.kanade.tachiyomi.data.source.online.english.EHentai;
+
+public class FavoritesSyncManager {
+ /*Context context;
+ DatabaseHelper db;
+
+ public FavoritesSyncManager(Context context, DatabaseHelper db) {
+ this.context = context;
+ this.db = db;
+ }
+
+ public void guiSyncFavorites(final Runnable onComplete) {
+ if(!DialogLogin.isLoggedIn(context, false)) {
+ new AlertDialog.Builder(context).setTitle("Error")
+ .setMessage("You are not logged in! Please log in and try again!")
+ .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ }).show();
+ return;
+ }
+ final ProgressDialog dialog = ProgressDialog.show(context, "Downloading Favorites", "Please wait...", true, false);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Handler mainLooper = new Handler(Looper.getMainLooper());
+ try {
+ syncFavorites();
+ } catch (Exception e) {
+ mainLooper.post(new Runnable() {
+ @Override
+ public void run() {
+ new AlertDialog.Builder(context)
+ .setTitle("Error")
+ .setMessage("There was an error downloading your favorites, please try again later!")
+ .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ }).show();
+ }
+ });
+ e.printStackTrace();
+ }
+ dialog.dismiss();
+ mainLooper.post(onComplete);
+ }
+ }).start();
+ }*/
+/*
+ public void syncFavorites() throws IOException {
+ Pair favResponse = EHentai.fetchFavorites(context);
+ Map> favorites = favResponse.favs;
+ List ourCategories = new ArrayList<>(db.getCategories().executeAsBlocking());
+ List ourMangas = new ArrayList<>(db.getMangas().executeAsBlocking());
+ //Add required categories (categories do not sync upwards)
+ List categoriesToInsert = new ArrayList<>();
+ for (String theirCategory : favorites.keySet()) {
+ boolean haveCategory = false;
+ for (Category category : ourCategories) {
+ if (category.getName().endsWith(theirCategory)) {
+ haveCategory = true;
+ }
+ }
+ if (!haveCategory) {
+ Category category = Category.Companion.create(theirCategory);
+ ourCategories.add(category);
+ categoriesToInsert.add(category);
+ }
+ }
+ if (!categoriesToInsert.isEmpty()) {
+ for(Map.Entry result : db.insertCategories(categoriesToInsert).executeAsBlocking().results().entrySet()) {
+ if(result.getValue().wasInserted()) {
+ result.getKey().setId(result.getValue().insertedId().intValue());
+ }
+ }
+ }
+ //Build category map
+ Map categoryMap = new HashMap<>();
+ for (Category category : ourCategories) {
+ categoryMap.put(category.getName(), category);
+ }
+ //Insert new mangas
+ List mangaToInsert = new ArrayList<>();
+ Map mangaToSetCategories = new HashMap<>();
+ for (Map.Entry> entry : favorites.entrySet()) {
+ Category category = categoryMap.get(entry.getKey());
+ for (Manga manga : entry.getValue()) {
+ boolean alreadyHaveManga = false;
+ for (Manga ourManga : ourMangas) {
+ if (ourManga.getUrl().equals(manga.getUrl())) {
+ alreadyHaveManga = true;
+ manga = ourManga;
+ break;
+ }
+ }
+ if (!alreadyHaveManga) {
+ ourMangas.add(manga);
+ mangaToInsert.add(manga);
+ }
+ mangaToSetCategories.put(manga, category);
+ manga.setFavorite(true);
+ }
+ }
+ for (Map.Entry results : db.insertMangas(mangaToInsert).executeAsBlocking().results().entrySet()) {
+ if(results.getValue().wasInserted()) {
+ results.getKey().setId(results.getValue().insertedId());
+ }
+ }
+ for(Map.Entry entry : mangaToSetCategories.entrySet()) {
+ db.setMangaCategories(Collections.singletonList(MangaCategory.Companion.create(entry.getKey(), entry.getValue())),
+ Collections.singletonList(entry.getKey()));
+ }*/
+ //Determines what
+ /*Map> toUpload = new HashMap<>();
+ for (Manga manga : ourMangas) {
+ if(manga.getFavorite()) {
+ boolean remoteHasManga = false;
+ for (List remoteMangas : favorites.values()) {
+ for (Manga remoteManga : remoteMangas) {
+ if (remoteManga.getUrl().equals(manga.getUrl())) {
+ remoteHasManga = true;
+ break;
+ }
+ }
+ }
+ if (!remoteHasManga) {
+ List mangaCategories = db.getCategoriesForManga(manga).executeAsBlocking();
+ for (Category category : mangaCategories) {
+ int categoryIndex = favResponse.favCategories.indexOf(category.getName());
+ if (categoryIndex >= 0) {
+ List uploadMangas = toUpload.get(categoryIndex);
+ if (uploadMangas == null) {
+ uploadMangas = new ArrayList<>();
+ toUpload.put(categoryIndex, uploadMangas);
+ }
+ uploadMangas.add(manga);
+ }
+ }
+ }
+ }
+ }*/
+ /********** NON-FUNCTIONAL, modifygids[] CANNOT ADD NEW FAVORITES! (or as of my testing it can't, maybe I'll do more testing)**/
+ /*PreferencesHelper helper = new PreferencesHelper(context);
+ for(Map.Entry> entry : toUpload.entrySet()) {
+ FormBody.Builder formBody = new FormBody.Builder()
+ .add("ddact", "fav" + entry.getKey());
+ for(Manga manga : entry.getValue()) {
+ List splitUrl = new ArrayList<>(Arrays.asList(manga.getUrl().split("/")));
+ splitUrl.removeAll(Collections.singleton(""));
+ if(splitUrl.size() < 2) {
+ continue;
+ }
+ formBody.add("modifygids[]", splitUrl.get(1).trim());
+ }
+ formBody.add("apply", "Apply");
+ Request request = RequestsKt.POST(EHentai.buildFavoritesBase(context, helper.getPrefs()).favoritesBase,
+ EHentai.getHeadersBuilder(helper).build(),
+ formBody.build(),
+ RequestsKt.getDEFAULT_CACHE_CONTROL());
+ Response response = NetworkManager.getInstance().getClient().newCall(request).execute();
+ Util.d("EHentai", response.body().string());
+ }*/
+// }
+}
diff --git a/app/src/main/java/exh/GalleryAdder.kt b/app/src/main/java/exh/GalleryAdder.kt
new file mode 100755
index 000000000..b0e7a9ff9
--- /dev/null
+++ b/app/src/main/java/exh/GalleryAdder.kt
@@ -0,0 +1,80 @@
+package exh
+
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.util.syncChaptersWithSource
+import exh.metadata.MetadataHelper
+import exh.metadata.copyTo
+import timber.log.Timber
+import uy.kohesive.injekt.injectLazy
+import java.net.MalformedURLException
+import java.net.URI
+import java.net.URISyntaxException
+import java.net.URL
+
+class GalleryAdder {
+
+ private val db: DatabaseHelper by injectLazy()
+
+ private val sourceManager: SourceManager by injectLazy()
+
+ private val metadataHelper = MetadataHelper()
+
+ fun addGallery(url: String, fav: Boolean = false): Manga {
+ val source = when(URL(url).host) {
+ "g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
+ "exhentai.org" -> EXH_SOURCE_ID
+ else -> throw MalformedURLException("Not a valid gallery URL!")
+ }
+
+ val sourceObj = sourceManager.get(source)
+ ?: throw IllegalStateException("Could not find EH source!")
+
+ val pathOnlyUrl = getUrlWithoutDomain(url)
+
+ //Use manga in DB if possible, otherwise, make a new manga
+ val manga = db.getManga(pathOnlyUrl, source).executeAsBlocking()
+ ?: Manga.create(source).apply {
+ this.url = pathOnlyUrl
+ title = url
+ }
+
+ //Copy basics
+ manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
+
+ //Apply metadata
+ metadataHelper.fetchEhMetadata(url, isExSource(source))?.copyTo(manga)
+
+ if(fav) manga.favorite = true
+
+ db.insertManga(manga).executeAsBlocking().insertedId()?.let {
+ manga.id = it
+ }
+
+ //Fetch and copy chapters
+ try {
+ sourceObj.fetchChapterList(manga).map {
+ syncChaptersWithSource(db, it, manga, sourceObj)
+ }.toBlocking().first()
+ } catch (e: Exception) {
+ Timber.w(e, "Failed to update chapters for gallery: ${manga.title}!")
+ }
+
+ return manga
+ }
+
+ private fun getUrlWithoutDomain(orig: String): String {
+ try {
+ val uri = URI(orig)
+ var out = uri.path
+ if (uri.query != null)
+ out += "?" + uri.query
+ if (uri.fragment != null)
+ out += "#" + uri.fragment
+ return out
+ } catch (e: URISyntaxException) {
+ return orig
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/StringBuilderExtensions.kt b/app/src/main/java/exh/StringBuilderExtensions.kt
new file mode 100755
index 000000000..1dffedbb0
--- /dev/null
+++ b/app/src/main/java/exh/StringBuilderExtensions.kt
@@ -0,0 +1,3 @@
+package exh
+
+operator fun StringBuilder.plusAssign(other: String) { append(other) }
diff --git a/app/src/main/java/exh/VerbelExpressionExtensions.kt b/app/src/main/java/exh/VerbelExpressionExtensions.kt
new file mode 100755
index 000000000..12f060076
--- /dev/null
+++ b/app/src/main/java/exh/VerbelExpressionExtensions.kt
@@ -0,0 +1,5 @@
+package exh
+
+import ru.lanwen.verbalregex.VerbalExpression
+
+fun VerbalExpression.Builder.anyChar() = add(".")!!
diff --git a/app/src/main/java/exh/metadata/MetadataHelper.kt b/app/src/main/java/exh/metadata/MetadataHelper.kt
new file mode 100755
index 000000000..81150bf02
--- /dev/null
+++ b/app/src/main/java/exh/metadata/MetadataHelper.kt
@@ -0,0 +1,62 @@
+package exh.metadata
+
+import exh.*
+import exh.metadata.models.ExGalleryMetadata
+import exh.metadata.models.NHentaiMetadata
+import exh.metadata.models.PervEdenGalleryMetadata
+import exh.metadata.models.SearchableGalleryMetadata
+import io.paperdb.Paper
+
+class MetadataHelper {
+
+ fun writeGallery(galleryMetadata: SearchableGalleryMetadata, source: Long)
+ = (if(isExSource(source) || isEhSource(source)) exGalleryBook()
+ else if(isPervEdenSource(source)) pervEdenGalleryBook()
+ else if(isNhentaiSource(source)) nhentaiGalleryBook()
+ else null)?.write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!!
+
+ fun fetchEhMetadata(url: String, exh: Boolean): ExGalleryMetadata?
+ = ExGalleryMetadata().let {
+ it.url = url
+ it.exh = exh
+ return exGalleryBook().read(it.galleryUniqueIdentifier())
+ }
+
+ fun fetchPervEdenMetadata(url: String, source: Long): PervEdenGalleryMetadata?
+ = PervEdenGalleryMetadata().let {
+ it.url = url
+ if(source == PERV_EDEN_EN_SOURCE_ID)
+ it.lang = "en"
+ else if(source == PERV_EDEN_IT_SOURCE_ID)
+ it.lang = "it"
+ else throw IllegalArgumentException("Invalid source id!")
+ return pervEdenGalleryBook().read(it.galleryUniqueIdentifier())
+ }
+
+ fun fetchNhentaiMetadata(url: String) = NHentaiMetadata().let {
+ it.url = url
+ nhentaiGalleryBook().read(it.galleryUniqueIdentifier())
+ }
+
+ fun fetchMetadata(url: String, source: Long): SearchableGalleryMetadata? {
+ if(isExSource(source) || isEhSource(source)) {
+ return fetchEhMetadata(url, isExSource(source))
+ } else if(isPervEdenSource(source)) {
+ return fetchPervEdenMetadata(url, source)
+ } else if(isNhentaiSource(source)) {
+ return fetchNhentaiMetadata(url)
+ } else {
+ return null
+ }
+ }
+
+ fun getAllGalleries() = exGalleryBook().allKeys.map {
+ exGalleryBook().read(it)
+ }
+
+ fun exGalleryBook() = Paper.book("gallery-ex")!!
+
+ fun pervEdenGalleryBook() = Paper.book("gallery-perveden")!!
+
+ fun nhentaiGalleryBook() = Paper.book("gallery-nhentai")!!
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/metadata/MetadataUtil.kt b/app/src/main/java/exh/metadata/MetadataUtil.kt
new file mode 100755
index 000000000..73a205ea1
--- /dev/null
+++ b/app/src/main/java/exh/metadata/MetadataUtil.kt
@@ -0,0 +1,47 @@
+package exh.metadata
+
+/**
+ * Metadata utils
+ */
+fun humanReadableByteCount(bytes: Long, si: Boolean): String {
+ val unit = if (si) 1000 else 1024
+ if (bytes < unit) return bytes.toString() + " B"
+ val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt()
+ val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
+ return String.format("%.1f %sB", bytes / Math.pow(unit.toDouble(), exp.toDouble()), pre)
+}
+
+private val KB_FACTOR: Long = 1000
+private val KIB_FACTOR: Long = 1024
+private val MB_FACTOR = 1000 * KB_FACTOR
+private val MIB_FACTOR = 1024 * KIB_FACTOR
+private val GB_FACTOR = 1000 * MB_FACTOR
+private val GIB_FACTOR = 1024 * MIB_FACTOR
+
+fun parseHumanReadableByteCount(arg0: String): Double? {
+ val spaceNdx = arg0.indexOf(" ")
+ val ret = java.lang.Double.parseDouble(arg0.substring(0, spaceNdx))
+ when (arg0.substring(spaceNdx + 1)) {
+ "GB" -> return ret * GB_FACTOR
+ "GiB" -> return ret * GIB_FACTOR
+ "MB" -> return ret * MB_FACTOR
+ "MiB" -> return ret * MIB_FACTOR
+ "KB" -> return ret * KB_FACTOR
+ "KiB" -> return ret * KIB_FACTOR
+ }
+ return null
+}
+
+
+fun String?.nullIfBlank(): String? = if(isNullOrBlank())
+ null
+else
+ this
+
+fun ignore(expr: () -> T): T? {
+ return try { expr() } catch (t: Throwable) { null }
+}
+
+fun Set>.forEach(action: (K, V) -> Unit) {
+ forEach { action(it.key, it.value) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/metadata/MetdataCopier.kt b/app/src/main/java/exh/metadata/MetdataCopier.kt
new file mode 100755
index 000000000..ed5744983
--- /dev/null
+++ b/app/src/main/java/exh/metadata/MetdataCopier.kt
@@ -0,0 +1,218 @@
+package exh.metadata
+
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.all.EHentai
+import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata
+import eu.kanade.tachiyomi.source.online.all.PervEden
+import exh.metadata.models.*
+import exh.plusAssign
+import uy.kohesive.injekt.injectLazy
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * Copies gallery metadata to a manga object
+ */
+
+private const val EH_ARTIST_NAMESPACE = "artist"
+private const val EH_AUTHOR_NAMESPACE = "author"
+
+private const val NHENTAI_ARTIST_NAMESPACE = "artist"
+private const val NHENTAI_CATEGORIES_NAMESPACE = "category"
+
+private val ONGOING_SUFFIX = arrayOf(
+ "[ongoing]",
+ "(ongoing)",
+ "{ongoing}"
+)
+
+val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
+
+private val prefs: PreferencesHelper by injectLazy()
+
+fun ExGalleryMetadata.copyTo(manga: SManga) {
+ //TODO Find some way to do this with SManga
+ /*exh?.let {
+ manga.source = if(it)
+ 2
+ else
+ 1
+ }*/
+ url?.let { manga.url = it }
+ thumbnailUrl?.let { manga.thumbnail_url = it }
+
+ //No title bug?
+ val titleObj = if(prefs.useJapaneseTitle().getOrDefault())
+ altTitle ?: title
+ else
+ title
+ titleObj?.let { manga.title = it }
+
+ //Set artist (if we can find one)
+ tags[EH_ARTIST_NAMESPACE]?.let {
+ if(it.isNotEmpty()) manga.artist = it.joinToString(transform = Tag::name)
+ }
+ //Set author (if we can find one)
+ tags[EH_AUTHOR_NAMESPACE]?.let {
+ if(it.isNotEmpty()) manga.author = it.joinToString(transform = Tag::name)
+ }
+ //Set genre
+ genre?.let { manga.genre = it }
+
+ //Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
+ //We default to completed
+ manga.status = SManga.COMPLETED
+ title?.let { t ->
+ ONGOING_SUFFIX.find {
+ t.endsWith(it, ignoreCase = true)
+ }?.let {
+ manga.status = SManga.ONGOING
+ }
+ }
+
+ //Build a nice looking description out of what we know
+ val titleDesc = StringBuilder()
+ title?.let { titleDesc += "Title: $it\n" }
+ altTitle?.let { titleDesc += "Alternate Title: $it\n" }
+
+ val detailsDesc = StringBuilder()
+ uploader?.let { detailsDesc += "Uploader: $it\n" }
+ datePosted?.let { detailsDesc += "Posted: ${EX_DATE_FORMAT.format(Date(it))}\n" }
+ visible?.let { detailsDesc += "Visible: $it\n" }
+ language?.let {
+ detailsDesc += "Language: $it"
+ if(translated == true) detailsDesc += " TR"
+ detailsDesc += "\n"
+ }
+ size?.let { detailsDesc += "File Size: ${humanReadableByteCount(it, true)}\n" }
+ length?.let { detailsDesc += "Length: $it pages\n" }
+ favorites?.let { detailsDesc += "Favorited: $it times\n" }
+ averageRating?.let {
+ detailsDesc += "Rating: $it"
+ ratingCount?.let { detailsDesc += " ($it)" }
+ detailsDesc += "\n"
+ }
+
+ val tagsDesc = buildTagsDescription(this)
+
+ manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
+ .filter(String::isNotBlank)
+ .joinToString(separator = "\n")
+}
+
+fun PervEdenGalleryMetadata.copyTo(manga: SManga) {
+ url?.let { manga.url = it }
+ thumbnailUrl?.let { manga.thumbnail_url = it }
+
+ val titleDesc = StringBuilder()
+ title?.let {
+ manga.title = it
+ titleDesc += "Title: $it\n"
+ }
+ if(altTitles.isNotEmpty())
+ titleDesc += "Alternate Titles: \n" + altTitles.map {
+ "⪠$it"
+ }.joinToString(separator = "\n", postfix = "\n")
+
+ val detailsDesc = StringBuilder()
+ artist?.let {
+ manga.artist = it
+ detailsDesc += "Artist: $it\n"
+ }
+
+ type?.let {
+ manga.genre = it
+ detailsDesc += "Type: $it\n"
+ }
+
+ status?.let {
+ manga.status = when(it) {
+ "Ongoing" -> SManga.ONGOING
+ "Completed", "Suspended" -> SManga.COMPLETED
+ else -> SManga.UNKNOWN
+ }
+ detailsDesc += "Status: $it\n"
+ }
+
+ rating?.let {
+ detailsDesc += "Rating: %.2\n".format(it)
+ }
+
+ val tagsDesc = buildTagsDescription(this)
+
+ manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
+ .filter(String::isNotBlank)
+ .joinToString(separator = "\n")
+}
+
+fun NHentaiMetadata.copyTo(manga: SManga) {
+ url?.let { manga.url = it }
+
+ //TODO next update allow this to be changed to use HD covers
+ if(mediaId != null)
+ NHentaiMetadata.typeToExtension(thumbnailImageType)?.let {
+ manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/thumb.$it"
+ }
+
+ manga.title = englishTitle ?: japaneseTitle ?: shortTitle!!
+
+ //Set artist (if we can find one)
+ tags[NHENTAI_ARTIST_NAMESPACE]?.let {
+ if(it.isNotEmpty()) manga.artist = it.joinToString(transform = Tag::name)
+ }
+
+ tags[NHENTAI_CATEGORIES_NAMESPACE]?.let {
+ if(it.isNotEmpty()) manga.genre = it.joinToString(transform = Tag::name)
+ }
+
+ //Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
+ //We default to completed
+ manga.status = SManga.COMPLETED
+ englishTitle?.let { t ->
+ ONGOING_SUFFIX.find {
+ t.endsWith(it, ignoreCase = true)
+ }?.let {
+ manga.status = SManga.ONGOING
+ }
+ }
+
+ val titleDesc = StringBuilder()
+ englishTitle?.let { titleDesc += "English Title: $it\n" }
+ japaneseTitle?.let { titleDesc += "Japanese Title: $it\n" }
+ shortTitle?.let { titleDesc += "Short Title: $it\n" }
+
+ val detailsDesc = StringBuilder()
+ uploadDate?.let { detailsDesc += "Upload Date: ${EX_DATE_FORMAT.format(Date(it))}\n" }
+ pageImageTypes.size.let { detailsDesc += "Length: $it pages\n" }
+ favoritesCount?.let { detailsDesc += "Favorited: $it times\n" }
+ scanlator?.nullIfBlank()?.let { detailsDesc += "Scanlator: $it\n" }
+
+ val tagsDesc = buildTagsDescription(this)
+
+ manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
+ .filter(String::isNotBlank)
+ .joinToString(separator = "\n")
+}
+
+fun SearchableGalleryMetadata.genericCopyTo(manga: SManga): Boolean {
+ when(this) {
+ is ExGalleryMetadata -> this.copyTo(manga)
+ is PervEdenGalleryMetadata -> this.copyTo(manga)
+ is NHentaiMetadata -> this.copyTo(manga)
+ else -> return false
+ }
+ return true
+}
+
+private fun buildTagsDescription(metadata: SearchableGalleryMetadata)
+ = StringBuilder("Tags:\n").apply {
+ //BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
+ metadata.tags.entries.forEach { namespace, tags ->
+ if (tags.isNotEmpty()) {
+ val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
+ this += "⪠$namespace: $joinedTags\n"
+ }
+ }
+ }
diff --git a/app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt b/app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt
new file mode 100755
index 000000000..2ce181523
--- /dev/null
+++ b/app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt
@@ -0,0 +1,51 @@
+package exh.metadata.models
+
+import android.net.Uri
+import java.util.*
+
+/**
+ * Gallery metadata storage model
+ */
+
+class ExGalleryMetadata : SearchableGalleryMetadata() {
+ var url: String? = null
+
+ var exh: Boolean? = null
+
+ var thumbnailUrl: String? = null
+
+ var title: String? = null
+ var altTitle: String? = null
+
+ var genre: String? = null
+
+ var datePosted: Long? = null
+ var parent: String? = null
+ var visible: String? = null //Not a boolean
+ var language: String? = null
+ var translated: Boolean? = null
+ var size: Long? = null
+ var length: Int? = null
+ var favorites: Int? = null
+ var ratingCount: Int? = null
+ var averageRating: Double? = null
+
+ override fun getTitles() = listOf(title, altTitle).filterNotNull()
+
+ private fun splitGalleryUrl()
+ = url?.let {
+ Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
+ }
+
+ fun galleryId() = splitGalleryUrl()?.let { it[it.size - 2] }
+
+ fun galleryToken() =
+ splitGalleryUrl()?.last()
+
+ override fun galleryUniqueIdentifier() = exh?.let { exh ->
+ url?.let {
+ //Fuck, this should be EXH and EH but it's too late to change it now...
+ "${if(exh) "EXH" else "EX"}-${galleryId()}-${galleryToken()}"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/metadata/models/NHentaiMetadata.kt b/app/src/main/java/exh/metadata/models/NHentaiMetadata.kt
new file mode 100755
index 000000000..52c7f2000
--- /dev/null
+++ b/app/src/main/java/exh/metadata/models/NHentaiMetadata.kt
@@ -0,0 +1,48 @@
+package exh.metadata.models
+
+/**
+ * NHentai metadata
+ */
+
+class NHentaiMetadata : SearchableGalleryMetadata() {
+
+ var id: Long? = null
+
+ var url get() = id?.let { "$BASE_URL/g/$it" }
+ set(a) {
+ a?.let {
+ id = a.split("/").last().toLong()
+ }
+ }
+
+ var uploadDate: Long? = null
+
+ var favoritesCount: Long? = null
+
+ var mediaId: String? = null
+
+ var japaneseTitle: String? = null
+ var englishTitle: String? = null
+ var shortTitle: String? = null
+
+ var coverImageType: String? = null
+ var pageImageTypes: MutableList = mutableListOf()
+ var thumbnailImageType: String? = null
+
+ var scanlator: String? = null
+
+ override fun galleryUniqueIdentifier(): String? = "NHENTAI-$id"
+
+ override fun getTitles() = listOf(japaneseTitle, englishTitle, shortTitle).filterNotNull()
+
+ companion object {
+ val BASE_URL = "https://nhentai.net"
+
+ fun typeToExtension(t: String?) =
+ when(t) {
+ "p" -> "png"
+ "j" -> "jpg"
+ else -> null
+ }
+ }
+}
diff --git a/app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt b/app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt
new file mode 100755
index 000000000..b9770aa7a
--- /dev/null
+++ b/app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt
@@ -0,0 +1,32 @@
+package exh.metadata.models
+
+import android.net.Uri
+
+class PervEdenGalleryMetadata : SearchableGalleryMetadata() {
+ var url: String? = null
+ var thumbnailUrl: String? = null
+
+ var title: String? = null
+ var altTitles: MutableList = mutableListOf()
+
+ var artist: String? = null
+
+ var type: String? = null
+
+ var rating: Float? = null
+
+ var status: String? = null
+
+ var lang: String? = null
+
+ override fun getTitles() = listOf(title).plus(altTitles).filterNotNull()
+
+ private fun splitGalleryUrl()
+ = url?.let {
+ Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
+ }
+
+ override fun galleryUniqueIdentifier() = splitGalleryUrl()?.let {
+ "PERVEDEN-${lang?.toUpperCase()}-${it.last()}"
+ }
+}
diff --git a/app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt b/app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt
new file mode 100755
index 000000000..bc33eef24
--- /dev/null
+++ b/app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt
@@ -0,0 +1,18 @@
+package exh.metadata.models
+
+import java.util.ArrayList
+import java.util.HashMap
+
+/**
+ * A gallery that can be searched using the EH search engine
+ */
+abstract class SearchableGalleryMetadata {
+ var uploader: String? = null
+
+ //Being specific about which classes are used in generics to make deserialization easier
+ val tags: HashMap> = HashMap()
+
+ abstract fun galleryUniqueIdentifier(): String?
+
+ abstract fun getTitles(): List
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/metadata/models/Tag.kt b/app/src/main/java/exh/metadata/models/Tag.kt
new file mode 100755
index 000000000..9b78b0c35
--- /dev/null
+++ b/app/src/main/java/exh/metadata/models/Tag.kt
@@ -0,0 +1,7 @@
+package exh.metadata.models
+
+/**
+ * Simple tag model
+ */
+
+data class Tag(var name: String, var light: Boolean)
diff --git a/app/src/main/java/exh/search/MultiWildcard.kt b/app/src/main/java/exh/search/MultiWildcard.kt
new file mode 100755
index 000000000..b5cecbe57
--- /dev/null
+++ b/app/src/main/java/exh/search/MultiWildcard.kt
@@ -0,0 +1,3 @@
+package exh.search
+
+class MultiWildcard : TextComponent()
diff --git a/app/src/main/java/exh/search/Namespace.kt b/app/src/main/java/exh/search/Namespace.kt
new file mode 100755
index 000000000..e23a0e722
--- /dev/null
+++ b/app/src/main/java/exh/search/Namespace.kt
@@ -0,0 +1,4 @@
+package exh.search
+
+class Namespace(var namespace: String,
+ var tag: Text? = null) : QueryComponent()
diff --git a/app/src/main/java/exh/search/QueryComponent.kt b/app/src/main/java/exh/search/QueryComponent.kt
new file mode 100755
index 000000000..bfc34eea0
--- /dev/null
+++ b/app/src/main/java/exh/search/QueryComponent.kt
@@ -0,0 +1,6 @@
+package exh.search
+
+open class QueryComponent {
+ var excluded = false
+ var exact = false
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/search/SearchEngine.kt b/app/src/main/java/exh/search/SearchEngine.kt
new file mode 100755
index 000000000..eb4d6f5a2
--- /dev/null
+++ b/app/src/main/java/exh/search/SearchEngine.kt
@@ -0,0 +1,150 @@
+package exh.search
+
+import exh.metadata.models.SearchableGalleryMetadata
+import exh.metadata.models.Tag
+
+class SearchEngine {
+
+ private val queryCache = mutableMapOf>()
+
+ fun matches(metadata: SearchableGalleryMetadata, query: List): Boolean {
+
+ fun matchTagList(tags: Sequence,
+ component: Text): Boolean {
+ //Match tags
+ val tagMatcher = if(!component.exact)
+ component.asLenientRegex()
+ else
+ component.asRegex()
+ //Match beginning of tag
+ if (tags.find {
+ tagMatcher.testExact(it.name)
+ } != null) {
+ if(component.excluded) return false
+ } else {
+ //No tag matched for this component
+ return false
+ }
+ return true
+ }
+
+ val cachedTitles = metadata.getTitles().map(String::toLowerCase)
+
+ for(component in query) {
+ if(component is Text) {
+ //Match title
+ if (cachedTitles.find { component.asRegex().test(it) } != null) {
+ continue
+ }
+ //Match tags
+ if(!matchTagList(metadata.tags.entries.asSequence().flatMap { it.value.asSequence() },
+ component)) return false
+ } else if(component is Namespace) {
+ if(component.namespace == "uploader") {
+ //Match uploader
+ if(!component.tag?.rawTextOnly().equals(metadata.uploader,
+ ignoreCase = true)) {
+ return false
+ }
+ } else {
+ if(component.tag!!.components.size > 0) {
+ //Match namespace
+ val ns = metadata.tags.entries.asSequence().filter {
+ it.key == component.namespace
+ }.flatMap { it.value.asSequence() }
+ //Match tags
+ if (!matchTagList(ns, component.tag!!))
+ return false
+ } else {
+ //Perform namespace search
+ val hasNs = metadata.tags.entries.find {
+ it.key == component.namespace
+ } != null
+
+ if(hasNs && component.excluded)
+ return false
+ else if(!hasNs && !component.excluded)
+ return false
+ }
+ }
+ }
+ }
+ return true
+ }
+
+ fun parseQuery(query: String) = queryCache.getOrPut(query, {
+ val res = mutableListOf()
+
+ var inQuotes = false
+ val queuedRawText = StringBuilder()
+ val queuedText = mutableListOf()
+ var namespace: Namespace? = null
+
+ var nextIsExcluded = false
+ var nextIsExact = false
+
+ fun flushText() {
+ if(queuedRawText.isNotEmpty()) {
+ queuedText += StringTextComponent(queuedRawText.toString())
+ queuedRawText.setLength(0)
+ }
+ }
+
+ fun flushToText() = Text().apply {
+ components += queuedText
+ queuedText.clear()
+ }
+
+ fun flushAll() {
+ flushText()
+ if (queuedText.isNotEmpty() || namespace != null) {
+ val component = namespace?.apply {
+ tag = flushToText()
+ namespace = null
+ } ?: flushToText()
+ component.excluded = nextIsExcluded
+ component.exact = nextIsExact
+ res += component
+ }
+ }
+
+ for(char in query.toLowerCase()) {
+ if(char == '"') {
+ inQuotes = !inQuotes
+ } else if(char == '?' || char == '_') {
+ flushText()
+ queuedText.add(SingleWildcard())
+ } else if(char == '*' || char == '%') {
+ flushText()
+ queuedText.add(MultiWildcard())
+ } else if(char == '-') {
+ nextIsExcluded = true
+ } else if(char == '$') {
+ nextIsExact = true
+ } else if(char == ':') {
+ flushText()
+ var flushed = flushToText().rawTextOnly()
+ //Map tag aliases
+ flushed = when(flushed) {
+ "a" -> "artist"
+ "c", "char" -> "character"
+ "f" -> "female"
+ "g", "creator", "circle" -> "group"
+ "l", "lang" -> "language"
+ "m" -> "male"
+ "p", "series" -> "parody"
+ "r" -> "reclass"
+ else -> flushed
+ }
+ namespace = Namespace(flushed, null)
+ } else if(char == ' ' && !inQuotes) {
+ flushAll()
+ } else {
+ queuedRawText.append(char)
+ }
+ }
+ flushAll()
+
+ res
+ })
+}
diff --git a/app/src/main/java/exh/search/SingleWildcard.kt b/app/src/main/java/exh/search/SingleWildcard.kt
new file mode 100755
index 000000000..503d751e1
--- /dev/null
+++ b/app/src/main/java/exh/search/SingleWildcard.kt
@@ -0,0 +1,3 @@
+package exh.search
+
+class SingleWildcard : TextComponent()
diff --git a/app/src/main/java/exh/search/StringTextComponent.kt b/app/src/main/java/exh/search/StringTextComponent.kt
new file mode 100755
index 000000000..736f8c225
--- /dev/null
+++ b/app/src/main/java/exh/search/StringTextComponent.kt
@@ -0,0 +1,3 @@
+package exh.search
+
+class StringTextComponent(val value: String) : TextComponent()
diff --git a/app/src/main/java/exh/search/Text.kt b/app/src/main/java/exh/search/Text.kt
new file mode 100755
index 000000000..5bccb2671
--- /dev/null
+++ b/app/src/main/java/exh/search/Text.kt
@@ -0,0 +1,49 @@
+package exh.search
+
+import exh.anyChar
+import ru.lanwen.verbalregex.VerbalExpression
+
+class Text: QueryComponent() {
+ val components = mutableListOf()
+
+ private var regex: VerbalExpression? = null
+ private var lenientRegex: VerbalExpression? = null
+ private var rawText: String? = null
+
+ fun asRegex(): VerbalExpression {
+ if(regex == null) {
+ regex = baseBuilder().build()
+ }
+ return regex!!
+ }
+
+ fun asLenientRegex(): VerbalExpression {
+ if(lenientRegex == null) {
+ lenientRegex = baseBuilder().anything().build()
+ }
+ return lenientRegex!!
+ }
+
+ fun baseBuilder(): VerbalExpression.Builder {
+ val builder = VerbalExpression.regex()
+ for(component in components) {
+ when(component) {
+ is StringTextComponent -> builder.then(component.value)
+ is SingleWildcard -> builder.anyChar()
+ is MultiWildcard -> builder.anything()
+ }
+ }
+ return builder
+ }
+
+ fun rawTextOnly() = if(rawText != null)
+ rawText!!
+ else {
+ rawText = components
+ .filter { it is StringTextComponent }
+ .joinToString(separator = "", transform = {
+ (it as StringTextComponent).value
+ })
+ rawText!!
+ }
+}
diff --git a/app/src/main/java/exh/search/TextComponent.kt b/app/src/main/java/exh/search/TextComponent.kt
new file mode 100755
index 000000000..9b50051f5
--- /dev/null
+++ b/app/src/main/java/exh/search/TextComponent.kt
@@ -0,0 +1,3 @@
+package exh.search
+
+open class TextComponent
diff --git a/app/src/main/java/exh/ui/batchadd/BatchAddFragment.kt b/app/src/main/java/exh/ui/batchadd/BatchAddFragment.kt
new file mode 100755
index 000000000..a4d994d0b
--- /dev/null
+++ b/app/src/main/java/exh/ui/batchadd/BatchAddFragment.kt
@@ -0,0 +1,131 @@
+package exh.ui.batchadd
+
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
+import exh.GalleryAdder
+import exh.metadata.nullIfBlank
+import kotlinx.android.synthetic.main.eh_fragment_batch_add.*
+import timber.log.Timber
+import kotlin.concurrent.thread
+
+/**
+ * LoginActivity
+ */
+
+class BatchAddFragment : BaseFragment() {
+
+ private val galleryAdder by lazy { GalleryAdder() }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?)
+ = inflater.inflate(R.layout.eh_fragment_batch_add, container, false)!!
+
+ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+ setToolbarTitle("Batch add")
+
+ setup()
+ }
+
+ fun setup() {
+ btn_add_galleries.setOnClickListener {
+ val galleries = galleries_box.text.toString()
+ //Check text box has content
+ if(galleries.isNullOrBlank()) {
+ noGalleriesSpecified()
+ return@setOnClickListener
+ }
+
+ //Too lazy to actually deal with orientation changes
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
+
+ val splitGalleries = galleries.split("\n").map {
+ it.trim().nullIfBlank()
+ }.filterNotNull()
+
+ val dialog = MaterialDialog.Builder(context)
+ .title("Adding galleries...")
+ .progress(false, splitGalleries.size, true)
+ .cancelable(false)
+ .canceledOnTouchOutside(false)
+ .show()
+
+ val succeeded = mutableListOf()
+ val failed = mutableListOf()
+
+ thread {
+ splitGalleries.forEachIndexed { i, s ->
+ activity.runOnUiThread {
+ dialog.setContent("Processing: $s")
+ }
+ if(addGallery(s)) {
+ succeeded.add(s)
+ } else {
+ failed.add(s)
+ }
+ activity.runOnUiThread {
+ dialog.setProgress(i + 1)
+ }
+ }
+
+ //Show report
+ val succeededCount = succeeded.size
+ val failedCount = failed.size
+
+ if(succeeded.isEmpty()) succeeded += "None"
+ if(failed.isEmpty()) failed += "None"
+ val succeededReport = succeeded.joinToString(separator = "\n", prefix = "Added:\n")
+ val failedReport = failed.joinToString(separator = "\n", prefix = "Failed:\n")
+
+ val summary = "Summary:\nAdded: $succeededCount gallerie(s)\nFailed: $failedCount gallerie(s)"
+
+ val report = listOf(succeededReport, failedReport, summary).joinToString(separator = "\n\n")
+
+ activity.runOnUiThread {
+ //Enable orientation changes again
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
+
+ dialog.dismiss()
+
+ MaterialDialog.Builder(context)
+ .title("Batch add report")
+ .content(report)
+ .positiveText("Ok")
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .show()
+ }
+ }
+
+ }
+ }
+
+ fun addGallery(url: String): Boolean {
+ try {
+ galleryAdder.addGallery(url, true)
+ } catch(t: Throwable) {
+ Timber.e(t, "Could not add gallery!")
+ return false
+ }
+ return true
+ }
+
+ fun noGalleriesSpecified() {
+ MaterialDialog.Builder(context)
+ .title("No galleries to add!")
+ .content("You must specify at least one gallery to add!")
+ .positiveText("Ok")
+ .onPositive { materialDialog, _ -> materialDialog.dismiss() }
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .show()
+ }
+
+ companion object {
+ fun newInstance() = BatchAddFragment()
+ }
+}
diff --git a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt
new file mode 100755
index 000000000..d46811b7d
--- /dev/null
+++ b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt
@@ -0,0 +1,82 @@
+package exh.ui.intercept
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.MenuItem
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
+import eu.kanade.tachiyomi.ui.manga.MangaActivity
+import exh.GalleryAdder
+import kotlinx.android.synthetic.main.toolbar.*
+import timber.log.Timber
+import kotlin.concurrent.thread
+
+class InterceptActivity : BaseActivity() {
+
+ private val galleryAdder = GalleryAdder()
+
+ var finished = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setAppTheme()
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.eh_activity_intercept)
+
+ setupToolbar(toolbar, backNavigation = false)
+
+ if(savedInstanceState == null)
+ thread { setup() }
+ }
+
+ fun setup() {
+ try {
+ processLink()
+ } catch(t: Throwable) {
+ Timber.e(t, "Could not intercept link!")
+ if(!finished)
+ runOnUiThread {
+ MaterialDialog.Builder(this)
+ .title("Error")
+ .content("Could not load this gallery!")
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .cancelListener { onBackPressed() }
+ .positiveText("Ok")
+ .onPositive { _, _ -> onBackPressed() }
+ .dismissListener { onBackPressed() }
+ .show()
+ }
+ }
+ }
+
+ fun processLink() {
+ if(Intent.ACTION_VIEW == intent.action) {
+ val manga = galleryAdder.addGallery(intent.dataString)
+
+ if(!finished)
+ startActivity(MangaActivity.newIntent(this, manga, true))
+ onBackPressed()
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> onBackPressed()
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ override fun onBackPressed() {
+ if(!finished)
+ runOnUiThread {
+ super.onBackPressed()
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ finished = true
+ }
+}
diff --git a/app/src/main/java/exh/ui/lock/LockActivity.kt b/app/src/main/java/exh/ui/lock/LockActivity.kt
new file mode 100755
index 000000000..a8e428541
--- /dev/null
+++ b/app/src/main/java/exh/ui/lock/LockActivity.kt
@@ -0,0 +1,60 @@
+package exh.ui.lock
+
+import android.os.Bundle
+import com.afollestad.materialdialogs.MaterialDialog
+import com.andrognito.pinlockview.PinLockListener
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
+import kotlinx.android.synthetic.main.activity_lock.*
+import uy.kohesive.injekt.injectLazy
+
+class LockActivity : BaseActivity() {
+
+ val prefs: PreferencesHelper by injectLazy()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ disableLock = true
+
+ setTheme(R.style.Theme_Tachiyomi_Dark)
+ super.onCreate(savedInstanceState)
+
+ if(!lockEnabled(prefs)) {
+ finish()
+ return
+ }
+
+ setContentView(R.layout.activity_lock)
+
+ pin_lock_view.attachIndicatorDots(indicator_dots)
+
+ pin_lock_view.pinLength = prefs.lockLength().getOrDefault()
+ pin_lock_view.setPinLockListener(object : PinLockListener {
+ override fun onEmpty() {}
+
+ override fun onComplete(pin: String) {
+ if(sha512(pin, prefs.lockSalt().get()!!) == prefs.lockHash().get()) {
+ //Yay!
+ finish()
+ } else {
+ MaterialDialog.Builder(this@LockActivity)
+ .title("PIN code incorrect")
+ .content("The PIN code you entered is incorrect. Please try again.")
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .positiveText("Ok")
+ .autoDismiss(true)
+ .show()
+ pin_lock_view.resetPinLockView()
+ }
+ }
+
+ override fun onPinChange(pinLength: Int, intermediatePin: String?) {}
+ })
+ }
+
+ override fun onBackPressed() {
+ moveTaskToBack(true)
+ }
+}
diff --git a/app/src/main/java/exh/ui/lock/LockPreference.kt b/app/src/main/java/exh/ui/lock/LockPreference.kt
new file mode 100755
index 000000000..cc7da2646
--- /dev/null
+++ b/app/src/main/java/exh/ui/lock/LockPreference.kt
@@ -0,0 +1,85 @@
+package exh.ui.lock
+
+import android.content.Context
+import android.support.v7.preference.Preference
+import android.text.InputType
+import android.util.AttributeSet
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import rx.Observable
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
+import uy.kohesive.injekt.injectLazy
+import java.math.BigInteger
+import java.security.SecureRandom
+
+class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ Preference(context, attrs) {
+
+ val secureRandom by lazy { SecureRandom() }
+
+ val prefs: PreferencesHelper by injectLazy()
+
+ override fun onAttached() {
+ super.onAttached()
+ updateSummary()
+ }
+
+ fun updateSummary() {
+ if(lockEnabled(prefs)) {
+ summary = "Application is locked"
+ } else {
+ summary = "Application is not locked, tap to lock"
+ }
+ }
+
+ override fun onClick() {
+ super.onClick()
+ if(!notifyLockSecurity(context)) {
+ MaterialDialog.Builder(context)
+ .title("Lock application")
+ .content("Enter a pin to lock the application. Enter nothing to disable the pin lock.")
+ .inputRangeRes(0, 10, R.color.material_red_500)
+ .inputType(InputType.TYPE_CLASS_NUMBER)
+ .input("", "", { _, c ->
+ val progressDialog = MaterialDialog.Builder(context)
+ .title("Saving password")
+ .progress(true, 0)
+ .cancelable(false)
+ .show()
+ Observable.fromCallable {
+ savePassword(c.toString())
+ }.subscribeOn(Schedulers.computation())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ progressDialog.dismiss()
+ updateSummary()
+ }
+ })
+ .negativeText("Cancel")
+ .autoDismiss(true)
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .show()
+ }
+ }
+
+ fun savePassword(password: String) {
+ val salt: String?
+ val hash: String?
+ val length: Int
+ if(password.isEmpty()) {
+ salt = null
+ hash = null
+ length = -1
+ } else {
+ salt = BigInteger(130, secureRandom).toString(32)
+ hash = sha512(password, salt)
+ length = password.length
+ }
+ prefs.lockSalt().set(salt)
+ prefs.lockHash().set(hash)
+ prefs.lockLength().set(length)
+ }
+}
diff --git a/app/src/main/java/exh/ui/lock/LockUtils.kt b/app/src/main/java/exh/ui/lock/LockUtils.kt
new file mode 100755
index 000000000..e2e88897b
--- /dev/null
+++ b/app/src/main/java/exh/ui/lock/LockUtils.kt
@@ -0,0 +1,91 @@
+package exh.ui.lock
+
+import android.annotation.TargetApi
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.provider.Settings
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.security.MessageDigest
+import kotlin.experimental.and
+
+
+/**
+ * Password hashing utils
+ */
+
+/**
+ * Yes, I know SHA512 is fast, but bcrypt on mobile devices is too slow apparently
+ */
+fun sha512(passwordToHash: String, salt: String): String {
+ val md = MessageDigest.getInstance("SHA-512")
+ md.update(salt.toByteArray(charset("UTF-8")))
+ val bytes = md.digest(passwordToHash.toByteArray(charset("UTF-8")))
+ val sb = StringBuilder()
+ for (i in bytes.indices) {
+ sb.append(Integer.toString((bytes[i] and 0xff.toByte()) + 0x100, 16).substring(1))
+ }
+ return sb.toString()
+}
+
+/**
+ * Check if lock is enabled
+ */
+fun lockEnabled(prefs: PreferencesHelper = Injekt.get())
+ = prefs.lockHash().get() != null
+ && prefs.lockSalt().get() != null
+ && prefs.lockLength().getOrDefault() != -1
+
+/**
+ * Lock the screen
+ */
+fun showLockActivity(activity: Activity) {
+ activity.startActivity(Intent(activity, LockActivity::class.java))
+}
+
+/**
+ * Check if the lock will function properly
+ *
+ * @return true if action is required, false if lock is working properly
+ */
+fun notifyLockSecurity(context: Context): Boolean {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !hasAccessToUsageStats(context)) {
+ MaterialDialog.Builder(context)
+ .title("Permission required")
+ .content("${context.getString(R.string.app_name)} requires the usage stats permission to detect when you leave the app. " +
+ "This is required for the application lock to function properly. " +
+ "Press OK to grant this permission now.")
+ .negativeText("Cancel")
+ .positiveText("Ok")
+ .onPositive { _, _ ->
+ context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
+ }
+ .autoDismiss(true)
+ .cancelable(false)
+ .show()
+ return true
+ } else {
+ return false
+ }
+}
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+fun hasAccessToUsageStats(context: Context): Boolean {
+ try {
+ val packageManager = context.packageManager
+ val applicationInfo = packageManager.getApplicationInfo(context.packageName, 0)
+ val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+ val mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName)
+ return (mode == AppOpsManager.MODE_ALLOWED)
+ } catch (e: PackageManager.NameNotFoundException) {
+ return false
+ }
+}
diff --git a/app/src/main/java/exh/ui/login/LoginActivity.kt b/app/src/main/java/exh/ui/login/LoginActivity.kt
new file mode 100755
index 000000000..a205b50ec
--- /dev/null
+++ b/app/src/main/java/exh/ui/login/LoginActivity.kt
@@ -0,0 +1,218 @@
+package exh.ui.login
+
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.view.MenuItem
+import android.webkit.CookieManager
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.source.online.all.EHentai
+import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
+import exh.EXH_SOURCE_ID
+import kotlinx.android.synthetic.main.eh_activity_login.*
+import kotlinx.android.synthetic.main.toolbar.*
+import rx.Observable
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
+import timber.log.Timber
+import uy.kohesive.injekt.injectLazy
+import java.net.HttpCookie
+
+/**
+ * LoginActivity
+ */
+
+class LoginActivity : BaseActivity() {
+
+ val preferenceManager: PreferencesHelper by injectLazy()
+
+ val sourceManager: SourceManager by injectLazy()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setAppTheme()
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.eh_activity_login)
+
+ setup()
+
+ setupToolbar(toolbar, backNavigation = false)
+ }
+
+ fun setup() {
+ btn_cancel.setOnClickListener { onBackPressed() }
+ btn_recheck.setOnClickListener { webview.loadUrl("http://exhentai.org/") }
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ CookieManager.getInstance().removeAllCookies {
+ runOnUiThread {
+ startWebview()
+ }
+ }
+ } else {
+ CookieManager.getInstance().removeAllCookie()
+ startWebview()
+ }
+ }
+
+ fun startWebview() {
+ webview.settings.javaScriptEnabled = true
+ webview.settings.domStorageEnabled = true
+
+ webview.loadUrl("https://forums.e-hentai.org/index.php?act=Login")
+
+ webview.setWebViewClient(object : WebViewClient() {
+ override fun onPageFinished(view: WebView, url: String) {
+ super.onPageFinished(view, url)
+ Timber.d(url)
+ val parsedUrl = Uri.parse(url)
+ if(parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) {
+ //Hide distracting content
+ view.loadUrl(HIDE_JS)
+
+ //Check login result
+ if(parsedUrl.getQueryParameter("code")?.toInt() != 0) {
+ if(checkLoginCookies(url)) view.loadUrl("http://exhentai.org/")
+ }
+ } else if(parsedUrl.host.equals("exhentai.org", ignoreCase = true)) {
+ //At ExHentai, check that everything worked out...
+ if(applyExHentaiCookies(url)) {
+ preferenceManager.enableExhentai().set(true)
+ finishLogin()
+ }
+ }
+ }
+ })
+ }
+
+ fun finishLogin() {
+ val progressDialog = MaterialDialog.Builder(this)
+ .title("Finalizing login")
+ .progress(true, 0)
+ .content("Please wait...")
+ .cancelable(false)
+ .show()
+
+ val eh = sourceManager
+ .getOnlineSources()
+ .find { it.id == EXH_SOURCE_ID } as EHentai
+ Observable.fromCallable {
+ //I honestly have no idea why we need to call this twice, but it works, so whatever
+ try {
+ eh.fetchFavorites()
+ } catch(ignored: Exception) {}
+ try {
+ eh.fetchFavorites()
+ } catch(ignored: Exception) {}
+ }.subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ progressDialog.dismiss()
+ onBackPressed()
+ }
+ }
+
+ /**
+ * Check if we are logged in
+ */
+ fun checkLoginCookies(url: String): Boolean {
+ getCookies(url)?.let { parsed ->
+ return parsed.filter {
+ (it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true)
+ || it.name.equals(PASS_HASH_COOKIE, ignoreCase = true))
+ && it.value.isNotBlank()
+ }.count() >= 2
+ }
+ return false
+ }
+
+ /**
+ * Parse cookies at ExHentai
+ */
+ fun applyExHentaiCookies(url: String): Boolean {
+ getCookies(url)?.let { parsed ->
+
+ var memberId: String? = null
+ var passHash: String? = null
+ var igneous: String? = null
+
+ parsed.forEach {
+ when (it.name.toLowerCase()) {
+ MEMBER_ID_COOKIE -> memberId = it.value
+ PASS_HASH_COOKIE -> passHash = it.value
+ IGNEOUS_COOKIE -> igneous = it.value
+ }
+ }
+
+ //Missing a cookie
+ if (memberId == null || passHash == null || igneous == null) return false
+
+ //Update prefs
+ preferenceManager.memberIdVal().set(memberId)
+ preferenceManager.passHashVal().set(passHash)
+ preferenceManager.igneousVal().set(igneous)
+
+ return true
+ }
+ return false
+ }
+
+ fun getCookies(url: String): List?
+ = CookieManager.getInstance().getCookie(url)?.let {
+ it.split("; ").flatMap {
+ HttpCookie.parse(it)
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> onBackPressed()
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ companion object {
+ const val MEMBER_ID_COOKIE = "ipb_member_id"
+ const val PASS_HASH_COOKIE = "ipb_pass_hash"
+ const val IGNEOUS_COOKIE = "igneous"
+
+ const val HIDE_JS = """
+ javascript:(function () {
+ document.getElementsByTagName('body')[0].style.visibility = 'hidden';
+ document.getElementsByName('submit')[0].style.visibility = 'visible';
+ document.querySelector('td[width="60%"][valign="top"]').style.visibility = 'visible';
+
+ function hide(e) {if(e !== null && e !== undefined) e.style.display = 'none';}
+
+ hide(document.querySelector(".errorwrap"));
+ hide(document.querySelector('td[width="40%"][valign="top"]'));
+ var child = document.querySelector(".page").querySelector('div');
+ child.style.padding = null;
+ var ft = child.querySelectorAll('table');
+ var fd = child.parentNode.querySelectorAll('div > div');
+ var fh = document.querySelector('#border').querySelectorAll('td > table');
+ hide(ft[0]);
+ hide(ft[1]);
+ hide(fd[1]);
+ hide(fd[2]);
+ hide(child.querySelector('br'));
+ var error = document.querySelector(".page > div > .borderwrap");
+ if(error !== null) error.style.visibility = 'visible';
+ hide(fh[0]);
+ hide(fh[1]);
+ hide(document.querySelector("#gfooter"));
+ hide(document.querySelector(".copyright"));
+ document.querySelectorAll("td").forEach(function(e) {
+ e.style.color = "white";
+ });
+ var pc = document.querySelector(".postcolor");
+ if(pc !== null) pc.style.color = "#26353F";
+ })()
+ """
+ }
+}
diff --git a/app/src/main/java/exh/ui/migration/MetadataFetchDialog.kt b/app/src/main/java/exh/ui/migration/MetadataFetchDialog.kt
new file mode 100755
index 000000000..75b72e165
--- /dev/null
+++ b/app/src/main/java/exh/ui/migration/MetadataFetchDialog.kt
@@ -0,0 +1,136 @@
+package exh.ui.migration
+
+import android.app.Activity
+import android.content.pm.ActivityInfo
+import android.text.Html
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.source.online.all.EHentai
+import exh.isExSource
+import exh.isLewdSource
+import exh.metadata.MetadataHelper
+import exh.metadata.copyTo
+import exh.metadata.genericCopyTo
+import timber.log.Timber
+import uy.kohesive.injekt.injectLazy
+import kotlin.concurrent.thread
+
+class MetadataFetchDialog {
+
+ val metadataHelper by lazy { MetadataHelper() }
+
+ val db: DatabaseHelper by injectLazy()
+
+ val sourceManager: SourceManager by injectLazy()
+
+ val preferenceHelper: PreferencesHelper by injectLazy()
+
+ fun show(context: Activity) {
+ //Too lazy to actually deal with orientation changes
+ context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
+
+ val progressDialog = MaterialDialog.Builder(context)
+ .title("Fetching library metadata")
+ .content("Preparing library")
+ .progress(false, 0, true)
+ .cancelable(false)
+ .canceledOnTouchOutside(false)
+ .show()
+
+ thread {
+ db.deleteMangasNotInLibrary().executeAsBlocking()
+
+ val libraryMangas = db.getLibraryMangas()
+ .executeAsBlocking()
+ .filter {
+ isLewdSource(it.source)
+ && metadataHelper.fetchMetadata(it.url, it.source) == null
+ }
+
+ context.runOnUiThread {
+ progressDialog.maxProgress = libraryMangas.size
+ }
+
+ //Actual metadata fetch code
+ libraryMangas.forEachIndexed { i, manga ->
+ context.runOnUiThread {
+ progressDialog.setContent("Processing: ${manga.title}")
+ progressDialog.setProgress(i + 1)
+ }
+ try {
+ val source = sourceManager.get(manga.source)
+ source?.let {
+ manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
+ metadataHelper.fetchMetadata(manga.url, manga.source)?.genericCopyTo(manga)
+ }
+ } catch(t: Throwable) {
+ Timber.e(t, "Could not migrate manga!")
+ }
+ }
+
+ context.runOnUiThread {
+ progressDialog.dismiss()
+
+ //Enable orientation changes again
+ context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
+
+ displayMigrationComplete(context)
+ }
+ }
+ }
+
+ fun askMigration(activity: Activity) {
+ var extra = ""
+ db.getLibraryMangas().asRxSingle().subscribe {
+ //Not logged in but have ExHentai galleries
+ if(!preferenceHelper.enableExhentai().getOrDefault()) {
+ it.find { isExSource(it.source) }?.let {
+ extra = "If you use ExHentai, please log in first before fetching your library metadata!
"
+ }
+ }
+ activity.runOnUiThread {
+ MaterialDialog.Builder(activity)
+ .title("Fetch library metadata")
+ .content(Html.fromHtml("You need to fetch your library's metadata before tag searching in the library will function.
" +
+ "This process may take a long time depending on your library size and will also use up a significant amount of internet bandwidth.
" +
+ extra +
+ "This process can be done later if required."))
+ .positiveText("Migrate")
+ .negativeText("Later")
+ .onPositive { _, _ -> show(activity) }
+ .onNegative({ _, _ -> adviseMigrationLater(activity) })
+ .cancelable(false)
+ .canceledOnTouchOutside(false)
+ .dismissListener {
+ preferenceHelper.migrateLibraryAsked().set(true)
+ }.show()
+ }
+ }
+
+ }
+
+ fun adviseMigrationLater(activity: Activity) {
+ MaterialDialog.Builder(activity)
+ .title("Metadata fetch canceled")
+ .content("Library metadata fetch has been canceled.\n\n" +
+ "You can run this operation later by going to: Settings > E-Hentai > Migrate library metadata")
+ .positiveText("Ok")
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .show()
+ }
+
+ fun displayMigrationComplete(activity: Activity) {
+ MaterialDialog.Builder(activity)
+ .title("Migration complete")
+ .content("${activity.getString(R.string.app_name)} is now ready for use!")
+ .positiveText("Ok")
+ .cancelable(true)
+ .canceledOnTouchOutside(true)
+ .show()
+ }
+}
diff --git a/app/src/main/java/exh/ui/migration/MigrationStatus.kt b/app/src/main/java/exh/ui/migration/MigrationStatus.kt
new file mode 100755
index 000000000..1fa3aa5cf
--- /dev/null
+++ b/app/src/main/java/exh/ui/migration/MigrationStatus.kt
@@ -0,0 +1,16 @@
+package exh.ui.migration
+
+class MigrationStatus {
+ companion object {
+ val NOT_INITIALIZED = -1
+ val COMPLETED = 0
+
+ //Migration process
+ val NOTIFY_USER = 1
+ val OPEN_BACKUP_MENU = 2
+ val PERFORM_BACKUP = 3
+ val FINALIZE_MIGRATION = 4
+
+ val MAX_MIGRATION_STEPS = 2
+ }
+}
diff --git a/app/src/main/java/exh/ui/migration/UrlMigrator.kt b/app/src/main/java/exh/ui/migration/UrlMigrator.kt
new file mode 100755
index 000000000..2169c555d
--- /dev/null
+++ b/app/src/main/java/exh/ui/migration/UrlMigrator.kt
@@ -0,0 +1,79 @@
+package exh.ui.migration
+
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import exh.isExSource
+import exh.isLewdSource
+import exh.metadata.MetadataHelper
+import uy.kohesive.injekt.injectLazy
+
+class UrlMigrator {
+ private val db: DatabaseHelper by injectLazy()
+
+ private val prefs: PreferencesHelper by injectLazy()
+
+ private val metadataHelper: MetadataHelper by lazy { MetadataHelper() }
+
+ fun perform() {
+ db.inTransaction {
+ val dbMangas = db.getMangas()
+ .executeAsBlocking()
+
+ //Find all EX mangas
+ val qualifyingMangas = dbMangas.asSequence().filter {
+ isLewdSource(it.source)
+ }
+
+ val possibleDups = mutableListOf()
+ val badMangas = mutableListOf()
+
+ qualifyingMangas.forEach {
+ if(it.url.startsWith("g/")) //Missing slash at front so we are bad
+ badMangas.add(it)
+ else
+ possibleDups.add(it)
+ }
+
+ //Sort possible dups so we can use binary search on it
+ possibleDups.sortBy { it.url }
+
+ badMangas.forEach { manga ->
+ //Build fixed URL
+ val urlWithSlash = "/" + manga.url
+ //Fix metadata if required
+ val metadata = metadataHelper.fetchEhMetadata(manga.url, isExSource(manga.source))
+ metadata?.url?.let {
+ if(it.startsWith("g/")) { //Check if metadata URL has no slash
+ metadata.url = urlWithSlash //Fix it
+ metadataHelper.writeGallery(metadata, manga.source) //Write new metadata to disk
+ }
+ }
+ //If we have a dup (with the fixed url), use the dup instead
+ val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url })
+ if(possibleDup >= 0) {
+ //Make sure it is favorited if we are
+ if(manga.favorite) {
+ val dup = possibleDups[possibleDup]
+ dup.favorite = true
+ db.insertManga(dup).executeAsBlocking() //Update DB with changes
+ }
+ //Delete ourself (but the dup is still there)
+ db.deleteManga(manga).executeAsBlocking()
+ return@forEach
+ }
+ //No dup, correct URL and reinsert ourselves
+ manga.url = urlWithSlash
+ db.insertManga(manga).executeAsBlocking()
+ }
+ }
+ }
+
+ fun tryMigration() {
+ if(!prefs.hasPerformedURLMigration().getOrDefault()) {
+ perform()
+ prefs.hasPerformedURLMigration().set(true)
+ }
+ }
+}
diff --git a/app/src/main/java/exh/util/UriFilter.kt b/app/src/main/java/exh/util/UriFilter.kt
new file mode 100755
index 000000000..655b03996
--- /dev/null
+++ b/app/src/main/java/exh/util/UriFilter.kt
@@ -0,0 +1,10 @@
+package exh.util
+
+import android.net.Uri
+
+/**
+ * Uri filter
+ */
+interface UriFilter {
+ fun addToUri(builder: Uri.Builder)
+}
\ No newline at end of file
diff --git a/app/src/main/java/exh/util/UriGroup.kt b/app/src/main/java/exh/util/UriGroup.kt
new file mode 100755
index 000000000..cb9225c40
--- /dev/null
+++ b/app/src/main/java/exh/util/UriGroup.kt
@@ -0,0 +1,15 @@
+package exh.util
+
+import android.net.Uri
+import eu.kanade.tachiyomi.source.model.Filter
+
+/**
+ * UriGroup
+ */
+open class UriGroup(name: String, state: List) : Filter.Group(name, state), UriFilter {
+ override fun addToUri(builder: Uri.Builder) {
+ state.forEach {
+ if(it is UriFilter) it.addToUri(builder)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/anim/enter_from_bottom.xml b/app/src/main/res/anim/enter_from_bottom.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/enter_from_left.xml b/app/src/main/res/anim/enter_from_left.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/enter_from_right.xml b/app/src/main/res/anim/enter_from_right.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/enter_from_top.xml b/app/src/main/res/anim/enter_from_top.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/exit_to_bottom.xml b/app/src/main/res/anim/exit_to_bottom.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/exit_to_left.xml b/app/src/main/res/anim/exit_to_left.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/exit_to_right.xml b/app/src/main/res/anim/exit_to_right.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/exit_to_top.xml b/app/src/main/res/anim/exit_to_top.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/fab_hide_to_bottom.xml b/app/src/main/res/anim/fab_hide_to_bottom.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/fab_show_from_bottom.xml b/app/src/main/res/anim/fab_show_from_bottom.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/anim/fade_in_long.xml b/app/src/main/res/anim/fade_in_long.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/color/abc_primary_text_material_dark.xml b/app/src/main/res/color/abc_primary_text_material_dark.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_av_pause_grey_24dp_img.png b/app/src/main/res/drawable-hdpi/ic_av_pause_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_av_play_arrow_grey_img.png b/app/src/main/res/drawable-hdpi/ic_av_play_arrow_grey_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_clear_grey_24dp_img.png b/app/src/main/res/drawable-hdpi/ic_clear_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_delete_grey_24dp.png b/app/src/main/res/drawable-hdpi/ic_delete_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_insert_photo_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_insert_photo_white_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_refresh_grey_24dp_img.png b/app/src/main/res/drawable-hdpi/ic_refresh_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp_img.png b/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_share_grey_24dp.png b/app/src/main/res/drawable-hdpi/ic_share_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-hdpi/ic_system_update_grey_24dp_img.png b/app/src/main/res/drawable-hdpi/ic_system_update_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_av_pause_grey_24dp_img.png b/app/src/main/res/drawable-mdpi/ic_av_pause_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_av_play_arrow_grey_img.png b/app/src/main/res/drawable-mdpi/ic_av_play_arrow_grey_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_clear_grey_24dp_img.png b/app/src/main/res/drawable-mdpi/ic_clear_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_delete_grey_24dp.png b/app/src/main/res/drawable-mdpi/ic_delete_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_insert_photo_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_insert_photo_white_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_refresh_grey_24dp_img.png b/app/src/main/res/drawable-mdpi/ic_refresh_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp_img.png b/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_share_grey_24dp.png b/app/src/main/res/drawable-mdpi/ic_share_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-mdpi/ic_system_update_grey_24dp_img.png b/app/src/main/res/drawable-mdpi/ic_system_update_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-v21/library_item_selector_dark.xml b/app/src/main/res/drawable-v21/library_item_selector_dark.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-v21/library_item_selector_light.xml b/app/src/main/res/drawable-v21/library_item_selector_light.xml
old mode 100644
new mode 100755
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
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-v21/list_item_selector_light.xml b/app/src/main/res/drawable-v21/list_item_selector_light.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/card_background.9.png b/app/src/main/res/drawable-xhdpi/card_background.9.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_av_pause_grey_24dp_img.png b/app/src/main/res/drawable-xhdpi/ic_av_pause_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_av_play_arrow_grey_img.png b/app/src/main/res/drawable-xhdpi/ic_av_play_arrow_grey_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_clear_grey_24dp_img.png b/app/src/main/res/drawable-xhdpi/ic_clear_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_delete_grey_24dp.png b/app/src/main/res/drawable-xhdpi/ic_delete_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_insert_photo_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_insert_photo_white_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh_grey_24dp_img.png b/app/src/main/res/drawable-xhdpi/ic_refresh_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp_img.png b/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_share_grey_24dp.png b/app/src/main/res/drawable-xhdpi/ic_share_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xhdpi/ic_system_update_grey_24dp_img.png b/app/src/main/res/drawable-xhdpi/ic_system_update_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_av_pause_grey_24dp_img.png b/app/src/main/res/drawable-xxhdpi/ic_av_pause_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_av_play_arrow_grey_img.png b/app/src/main/res/drawable-xxhdpi/ic_av_play_arrow_grey_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_clear_grey_24dp_img.png b/app/src/main/res/drawable-xxhdpi/ic_clear_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_delete_grey_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_delete_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_insert_photo_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_insert_photo_white_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh_grey_24dp_img.png b/app/src/main/res/drawable-xxhdpi/ic_refresh_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp_img.png b/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_grey_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_share_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxhdpi/ic_system_update_grey_24dp_img.png b/app/src/main/res/drawable-xxhdpi/ic_system_update_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/al.png b/app/src/main/res/drawable-xxxhdpi/al.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_av_pause_grey_24dp_img.png b/app/src/main/res/drawable-xxxhdpi/ic_av_pause_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_av_play_arrow_grey_img.png b/app/src/main/res/drawable-xxxhdpi/ic_av_play_arrow_grey_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clear_grey_24dp_img.png b/app/src/main/res/drawable-xxxhdpi/ic_clear_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delete_grey_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_delete_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_insert_photo_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_insert_photo_white_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_refresh_grey_24dp_img.png b/app/src/main/res/drawable-xxxhdpi/ic_refresh_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp_img.png b/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_share_grey_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_share_grey_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_system_update_grey_24dp_img.png b/app/src/main/res/drawable-xxxhdpi/ic_system_update_grey_24dp_img.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/kitsu.png b/app/src/main/res/drawable-xxxhdpi/kitsu.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable-xxxhdpi/mal.png b/app/src/main/res/drawable-xxxhdpi/mal.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/branded_logo.xml b/app/src/main/res/drawable/branded_logo.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/eh_ic_ehlogo_red_24dp.xml b/app/src/main/res/drawable/eh_ic_ehlogo_red_24dp.xml
new file mode 100755
index 000000000..ee5aca6a2
--- /dev/null
+++ b/app/src/main/res/drawable/eh_ic_ehlogo_red_24dp.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/eh_migration_backup.png b/app/src/main/res/drawable/eh_migration_backup.png
new file mode 100755
index 000000000..9d58af1e3
Binary files /dev/null and b/app/src/main/res/drawable/eh_migration_backup.png differ
diff --git a/app/src/main/res/drawable/eh_migration_backup_button.png b/app/src/main/res/drawable/eh_migration_backup_button.png
new file mode 100755
index 000000000..9903385c9
Binary files /dev/null and b/app/src/main/res/drawable/eh_migration_backup_button.png differ
diff --git a/app/src/main/res/drawable/eh_migration_hamburgers.png b/app/src/main/res/drawable/eh_migration_hamburgers.png
new file mode 100755
index 000000000..8e491b1e5
Binary files /dev/null and b/app/src/main/res/drawable/eh_migration_hamburgers.png differ
diff --git a/app/src/main/res/drawable/eh_migration_share_icon.png b/app/src/main/res/drawable/eh_migration_share_icon.png
new file mode 100755
index 000000000..7749f7797
Binary files /dev/null and b/app/src/main/res/drawable/eh_migration_share_icon.png differ
diff --git a/app/src/main/res/drawable/empty_divider.xml b/app/src/main/res/drawable/empty_divider.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/empty_drawable_32dp.xml b/app/src/main/res/drawable/empty_drawable_32dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/filter_mock.png b/app/src/main/res/drawable/filter_mock.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/gradient_shape.xml b/app/src/main/res/drawable/gradient_shape.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_account_circle_black_24dp.xml b/app/src/main/res/drawable/ic_account_circle_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_add_white_24dp.xml b/app/src/main/res/drawable/ic_add_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_backup_black_24dp.xml b/app/src/main/res/drawable/ic_backup_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_book_black_128dp.xml b/app/src/main/res/drawable/ic_book_black_128dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_book_black_24dp.xml b/app/src/main/res/drawable/ic_book_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_bookmark_border_white_24dp.xml b/app/src/main/res/drawable/ic_bookmark_border_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_bookmark_white_24dp.xml b/app/src/main/res/drawable/ic_bookmark_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_brightness_4_white_24dp.xml b/app/src/main/res/drawable/ic_brightness_4_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_brightness_5_black_24dp.xml b/app/src/main/res/drawable/ic_brightness_5_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_broken_image_grey_24dp.xml b/app/src/main/res/drawable/ic_broken_image_grey_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_check_box_24dp.xml b/app/src/main/res/drawable/ic_check_box_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml b/app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_check_box_x_24dp.xml b/app/src/main/res/drawable/ic_check_box_x_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_chevron_right_white_24dp.xml b/app/src/main/res/drawable/ic_chevron_right_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_chrome_reader_mode_black_24dp.xml b/app/src/main/res/drawable/ic_chrome_reader_mode_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_cloud_download_white_24dp.xml b/app/src/main/res/drawable/ic_cloud_download_white_24dp.xml
new file mode 100755
index 000000000..0f56a12d0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cloud_download_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_code_black_24dp.xml b/app/src/main/res/drawable/ic_code_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_create_white_24dp.xml b/app/src/main/res/drawable/ic_create_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_delete_white_24dp.xml b/app/src/main/res/drawable/ic_delete_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_done_all_grey_24dp.xml b/app/src/main/res/drawable/ic_done_all_grey_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_done_all_white_24dp.xml b/app/src/main/res/drawable/ic_done_all_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_done_green_24dp.xml b/app/src/main/res/drawable/ic_done_green_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_done_white_18dp.xml b/app/src/main/res/drawable/ic_done_white_18dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_expand_more_white_24dp.xml b/app/src/main/res/drawable/ic_expand_more_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_explore_black_24dp.xml b/app/src/main/res/drawable/ic_explore_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_file_download_black_128dp.xml b/app/src/main/res/drawable/ic_file_download_black_128dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_file_download_black_24dp.xml b/app/src/main/res/drawable/ic_file_download_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_file_download_white_24dp.xml b/app/src/main/res/drawable/ic_file_download_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_filter_list_white_24dp.xml b/app/src/main/res/drawable/ic_filter_list_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_glasses_black_128dp.xml b/app/src/main/res/drawable/ic_glasses_black_128dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_glasses_black_24dp.xml b/app/src/main/res/drawable/ic_glasses_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_help_black_24dp.xml b/app/src/main/res/drawable/ic_help_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_insert_photo_white_24dp.png b/app/src/main/res/drawable/ic_insert_photo_white_24dp.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_down_black_32dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_down_black_32dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_up_black_32dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_up_black_32dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_label_white_24dp.xml b/app/src/main/res/drawable/ic_label_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_language_black_24dp.xml b/app/src/main/res/drawable/ic_language_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_menu_white_24dp.xml b/app/src/main/res/drawable/ic_menu_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_more_horiz_black_24dp.xml b/app/src/main/res/drawable/ic_more_horiz_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_pause_white_24dp.xml b/app/src/main/res/drawable/ic_pause_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_play_arrow_white_24dp.xml b/app/src/main/res/drawable/ic_play_arrow_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_playlist_add_black_24dp.xml b/app/src/main/res/drawable/ic_playlist_add_black_24dp.xml
new file mode 100755
index 000000000..0460472b9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_playlist_add_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_refresh_white_24dp.xml b/app/src/main/res/drawable/ic_refresh_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_reorder_grey_24dp.xml b/app/src/main/res/drawable/ic_reorder_grey_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_search_white_24dp.xml b/app/src/main/res/drawable/ic_search_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_select_all_white_24dp.xml b/app/src/main/res/drawable/ic_select_all_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_settings_black_24dp.xml b/app/src/main/res/drawable/ic_settings_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_settings_white_24dp.xml b/app/src/main/res/drawable/ic_settings_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_share_white_24dp.xml b/app/src/main/res/drawable/ic_share_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_skip_next_white_24dp.xml b/app/src/main/res/drawable/ic_skip_next_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_skip_previous_white_24dp.xml b/app/src/main/res/drawable/ic_skip_previous_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_sort_by_numeric_white_24dp.xml b/app/src/main/res/drawable/ic_sort_by_numeric_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_sort_white_24dp.xml b/app/src/main/res/drawable/ic_sort_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_sync_black_24dp.xml b/app/src/main/res/drawable/ic_sync_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_tune_black_24dp.xml b/app/src/main/res/drawable/ic_tune_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_update_black_128dp.xml b/app/src/main/res/drawable/ic_update_black_128dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_update_black_24dp.xml b/app/src/main/res/drawable/ic_update_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_view_list_white_24dp.xml b/app/src/main/res/drawable/ic_view_list_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_view_module_white_24dp.xml b/app/src/main/res/drawable/ic_view_module_white_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/ic_watch_later_black_24dp.xml b/app/src/main/res/drawable/ic_watch_later_black_24dp.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/library_item_selector_dark.xml b/app/src/main/res/drawable/library_item_selector_dark.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/library_item_selector_light.xml b/app/src/main/res/drawable/library_item_selector_light.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/line_divider_dark.xml b/app/src/main/res/drawable/line_divider_dark.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/line_divider_light.xml b/app/src/main/res/drawable/line_divider_light.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/list_item_selector_dark.xml b/app/src/main/res/drawable/list_item_selector_dark.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/list_item_selector_light.xml b/app/src/main/res/drawable/list_item_selector_light.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/drawable/mask_star.png b/app/src/main/res/drawable/mask_star.png
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/activity_download_manager.xml b/app/src/main/res/layout/activity_download_manager.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/activity_edit_categories.xml b/app/src/main/res/layout/activity_edit_categories.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/activity_lock.xml b/app/src/main/res/layout/activity_lock.xml
new file mode 100755
index 000000000..e6898f560
--- /dev/null
+++ b/app/src/main/res/layout/activity_lock.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/activity_manga.xml b/app/src/main/res/layout/activity_manga.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/activity_preferences.xml b/app/src/main/res/layout/activity_preferences.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/activity_reader.xml b/app/src/main/res/layout/activity_reader.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/catalogue_drawer.xml b/app/src/main/res/layout/catalogue_drawer.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/catalogue_drawer_content.xml b/app/src/main/res/layout/catalogue_drawer_content.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/changelog_header_layout.xml b/app/src/main/res/layout/changelog_header_layout.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/changelog_row_layout.xml b/app/src/main/res/layout/changelog_row_layout.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/chapter_image.xml b/app/src/main/res/layout/chapter_image.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/dialog_reader_custom_filter.xml b/app/src/main/res/layout/dialog_reader_custom_filter.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/dialog_reader_settings.xml b/app/src/main/res/layout/dialog_reader_settings.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/dialog_track_chapters.xml b/app/src/main/res/layout/dialog_track_chapters.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/dialog_track_score.xml b/app/src/main/res/layout/dialog_track_score.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/dialog_track_search.xml b/app/src/main/res/layout/dialog_track_search.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/dialog_with_checkbox.xml b/app/src/main/res/layout/dialog_with_checkbox.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/eh_activity_finish_migration.xml b/app/src/main/res/layout/eh_activity_finish_migration.xml
new file mode 100755
index 000000000..e96f46e38
--- /dev/null
+++ b/app/src/main/res/layout/eh_activity_finish_migration.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/eh_activity_intercept.xml b/app/src/main/res/layout/eh_activity_intercept.xml
new file mode 100755
index 000000000..518bb973e
--- /dev/null
+++ b/app/src/main/res/layout/eh_activity_intercept.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/eh_activity_login.xml b/app/src/main/res/layout/eh_activity_login.xml
new file mode 100755
index 000000000..104324cd3
--- /dev/null
+++ b/app/src/main/res/layout/eh_activity_login.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/eh_fragment_batch_add.xml b/app/src/main/res/layout/eh_fragment_batch_add.xml
new file mode 100755
index 000000000..b8742a2a8
--- /dev/null
+++ b/app/src/main/res/layout/eh_fragment_batch_add.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_backup.xml b/app/src/main/res/layout/fragment_backup.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/fragment_catalogue.xml b/app/src/main/res/layout/fragment_catalogue.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/fragment_download_queue.xml b/app/src/main/res/layout/fragment_download_queue.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/fragment_library_category.xml b/app/src/main/res/layout/fragment_library_category.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/fragment_manga_chapters.xml b/app/src/main/res/layout/fragment_manga_chapters.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/fragment_manga_info.xml b/app/src/main/res/layout/fragment_manga_info.xml
old mode 100644
new mode 100755
index 23316d9e1..23924e04e
--- a/app/src/main/res/layout/fragment_manga_info.xml
+++ b/app/src/main/res/layout/fragment_manga_info.xml
@@ -255,7 +255,9 @@
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textIsSelectable="false"/>
+ android:textIsSelectable="true"
+ android:focusable="true"
+ android:longClickable="true"/>
diff --git a/app/src/main/res/layout/fragment_recent_chapters.xml b/app/src/main/res/layout/fragment_recent_chapters.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/fragment_recently_read.xml b/app/src/main/res/layout/fragment_recently_read.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/fragment_track.xml b/app/src/main/res/layout/fragment_track.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_catalogue_grid.xml b/app/src/main/res/layout/item_catalogue_grid.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_catalogue_list.xml b/app/src/main/res/layout/item_catalogue_list.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_chapter.xml b/app/src/main/res/layout/item_chapter.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_download.xml b/app/src/main/res/layout/item_download.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_edit_categories.xml b/app/src/main/res/layout/item_edit_categories.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_library_category.xml b/app/src/main/res/layout/item_library_category.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_pager_reader.xml b/app/src/main/res/layout/item_pager_reader.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_recent_chapter_section.xml b/app/src/main/res/layout/item_recent_chapter_section.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_recent_chapters.xml b/app/src/main/res/layout/item_recent_chapters.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_recently_read.xml b/app/src/main/res/layout/item_recently_read.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_track.xml b/app/src/main/res/layout/item_track.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_track_search.xml b/app/src/main/res/layout/item_track_search.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/item_webtoon_reader.xml b/app/src/main/res/layout/item_webtoon_reader.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/library_drawer.xml b/app/src/main/res/layout/library_drawer.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/library_grid_recycler.xml b/app/src/main/res/layout/library_grid_recycler.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/library_list_recycler.xml b/app/src/main/res/layout/library_list_recycler.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/listitem_dir.xml b/app/src/main/res/layout/listitem_dir.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/navigation_header.xml b/app/src/main/res/layout/navigation_header.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/navigation_view_checkbox.xml b/app/src/main/res/layout/navigation_view_checkbox.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/navigation_view_checkedtext.xml b/app/src/main/res/layout/navigation_view_checkedtext.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/navigation_view_group.xml b/app/src/main/res/layout/navigation_view_group.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/navigation_view_radio.xml b/app/src/main/res/layout/navigation_view_radio.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/navigation_view_spinner.xml b/app/src/main/res/layout/navigation_view_spinner.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/navigation_view_text.xml b/app/src/main/res/layout/navigation_view_text.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/page_decode_error.xml b/app/src/main/res/layout/page_decode_error.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/pref_account_login.xml b/app/src/main/res/layout/pref_account_login.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/pref_item_source.xml b/app/src/main/res/layout/pref_item_source.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/pref_library_columns.xml b/app/src/main/res/layout/pref_library_columns.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/preference_widget_imageview.xml b/app/src/main/res/layout/preference_widget_imageview.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/progress_item.xml b/app/src/main/res/layout/progress_item.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/recycler_autofit.xml b/app/src/main/res/layout/recycler_autofit.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/spinner_item.xml b/app/src/main/res/layout/spinner_item.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/layout/view_empty.xml b/app/src/main/res/layout/view_empty.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/catalogue_list.xml b/app/src/main/res/menu/catalogue_list.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/category_selection.xml b/app/src/main/res/menu/category_selection.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/chapter_recent.xml b/app/src/main/res/menu/chapter_recent.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/chapter_recent_selection.xml b/app/src/main/res/menu/chapter_recent_selection.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/chapter_selection.xml b/app/src/main/res/menu/chapter_selection.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/chapter_single.xml b/app/src/main/res/menu/chapter_single.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/chapters.xml b/app/src/main/res/menu/chapters.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/download_queue.xml b/app/src/main/res/menu/download_queue.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/library.xml b/app/src/main/res/menu/library.xml
old mode 100644
new mode 100755
index 5e7bf7ad5..c6bdd721f
--- a/app/src/main/res/menu/library.xml
+++ b/app/src/main/res/menu/library.xml
@@ -22,9 +22,14 @@
android:title="@string/action_update_library"
app:showAsAction="ifRoom"/>
+
+
-
diff --git a/app/src/main/res/menu/library_selection.xml b/app/src/main/res/menu/library_selection.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/manga_info.xml b/app/src/main/res/menu/manga_info.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/menu/menu_navigation.xml b/app/src/main/res/menu/menu_navigation.xml
old mode 100644
new mode 100755
index 06e2bd68e..51e76bd43
--- a/app/src/main/res/menu/menu_navigation.xml
+++ b/app/src/main/res/menu/menu_navigation.xml
@@ -18,11 +18,15 @@
+ android:title="Galleries" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/raw/changelog_release.xml b/app/src/main/res/raw/changelog_release.xml
old mode 100644
new mode 100755
index 7c97cf6b4..c98dee4f8
--- a/app/src/main/res/raw/changelog_release.xml
+++ b/app/src/main/res/raw/changelog_release.xml
@@ -1,7 +1,9 @@
-
+
+ EH: Fix link importer
+
New backup system. Smaller file size but requires a network connection to restore.Fixed descriptions showing a single line.
@@ -16,9 +18,6 @@
Last read page is now retained in webtoon reader.
-
-
- Added an option to auto download from selected categories.Handle a few more directories for local manga.
@@ -30,7 +29,31 @@
Fixed gallery not showing saved images.
-
+
+ EH: Fix nhentai source only showing first page.
+
+
+
+ EH: Add the ability to download favorites from your E-Hentai/ExHentai account.
+
+ Fix downloader breaking on some manga.
+
+
+
+ EH: Add namespace existence searching.
+
+ EH: Added advanced search options.
+
+ EH: Pressing retry on an E-Hentai/ExHentai image that failed to download will now properly download the image from a different server.
+
+ EH: Gallery info screen is now selectable.
+
+ EH: Add app lock feature.
+
+ EH: Added PervEden and nhentai sources.
+
+ EH: Various bug fixes.
+
Support for local manga. Head to the [a href="https://github.com/inorichi/tachiyomi/wiki/Local-manga"]wiki page[/a] for instructions.Added an option to detect and remove the white borders of the images.
@@ -46,138 +69,42 @@
Fixed lost covers on some devices.
-
- Added support for Anilist and Kitsu.
-
- Added library refresh option to library updates tab.
-
- Back button closes drawers before exiting the app.
-
- Fixed issues when using custom app language.
-
- Fixed updater in Android N.
-
- Fixed Mangafox search.
+
+ Fixed duplicate galleries bug
-
- Added an app's language selector.
-
- Added options to sort the library and merged them with the filters.
-
- Added an option to automatically download chapters.
-
- Fixed performance issues when using a custom downloads directory, especially in the library updates tab.
-
- Fixed gesture conflicts with the contextual menu and the webtoon reader.
-
- Fixed wrong page direction when using volume keys for the right to left reader.
-
- Fixed many crashes.
-
-
-
- The download manager has been rewritten and it's possible some of your downloads
- aren't recognized anymore. It's recommended to manually delete everything and start over.
+
+
+ [b]Important![/b] The application is now signed with a different key, you must migrate over your previous library if you wish to keep it!
- Now it's possible to download to any folder in a SD card.
-
- The download directory setting has been reset.
-
- Active downloads now persist after restarts.
-
- Allow to bookmark chapters.
-
- Allow to share or save a single page while reading with a long tap.
-
- Added italian translation.
-
- Image is now the default decoder.
-
-
-
- Added a new image decoder. It should be faster than Rapid and more reliable than Skia.
-
- Removed the advanced setting reencode images. Use the new image decoder instead.
-
-
-
- Fixed a crash when opening latest updates. ([a href="https://github.com/inorichi/tachiyomi/issues/495"]#495[/a])
-
-
-
- Added a new tab to show latest manga updates from the catalogues. ([a href="https://github.com/inorichi/tachiyomi/issues/61"]#61[/a])
-
- Added genre filter for catalogues. ([a href="https://github.com/inorichi/tachiyomi/issues/428"]#428[/a])
-
- Added an optional auto updater (not available for F-Droid installs). ([a href="https://github.com/inorichi/tachiyomi/issues/449"]#449[/a])
-
- Added an option to display the library as a list. ([a href="https://github.com/inorichi/tachiyomi/issues/224"]#224[/a])
-
- Added a customizable color filter for the reader. ([a href="https://github.com/inorichi/tachiyomi/issues/432"]#432[/a])
-
- Added share intent in the info tab of a manga. ([a href="https://github.com/inorichi/tachiyomi/issues/340"]#340[/a])
-
- Allow to launcher shortcuts for manga. ([a href="https://github.com/inorichi/tachiyomi/issues/435"]#435[/a])
-
- Allow to select categories to update in global update. ([a href="https://github.com/inorichi/tachiyomi/issues/461"]#461[/a])
-
- Redesigned source tab in preferences, now it allows to hide unwanted sources and languages. ([a href="https://github.com/inorichi/tachiyomi/issues/447"]#447[/a])
-
- Fixed single page chapters not appending the next one. ([a href="https://github.com/inorichi/tachiyomi/issues/468"]#468[/a])
-
- Fixed reader status bar reappearing after focus restore. ([a href="https://github.com/inorichi/tachiyomi/issues/408"]#408[/a])
-
- Fixed various crashes in the webtoon reader.
-
-
-
- Added a history of reading. ([a href="https://github.com/inorichi/tachiyomi/issues/316"]#316[/a])
-
- Added an option to select the initial screen. ([a href="https://github.com/inorichi/tachiyomi/issues/395"]#395[/a])
-
- Added spanish and portuguese translations. ([a href="https://github.com/inorichi/tachiyomi/issues/365"]#365[/a], [a href="https://github.com/inorichi/tachiyomi/issues/375"]#375[/a])
-
- Added sources "Mangasee" and "Wie Manga!" ([a href="https://github.com/inorichi/tachiyomi/issues/355"]#355[/a], [a href="https://github.com/inorichi/tachiyomi/issues/379"]#379[/a])
-
- New design for the reader's menu. ([a href="https://github.com/inorichi/tachiyomi/issues/368"]#368[/a])
-
- When resuming chapters, the new loader starts from the page that was opened, instead of from the beginning. ([a href="https://github.com/inorichi/tachiyomi/issues/268"]#268[/a])
-
- Custom brightness in the reader can be set even lower by applying a black layer on the top. ([a href="https://github.com/inorichi/tachiyomi/issues/362"]#362[/a])
-
- Fixed reader's status bar reappearing in Android versions older than Kit Kat. ([a href="https://github.com/inorichi/tachiyomi/issues/359"]#359[/a])
-
- Fixed UI bugs. ([a href="https://github.com/inorichi/tachiyomi/issues/332"]#332[/a], [a href="https://github.com/inorichi/tachiyomi/issues/333"]#333[/a], [a href="https://github.com/inorichi/tachiyomi/issues/351"]#351[/a], [a href="https://github.com/inorichi/tachiyomi/issues/361"]#361[/a])
-
- Fixed empty library covers.
-
- Fixed some random crashes (most of them when downloading chapters).
-
-
-
- [b]Important![/b] Now chapters follow the order of the sources. [b]It's required that you update your entire library
- before reading to sync them.[/b] Old behavior can be restored for a manga in the overflow menu of the chapters tab.
+
+ Upstream merge
- Kissmanga now loads through CloudFlare.
+
+ Backend rewrite
+
- Persistent cookies have been added for a better experience with CloudFlare sites.
+
+ Optimizations to cookies and ExHentai login
+
- Added link to manga website in the info page. [a href="https://github.com/inorichi/tachiyomi/issues/157"]#157[/a]
+
+ Add more customization to E-Hentai/ExHentai
+
- Added notifications for downloads. [a href="https://github.com/inorichi/tachiyomi/pull/289"]#289[/a]
+
+ Add offline gallery search engine
+
- Added more options to recent updates. [a href="https://github.com/inorichi/tachiyomi/pull/324"]#324[/a]
+
+ Various aesthetic improvements
+
- Remember last active category. [a href="https://github.com/inorichi/tachiyomi/issues/261"]#261[/a]
-
- Fixed a bug with seamless mode for chapters with less than 5 pages. [a href="https://github.com/inorichi/tachiyomi/issues/291"]#291[/a]
-
- Improved chapter recognition.
-
- Bugfixes and more improvements.
+
+ Bugfixes and more improvements
+
\ No newline at end of file
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-v21/dimens.xml b/app/src/main/res/values-v21/dimens.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-v21/keys.xml b/app/src/main/res/values-v21/keys.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-v21/themes.xml b/app/src/main/res/values-v21/themes.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
old mode 100644
new mode 100755
index e01f29ba0..60b4b32f3
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -250,4 +250,47 @@
-1
+
+
+ Auto
+ 2400x
+ 1600x
+ 1280x
+ 980x
+ 780x
+
+
+ auto
+ ovrs_2400
+ ovrs_1600
+ high
+ med
+ low
+
+
+
+ 25 results
+ 50 results
+ 100 results
+ 200 results
+
+
+ rc_0
+ rc_1
+ rc_2
+ rc_3
+
+
+
+ 4
+ 10 (requires \'More Thumbs\' hath perk)
+ 20 (requires \'Thumbs Up\' hath perk)
+ 40 (requires \'All Thumbs\' hath perk)
+
+
+ tr_2
+ tr_5
+ tr_10
+ tr_20
+
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
old mode 100644
new mode 100755
index 9fed48159..b49994299
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
- Tachiyomi
+ TachiyomiEHName
@@ -425,4 +425,7 @@
No network connection availableDownload paused
+
+ Login
+ E-Hentai
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/xml/eh_pref_eh.xml b/app/src/main/res/xml/eh_pref_eh.xml
new file mode 100755
index 000000000..aaf50e998
--- /dev/null
+++ b/app/src/main/res/xml/eh_pref_eh.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/pref_about.xml b/app/src/main/res/xml/pref_about.xml
old mode 100644
new mode 100755
index cca8173b4..bf89e8136
--- a/app/src/main/res/xml/pref_about.xml
+++ b/app/src/main/res/xml/pref_about.xml
@@ -12,12 +12,6 @@
-
-
diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/xml/pref_downloads.xml b/app/src/main/res/xml/pref_downloads.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml
old mode 100644
new mode 100755
index 0c68c19be..d17b9018a
--- a/app/src/main/res/xml/pref_general.xml
+++ b/app/src/main/res/xml/pref_general.xml
@@ -71,6 +71,11 @@
android:title="@string/default_category"
android:summary="@string/default_category_summary"/>
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/xml/pref_sources.xml b/app/src/main/res/xml/pref_sources.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/xml/pref_tracking.xml b/app/src/main/res/xml/pref_tracking.xml
old mode 100644
new mode 100755
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
old mode 100644
new mode 100755
diff --git a/app/src/test/java/eu/kanade/tachiyomi/CustomRobolectricGradleTestRunner.kt b/app/src/test/java/eu/kanade/tachiyomi/CustomRobolectricGradleTestRunner.kt
old mode 100644
new mode 100755
diff --git a/app/src/test/java/eu/kanade/tachiyomi/TestApp.kt b/app/src/test/java/eu/kanade/tachiyomi/TestApp.kt
old mode 100644
new mode 100755
diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt
old mode 100644
new mode 100755
diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/database/CategoryTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/database/CategoryTest.kt
old mode 100644
new mode 100755
diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/database/ChapterRecognitionTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/database/ChapterRecognitionTest.kt
old mode 100644
new mode 100755
diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt
old mode 100644
new mode 100755
diff --git a/branding/teh-banner.png b/branding/teh-banner.png
new file mode 100755
index 000000000..09864d9e5
Binary files /dev/null and b/branding/teh-banner.png differ
diff --git a/build.gradle b/build.gradle
old mode 100644
new mode 100755
index b021a12b6..b1ee4880c
--- a/build.gradle
+++ b/build.gradle
@@ -10,6 +10,9 @@ buildscript {
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
+
+ //Firebase (EH)
+ classpath 'com.google.gms:google-services:3.0.0'
}
}
diff --git a/gradle.properties b/gradle.properties
old mode 100644
new mode 100755
index 1d3591c8a..8995be531
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,4 +15,7 @@
# 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
+org.gradle.jvmargs=-Xmx8000M
+android.enableBuildCache=true
+kotlin.incremental=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
old mode 100644
new mode 100755
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
old mode 100644
new mode 100755
diff --git a/gradlew.bat b/gradlew.bat
old mode 100644
new mode 100755
diff --git a/settings.gradle b/settings.gradle
old mode 100644
new mode 100755