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 | -|-------|----------|-------------| -| [![TeamCity (simple build status)](https://img.shields.io/teamcity/https/teamcity.kanade.eu/s/tachiyomi_Build.svg)](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [![Travis](https://img.shields.io/travis/inorichi/tachiyomi.svg)](https://travis-ci.org/inorichi/tachiyomi) | [![stable release](https://img.shields.io/github/release/inorichi/tachiyomi.svg?maxAge=3600&label=stable)](https://github.com/inorichi/tachiyomi/releases) [![latest dev build](https://img.shields.io/badge/dev-latest%20build-blue.svg)](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [![fdroid release](https://img.shields.io/badge/stable-f--droid.org-blue.svg)](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [![fdroid dev](https://img.shields.io/badge/dev-wiki-blue.svg)](//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 +[![stable release](https://img.shields.io/github/release/NerdNumber9/TachiyomiEH.svg?maxAge=3600&label=stable)](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 @@ + + + + + + + + + + + + + + + + + +