From 8a2e1f964fd121242eda885a758f70ca7c3f5837 Mon Sep 17 00:00:00 2001 From: InNoobWeTrust Date: Mon, 7 Dec 2020 14:50:38 +0700 Subject: [PATCH] Fix: Add referer header for requests to HocVienTruyenTranh - Correctly use referer header for requests to HocVienTruyenTranh. - Migrate from kotlin synthetic to databinding. - Remove firebase crash report. - Databinding everywhere, head scratched over refactoring. - Strip dependencies for reader view. Smoother scroll on webtoon view. - Remove gc call on recyclerview's item recycled event, let it lag once only when JVM decides to collect gabage. --- .github/workflows/build_on_commit.yml | 73 ++++- .github/workflows/manual_release.yml | 82 +++++ .gitignore | 5 +- .idea/codeStyleSettings.xml | 228 -------------- .idea/compiler.xml | 22 -- .idea/copyright/profiles_settings.xml | 3 - .idea/dictionaries/DucVu.xml | 8 - .idea/gradle.xml | 20 -- .idea/inspectionProfiles/Project_Default.xml | 12 - .../inspectionProfiles/profiles_settings.xml | 7 - .idea/kotlinc.xml | 7 - .idea/misc.xml | 44 --- .idea/modules.xml | 9 - .idea/runConfigurations.xml | 12 - .idea/vcs.xml | 6 - app/build.gradle | 71 +++-- app/src/main/AndroidManifest.xml | 5 +- .../innoobwetrust/kintamanga/KodeinModule.kt | 9 + .../kintamanga/download/DownloadProvider.kt | 7 +- .../kintamanga/download/Downloader.kt | 34 ++- .../innoobwetrust/kintamanga/model/Page.kt | 9 +- .../kintamanga/service/DownloadService.kt | 4 +- .../kintamanga/source/SourceManager.kt | 2 +- .../source/dom/parser/DomSegmentParser.kt | 6 +- .../mangahere/MangaHereMangaInfoProcessor.kt | 4 +- .../vi/hocvientruyentranh/AllSegment.kt | 2 +- .../HocVienTruyenTranhChapterInfoProcessor.kt | 2 +- .../HocVienTruyenTranhMangaInfoProcessor.kt | 4 +- .../TruyenTranhTuanMangaInfoProcessor.kt | 4 +- .../ui/downloader/DownloadAdapter.kt | 95 +++--- .../ui/downloader/DownloaderActivity.kt | 21 +- .../kintamanga/ui/filter/FilterActivity.kt | 52 ++-- .../ui/filter/FilterMultipleChoicesAdapter.kt | 22 +- .../ui/filter/FilterSingleChoiceAdapter.kt | 27 +- .../ui/filter/FilterUserInputAdapter.kt | 25 +- .../kintamanga/ui/main/MainActivity.kt | 44 +-- .../ui/main/favorite/FavoriteFragment.kt | 112 ++++--- .../ui/main/list/MangaListFragment.kt | 148 +++++---- .../ui/main/list/MangaListNetworkLoader.kt | 4 +- .../kintamanga/ui/manga/ChapterListAdapter.kt | 132 ++++---- .../kintamanga/ui/manga/MangaInfoActivity.kt | 69 +++-- .../kintamanga/ui/manga/MangaInfoLoader.kt | 10 - .../kintamanga/ui/reader/ChapterInfoLoader.kt | 7 +- .../ui/reader/ImageViewerAdapter.kt | 159 ++++------ .../kintamanga/ui/reader/ReaderActivity.kt | 177 ++++------- .../kintamanga/ui/reader/ViewerFragment.kt | 104 ++++--- .../kintamanga/util/GlideBitmapImageLoader.kt | 286 ------------------ .../util/extension/StringExtensions.kt | 5 +- app/src/main/res/layout/activity_main.xml | 3 +- .../main/res/layout/holder_image_viewer.xml | 47 +-- build.gradle | 18 +- gradle.properties | 2 + gradle/wrapper/gradle-wrapper.properties | 4 +- versioning.gradle | 37 +++ 54 files changed, 835 insertions(+), 1476 deletions(-) create mode 100644 .github/workflows/manual_release.yml delete mode 100644 .idea/codeStyleSettings.xml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/copyright/profiles_settings.xml delete mode 100644 .idea/dictionaries/DucVu.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/runConfigurations.xml delete mode 100644 .idea/vcs.xml delete mode 100644 app/src/main/java/io/github/innoobwetrust/kintamanga/util/GlideBitmapImageLoader.kt create mode 100644 versioning.gradle diff --git a/.github/workflows/build_on_commit.yml b/.github/workflows/build_on_commit.yml index fd3fcd8..c7eb8ce 100644 --- a/.github/workflows/build_on_commit.yml +++ b/.github/workflows/build_on_commit.yml @@ -12,32 +12,73 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout commit - uses: actions/checkout@v2 + - uses: actions/checkout@v2 + - name: Create version and tag + id: tag_version + run: | + echo "::set-output name=new_tag::v$(grep 'version=' gradle.properties | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" && + echo "::set-output name=new_version::$(grep 'version=' gradle.properties | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" && + echo "::set-output name=full_tag::v$(grep 'version=' gradle.properties | grep -oE '[0-9]+\.[0-9]+\.[0-9]+*')" && + echo "::set-output name=snapshot_date::$(date -Iseconds)" - name: Setup signing information env: KEY_FILE: ${{ secrets.signingKeyBase64 }} run: echo "$KEY_FILE" | openssl base64 -d > signingKey.jks - - name: Build release apk + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build apk env: KEY_STORE_PWD: ${{ secrets.keyStorePassword }} KEY_ALIAS: ${{ secrets.alias }} KEY_PWD: ${{ secrets.keyPassword }} - uses: vgaidarji/android-github-actions-build@v1.0.1 - with: - args: "./gradlew signingReport && ./gradlew :app:assembleRelease --stacktrace" + run: | + ./gradlew signingReport && + ./gradlew :app:assembleRelease --stacktrace - name: Cleanup signing information run: rm signingKey.jks - - name: Get version code and name - run: | - echo "::set-env name=VER_CODE::$(cat app/build/outputs/apk/release/output.json | tr ',' '\n' | grep versionCode | cut -d: -f2)" && \ - echo "::set-env name=VER_NAME::$(cat app/build/outputs/apk/release/output.json | tr ',' '\n' | grep versionName | cut -d: -f2 | tr -d \")" - name: Copy release apk and mapping - run: | - mkdir -p artifacts${{ env.VER_CODE }}v${{ env.VER_NAME }} && \ - cp -r app/build/outputs/* artifacts${{ env.VER_CODE }}v${{ env.VER_NAME }}/ - - name: Upload artifacts ${{ env.VER_CODE }}v${{ env.VER_NAME }} + run: zip -r artifacts_${{ steps.tag_version.outputs.new_tag }}.zip app/build/outputs + - name: Export artifacts + uses: actions/upload-artifact@v1 + with: + name: artifacts_${{ steps.tag_version.outputs.new_tag }}.zip + path: artifacts_${{ steps.tag_version.outputs.new_tag }}.zip + - name: Export APK uses: actions/upload-artifact@v1 with: - name: artifacts${{ env.VER_CODE }}v${{ env.VER_NAME }} - path: artifacts${{ env.VER_CODE }}v${{ env.VER_NAME }} + name: app-release-${{ steps.tag_version.outputs.new_tag }}.apk + path: app/build/outputs/apk/release/app-release.apk + - name: Create a GitHub release + if: ${{ github.event_name == 'push' }} + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.tag_version.outputs.full_tag }} + release_name: Nightly ${{ steps.tag_version.outputs.snapshot_date }} ${{ steps.tag_version.outputs.full_tag }} + prerelease: true + - name: Upload artifacts + if: ${{ github.event_name == 'push' }} + id: upload-artifacts + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./artifacts_${{ steps.tag_version.outputs.new_tag }}.zip + asset_name: artifacts_${{ steps.tag_version.outputs.new_tag }}.zip + asset_content_type: application/zip + - name: Upload APK + if: ${{ github.event_name == 'push' }} + id: upload-apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./app/build/outputs/apk/release/app-release.apk + asset_name: app-release-${{ steps.tag_version.outputs.new_tag }}.apk + asset_content_type: application/zip diff --git a/.github/workflows/manual_release.yml b/.github/workflows/manual_release.yml new file mode 100644 index 0000000..862c312 --- /dev/null +++ b/.github/workflows/manual_release.yml @@ -0,0 +1,82 @@ +name: Manual release + +on: + workflow_dispatch: + branches: [ master ] + +jobs: + build_and_release: + name: Build and release + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set version code to release + run: | + echo "$(grep 'version=' gradle.properties | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" |\ + xargs -I % sed -i 's/^version=.*$/version=%/' gradle.properties + - name: Create version and tag + id: tag_version + run: | + echo "::set-output name=new_tag::v$(grep 'version=' gradle.properties | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" && + echo "::set-output name=new_version::$(grep 'version=' gradle.properties | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" + - name: Setup signing information + env: + KEY_FILE: ${{ secrets.signingKeyBase64 }} + run: echo "$KEY_FILE" | openssl base64 -d > signingKey.jks + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build apk + env: + KEY_STORE_PWD: ${{ secrets.keyStorePassword }} + KEY_ALIAS: ${{ secrets.alias }} + KEY_PWD: ${{ secrets.keyPassword }} + run: | + ./gradlew signingReport && + ./gradlew :app:assembleRelease --stacktrace + - name: Cleanup signing information + run: rm signingKey.jks + - name: Copy release apk and mapping + run: zip -r artifacts_${{ steps.tag_version.outputs.new_tag }}.zip app/build/outputs + - name: Export artifacts + uses: actions/upload-artifact@v1 + with: + name: artifacts_${{ steps.tag_version.outputs.new_tag }}.zip + path: artifacts_${{ steps.tag_version.outputs.new_tag }}.zip + - name: Export APK + uses: actions/upload-artifact@v1 + with: + name: KINTAMAnga-${{ steps.tag_version.outputs.new_tag }}.apk + path: app/build/outputs/apk/release/app-release.apk + - name: Create a GitHub release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.tag_version.outputs.new_tag }} + release_name: Release ${{ steps.tag_version.outputs.new_tag }} + prerelease: false + - name: Upload artifacts + id: upload-artifacts + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./artifacts_${{ steps.tag_version.outputs.new_tag }}.zip + asset_name: artifacts_${{ steps.tag_version.outputs.new_tag }}.zip + asset_content_type: application/zip + - name: Upload APK + id: upload-apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./app/build/outputs/apk/release/app-release.apk + asset_name: KINTAMAnga-${{ steps.tag_version.outputs.new_tag }}.apk + asset_content_type: application/zip + diff --git a/.gitignore b/.gitignore index 5f45578..aabd240 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ *.iml .gradle -/local.properties -/.idea +.idea +.settings +local.properties .DS_Store /build /captures diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml deleted file mode 100644 index 719bb8b..0000000 --- a/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 96cc43e..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index c7d1c5a..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/dictionaries/DucVu.xml b/.idea/dictionaries/DucVu.xml deleted file mode 100644 index 8f2536c..0000000 --- a/.idea/dictionaries/DucVu.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - favorited - manga - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 5cd135a..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 1a5e326..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 6933c1e..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 3097f31..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index b5b015c..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index af03757..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 750326c..b723b12 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,16 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' -apply plugin: 'io.fabric' android { - compileSdkVersion 29 - buildToolsVersion '28.0.3' + compileSdkVersion 30 + buildToolsVersion '30.0.3' + ndkVersion '22.0.6917172' + buildFeatures { + viewBinding true + dataBinding true + } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -14,15 +18,12 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } - dataBinding { - enabled = true - } defaultConfig { applicationId "io.github.innoobwetrust.kintamanga" minSdkVersion 16 - targetSdkVersion 29 - versionCode 39 - versionName "1.3.11" + targetSdkVersion 30 + versionCode buildVersionCode() + versionName version testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -65,7 +66,6 @@ kapt { } ext { - firebaseVersion = '16.0.9' // Dependency injection kodeinVersion = '4.1.0' glideVersion = '4.11.0' @@ -73,29 +73,24 @@ ext { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - testImplementation 'junit:junit:4.13' - androidTestImplementation('androidx.test.espresso:espresso-core:3.2.0', { + testImplementation 'junit:junit:4.13.1' + androidTestImplementation('androidx.test.espresso:espresso-core:3.3.0', { exclude group: 'com.android.support', module: 'support-annotations' }) implementation 'androidx.annotation:annotation:1.1.0' - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.vectordrawable:vectordrawable:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.multidex:multidex:2.0.1' // Kotlin standard library implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - // Crash report - implementation "com.google.firebase:firebase-core:$firebaseVersion" - implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') { - transitive = true - } - // Logging implementation 'com.jakewharton.timber:timber:4.7.1' @@ -104,7 +99,7 @@ dependencies { implementation "com.github.salomonbrys.kodein:kodein-conf:$kodeinVersion" // Network - implementation 'com.squareup.okhttp3:okhttp:4.5.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' // Persistent cookieJar implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' @@ -116,14 +111,17 @@ dependencies { implementation 'com.squareup.duktape:duktape-android:1.3.0' // Image loading library focused on smooth scrolling - implementation "com.github.bumptech.glide:glide:$glideVersion" - implementation "com.github.bumptech.glide:annotations:$glideVersion" - kapt "com.github.bumptech.glide:compiler:$glideVersion" - implementation "com.github.bumptech.glide:okhttp3-integration:$glideVersion@aar" + implementation "com.github.bumptech.glide:glide:4.11.0" + implementation "com.github.bumptech.glide:annotations:4.11.0" + kapt "com.github.bumptech.glide:compiler:4.11.0" + implementation "com.github.bumptech.glide:okhttp3-integration:4.11.0@aar" + implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { + // Excludes the support library because it's already included by Glide. + transitive = false + } // Image view - implementation 'com.github.piasy:BigImageViewer:1.3.2' - implementation 'com.github.piasy:ProgressPieIndicator:1.3.2' + implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' // ReactiveX implementation 'io.reactivex:rxandroid:1.2.1' @@ -141,8 +139,15 @@ dependencies { // Database implementation 'com.pushtorefresh.storio:sqlite:1.13.0' } -repositories { - mavenCentral() -} -apply plugin: 'com.google.gms.google-services' +configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + def requested = details.requested + // Strange problem with AndroidX => https://stackoverflow.com/a/63423780 + if (requested.group == "androidx.appcompat") { + if (!requested.name.startsWith("multidex")) { + details.useVersion "1.+" + } + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8f50954..166cff2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,7 @@ - - + tools:targetApi="q"> ("headers") with factory { headers: Headers -> Interceptor { chain -> + chain.proceed(chain.request().run { + val withCustomHeaders = newBuilder() + headers.forEach{ withCustomHeaders.header(it.first, it.second) } + withCustomHeaders.build() + }) + }} bind() with singleton { CacheControl.Builder() .maxAge(5, TimeUnit.MINUTES) diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/download/DownloadProvider.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/download/DownloadProvider.kt index 4066950..8fcdcf6 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/download/DownloadProvider.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/download/DownloadProvider.kt @@ -1,5 +1,6 @@ package io.github.innoobwetrust.kintamanga.download +import android.net.Uri import io.github.innoobwetrust.kintamanga.model.DownloadStatus import io.github.innoobwetrust.kintamanga.model.Page import io.github.innoobwetrust.kintamanga.ui.model.MangaBinding @@ -168,11 +169,11 @@ object DownloadProvider { val images = findChapterImages(chapterDir = chapterDir) ?: throw Exception("Error occur when indexing offline chapter's images") val tempMutableList = mutableListOf() - var maxNum = images.maxBy { it.first }?.first ?: 1 + var maxNum = images.maxByOrNull { it.first }?.first ?: 1 if (maxNum < 1) maxNum = 1 (1..maxNum).asSequence().map { index -> images.find { it.first == index } }.forEach { if (null != it) - tempMutableList.add("file://" + it.second.absolutePath) + tempMutableList.add(Uri.fromFile(it.second).toString()) else tempMutableList.add("") } @@ -197,4 +198,4 @@ object DownloadProvider { false } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/download/Downloader.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/download/Downloader.kt index 9fd3f31..df7a841 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/download/Downloader.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/download/Downloader.kt @@ -1,8 +1,8 @@ package io.github.innoobwetrust.kintamanga.download import android.content.Context +import android.net.Uri import android.webkit.MimeTypeMap -import com.crashlytics.android.Crashlytics import com.github.salomonbrys.kodein.conf.KodeinGlobalAware import com.github.salomonbrys.kodein.instance import io.github.innoobwetrust.kintamanga.database.DatabaseHelper @@ -18,6 +18,7 @@ import io.github.innoobwetrust.kintamanga.util.ImageConverter import io.github.innoobwetrust.kintamanga.util.RetryWithDelay import io.github.innoobwetrust.kintamanga.util.Storage import io.github.innoobwetrust.kintamanga.util.extension.* +import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Response import rx.Observable @@ -85,7 +86,6 @@ class Downloader(private val context: Context) : KodeinGlobalAware { }, { error -> Timber.e(error) - Crashlytics.logException(error) }) } @@ -131,7 +131,6 @@ class Downloader(private val context: Context) : KodeinGlobalAware { { error -> DownloadService.stop(context) Timber.e(error) - Crashlytics.logException(error) notifier.onError(error.message) } ) @@ -335,7 +334,7 @@ class Downloader(private val context: Context) : KodeinGlobalAware { .getChapterInfoProcessorForSourceName(download.manga.mangaSourceName) ?.fetchPageList(download.chapter) ?: throw Exception("Error retrieving processor or fetching page list with source:" + - " ${download.manga.mangaSourceName}") + " ${download.manga.mangaSourceName}") }.doOnNext { pages -> if (pages.isEmpty()) { throw Exception("Page list is empty") @@ -359,9 +358,18 @@ class Downloader(private val context: Context) : KodeinGlobalAware { .flatMap { Observable.from(it) } // Start downloading images, consider we can have downloaded images already .concatMap { page -> + // Adding custom headers to requests + val headers = SourceManager + .getChapterInfoProcessorForSourceName(download.manga.mangaSourceName)?.headers() // Allow pausing individual download by changing the download's status if (DownloadStatus.DOWNLOADING == download.downloadStatus) - getOrDownloadImage(page, download, tmpDir) + getOrDownloadImage( + page, + download, + tmpDir, + SourceManager.getChapterInfoProcessorForSourceName(download.manga.mangaSourceName)?.headers() + ?: instance() + ) else Observable.just(page) } @@ -388,7 +396,7 @@ class Downloader(private val context: Context) : KodeinGlobalAware { * @param download the download of the page. * @param tmpDir the temporary directory of the download. */ - private fun getOrDownloadImage(page: Page, download: Download, tmpDir: File): Observable { + private fun getOrDownloadImage(page: Page, download: Download, tmpDir: File, headers: Headers): Observable { // If the image URL is empty, do nothing if (page.imageUrls.all { it.isBlank() }) return Observable.just(page) @@ -406,12 +414,12 @@ class Downloader(private val context: Context) : KodeinGlobalAware { val pageObservable = if (null != imageFile) Observable.just(imageFile) else - downloadImage(page, tmpDir, filename) + downloadImage(page, tmpDir, filename, headers) return pageObservable // When the image is ready, set image path, progress (just in case) and status .doOnNext { file -> - page.imageFileUri = "file://" + file.absolutePath + page.imageFileUri = Uri.fromFile(file).toString() download.downloadedImages++ page.pageStatus = DownloadStatus.DOWNLOADED } @@ -430,13 +438,13 @@ class Downloader(private val context: Context) : KodeinGlobalAware { * @param tmpDir the temporary directory of the download. * @param filename the filename of the image. */ - private fun downloadImage(page: Page, tmpDir: File, filename: String): Observable { + private fun downloadImage(page: Page, tmpDir: File, filename: String, headers: Headers): Observable { page.pageStatus = DownloadStatus.DOWNLOADING return Observable .from( page.imageUrls.map { instance("chapter") - .newCall(GET(it)) + .newCall(GET(it, headers = headers)) .asObservableSuccess() } ) @@ -453,8 +461,8 @@ class Downloader(private val context: Context) : KodeinGlobalAware { } var extension = getImageExtension(response, file) if (ImageConverter.convertRequiredImageTypes - .map { imgType -> imgType.substringAfter("image/") } - .contains(extension)) { + .map { imgType -> imgType.substringAfter("image/") } + .contains(extension)) { extension = "png" ImageConverter.convertToSupportedImage(file) } @@ -484,7 +492,7 @@ class Downloader(private val context: Context) : KodeinGlobalAware { private fun getImageExtension(response: Response, file: File): String { // Read content type if available. val mime = response.body?.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" } - // Else guess from the uri. + // Else guess from the uri. ?: context.contentResolver.getType(file.getUriCompat(context)) // Else read magic numbers. ?: ImageConverter.findImageMime { file.inputStream() } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/model/Page.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/model/Page.kt index 7cf292c..5d687f3 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/model/Page.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/model/Page.kt @@ -10,7 +10,7 @@ class Page( var chapterIndex: Int = -1, val pageIndex: Int, var imageUrls: List = listOf(""), - var imageFileUri: String = "" + var imageFileUri: String = "", ) : BaseObservable(), Serializable { init { @@ -18,14 +18,17 @@ class Page( } @get:Bindable - @Transient @Volatile var pageStatus: DownloadStatus = DownloadStatus.NOT_DOWNLOADED + @Transient + @Volatile + var pageStatus: DownloadStatus = DownloadStatus.NOT_DOWNLOADED set(value) { field = value statusSubject?.onNext(value) notifyPropertyChanged(BR.pageStatus) } - @Transient private var statusSubject: PublishSubject? = null + @Transient + private var statusSubject: PublishSubject? = null fun setStatusSubject(subject: PublishSubject?) { this.statusSubject = subject diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/service/DownloadService.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/service/DownloadService.kt index b3b2bbb..c3a961f 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/service/DownloadService.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/service/DownloadService.kt @@ -8,7 +8,6 @@ import android.net.NetworkInfo.State.DISCONNECTED import android.os.IBinder import android.os.PowerManager import android.widget.Toast -import com.crashlytics.android.Crashlytics import com.github.pwittchen.reactivenetwork.library.Connectivity import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork import com.github.salomonbrys.kodein.conf.KodeinGlobalAware @@ -123,7 +122,6 @@ class DownloadService : Service(), KodeinGlobalAware { }, { error -> toast(R.string.download_service_error, Toast.LENGTH_LONG) Timber.e(error) - Crashlytics.logException(error) stopSelf() }) ) @@ -176,4 +174,4 @@ class DownloadService : Service(), KodeinGlobalAware { private fun PowerManager.WakeLock.acquireIfNeeded() { if (!isHeld) acquire(1800000) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/SourceManager.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/SourceManager.kt index e8585fd..245a94a 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/SourceManager.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/SourceManager.kt @@ -32,7 +32,7 @@ object SourceManager { fun normalizeUri(uri: Uri): String? { val sourceName = findFirstSourceNameForHost(host = uri.host) ?: return null val source = getSourceByName(sourceName = sourceName) ?: return null - val uriString = uri.toString().let { if (!it.isBlank() && it.last() != '/') "$it/" else it } + val uriString = uri.toString().let { if (it.isNotBlank() && it.last() != '/') "$it/" else it } return uriString.replaceFirst(source.aliasRootUri, source.rootUri) } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/dom/parser/DomSegmentParser.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/dom/parser/DomSegmentParser.kt index 19ee53b..2156973 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/dom/parser/DomSegmentParser.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/dom/parser/DomSegmentParser.kt @@ -50,12 +50,12 @@ interface DomSegmentParser { } fun previousPageUriFromDocument(document: Document): String = - if (!previousPageSelector.isBlank()) + if (previousPageSelector.isNotBlank()) document.parseUri(selector = previousPageSelector, attribute = "href") else "" fun nextPageUriFromDocument(document: Document): String = - if (!nextPageSelector.isBlank()) + if (nextPageSelector.isNotBlank()) document.parseUri(selector = nextPageSelector, attribute = "href") else "" -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/en/mangahere/MangaHereMangaInfoProcessor.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/en/mangahere/MangaHereMangaInfoProcessor.kt index 4888114..d5a4e9a 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/en/mangahere/MangaHereMangaInfoProcessor.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/en/mangahere/MangaHereMangaInfoProcessor.kt @@ -51,7 +51,7 @@ object MangaHereMangaInfoProcessor : DomMangaInfoProcessor { override fun headers(): Headers = instance() override fun cacheControl(): CacheControl = instance() - override fun mangaFromDocument(document: Document): MangaBinding? = MangaBinding().apply { + override fun mangaFromDocument(document: Document): MangaBinding = MangaBinding().apply { mangaSourceName = source.sourceName mangaUri = document.baseUri() mangaTitle = titleFromDocument(document) @@ -105,4 +105,4 @@ object MangaHereMangaInfoProcessor : DomMangaInfoProcessor { } }.sortedBy { it.chapterIndex } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/AllSegment.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/AllSegment.kt index a49fb8a..34aeb73 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/AllSegment.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/AllSegment.kt @@ -34,7 +34,7 @@ object AllSegment : DomSegment { override var isUsable: Boolean = true override var isRequestByGET: Boolean = true - override fun headers(): Headers = instance() + override fun headers(): Headers = instance().newBuilder().add("Referer", source.rootUri).build() override fun cacheControl(): CacheControl = instance() override var urisSelector: String = ".table.table-hover>tbody>tr>td:nth-child(1)>a" diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/HocVienTruyenTranhChapterInfoProcessor.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/HocVienTruyenTranhChapterInfoProcessor.kt index d6134a9..46e041f 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/HocVienTruyenTranhChapterInfoProcessor.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/HocVienTruyenTranhChapterInfoProcessor.kt @@ -11,6 +11,6 @@ object HocVienTruyenTranhChapterInfoProcessor : DomChapterInfoProcessor { override var imagesUriSelector: String = ".manga-container>img" override var imagesUriAttribute: String = "src" - override fun headers(): Headers = instance() + override fun headers(): Headers = instance().newBuilder().add("Referer", source.rootUri).build() override fun cacheControl(): CacheControl = instance() } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/HocVienTruyenTranhMangaInfoProcessor.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/HocVienTruyenTranhMangaInfoProcessor.kt index 0bdd4b3..b5da237 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/HocVienTruyenTranhMangaInfoProcessor.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/hocvientruyentranh/HocVienTruyenTranhMangaInfoProcessor.kt @@ -44,10 +44,10 @@ object HocVienTruyenTranhMangaInfoProcessor : DomMangaInfoProcessor { override var chaptersUpdateTimeAttribute: String = "text" override var chaptersIndexedDescending: Boolean = true - override fun headers(): Headers = instance() + override fun headers(): Headers = instance().newBuilder().add("Referer", source.rootUri).build() override fun cacheControl(): CacheControl = instance() - override fun mangaFromDocument(document: Document): MangaBinding? = MangaBinding().apply { + override fun mangaFromDocument(document: Document): MangaBinding = MangaBinding().apply { mangaSourceName = source.sourceName mangaUri = document.baseUri() mangaTitle = titleFromDocument(document) diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/truyentranhtuan/TruyenTranhTuanMangaInfoProcessor.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/truyentranhtuan/TruyenTranhTuanMangaInfoProcessor.kt index 02004d9..a7d68c0 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/truyentranhtuan/TruyenTranhTuanMangaInfoProcessor.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/source/instance/vi/truyentranhtuan/TruyenTranhTuanMangaInfoProcessor.kt @@ -47,7 +47,7 @@ object TruyenTranhTuanMangaInfoProcessor : DomMangaInfoProcessor { override fun headers(): Headers = instance() override fun cacheControl(): CacheControl = instance() - override fun mangaFromDocument(document: Document): MangaBinding? = MangaBinding().apply { + override fun mangaFromDocument(document: Document): MangaBinding = MangaBinding().apply { mangaSourceName = source.sourceName mangaUri = document.baseUri() mangaTitle = titleFromDocument(document) @@ -63,4 +63,4 @@ object TruyenTranhTuanMangaInfoProcessor : DomMangaInfoProcessor { mangaWarning = warningFromDocument(document) chapters = chaptersFromDocument(document) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/downloader/DownloadAdapter.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/downloader/DownloadAdapter.kt index 117d47c..8fb58a8 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/downloader/DownloadAdapter.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/downloader/DownloadAdapter.kt @@ -2,10 +2,7 @@ package io.github.innoobwetrust.kintamanga.ui.downloader import android.view.ContextThemeWrapper import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.AppCompatImageButton -import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.PopupMenu import androidx.recyclerview.widget.RecyclerView import com.github.salomonbrys.kodein.conf.KodeinGlobalAware @@ -15,7 +12,6 @@ import io.github.innoobwetrust.kintamanga.databinding.HolderDownloadBinding import io.github.innoobwetrust.kintamanga.download.Downloader import io.github.innoobwetrust.kintamanga.model.Download import io.github.innoobwetrust.kintamanga.model.DownloadStatus -import kotlinx.android.synthetic.main.holder_download.view.* class DownloadAdapter( private var downloaderActivity: DownloaderActivity? @@ -48,55 +44,57 @@ class DownloadAdapter( val download = downloads?.getOrNull(position) holder.bind(download) download?.let { - holder.mangaTitle.isSelected = true - holder.downloadStatus.isSelected = true - holder.chapterTitle.isSelected = true - holder.downloadProgressText.isSelected = true - holder.downloadOption.setOnClickListener { - val downloadItemOption = - PopupMenu(contextWrapper!!, holder.downloadOption) - downloadItemOption.inflate(R.menu.menu_download_item) - downloadItemOption.menu.apply { - // Set start button visibility. - findItem(R.id.download_item_option_resume)?.isVisible = - download.downloadStatus == DownloadStatus.STOPPED - // Set pause button visibility. - findItem(R.id.download_item_option_stop)?.isVisible = - download.downloadStatus in listOf(DownloadStatus.DOWNLOADING, DownloadStatus.QUEUE) - } - downloadItemOption.setOnMenuItemClickListener { item -> - when (item.itemId) { - R.id.download_item_option_resume -> { - if (downloads?.getOrNull(holder.adapterPosition) - ?.let(instance()::resume) == true) { - notifyItemChanged(holder.adapterPosition) - return@setOnMenuItemClickListener true + holder.binding.apply { + mangaTitle.isSelected = true + downloadStatus.isSelected = true + chapterTitle.isSelected = true + downloadProgressText.isSelected = true + downloadOption.setOnClickListener { + val downloadItemOption = + PopupMenu(contextWrapper!!, downloadOption) + downloadItemOption.inflate(R.menu.menu_download_item) + downloadItemOption.menu.apply { + // Set start button visibility. + findItem(R.id.download_item_option_resume)?.isVisible = + download.downloadStatus == DownloadStatus.STOPPED + // Set pause button visibility. + findItem(R.id.download_item_option_stop)?.isVisible = + download.downloadStatus in listOf(DownloadStatus.DOWNLOADING, DownloadStatus.QUEUE) + } + downloadItemOption.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.download_item_option_resume -> { + if (downloads?.getOrNull(holder.adapterPosition) + ?.let(instance()::resume) == true) { + notifyItemChanged(holder.adapterPosition) + return@setOnMenuItemClickListener true + } } - } - R.id.download_item_option_stop -> { - if (downloads?.getOrNull(holder.adapterPosition) - ?.let(instance()::stop) == true) { - notifyItemChanged(holder.adapterPosition) - return@setOnMenuItemClickListener true + R.id.download_item_option_stop -> { + if (downloads?.getOrNull(holder.adapterPosition) + ?.let(instance()::stop) == true) { + notifyItemChanged(holder.adapterPosition) + return@setOnMenuItemClickListener true + } } - } - R.id.download_item_option_remove -> { - if (downloads?.getOrNull(holder.adapterPosition) - ?.let(instance()::remove) == true) { - notifyItemRemoved(holder.adapterPosition) - return@setOnMenuItemClickListener true + R.id.download_item_option_remove -> { + if (downloads?.getOrNull(holder.adapterPosition) + ?.let(instance()::remove) == true) { + notifyItemRemoved(holder.adapterPosition) + return@setOnMenuItemClickListener true + } } } + return@setOnMenuItemClickListener false } - return@setOnMenuItemClickListener false + downloadItemOption.show() } - downloadItemOption.show() } } } override fun onViewRecycled(holder: ViewHolder) { - holder.downloadOption.setOnClickListener(null) + holder.binding.downloadOption.setOnClickListener(null) holder.bind(null) System.gc() super.onViewRecycled(holder) @@ -117,19 +115,6 @@ class DownloadAdapter( inner class ViewHolder( val binding: HolderDownloadBinding ) : RecyclerView.ViewHolder(binding.root) { - val root: View - get() = binding.root - val mangaTitle: AppCompatTextView - get() = binding.root.mangaTitle - val downloadStatus: AppCompatTextView - get() = binding.root.downloadStatus - val chapterTitle: AppCompatTextView - get() = binding.root.chapterTitle - val downloadProgressText: AppCompatTextView - get() = binding.root.downloadProgressText - val downloadOption: AppCompatImageButton - get() = binding.root.downloadOption - fun bind(download: Download?) { binding.download = download binding.executePendingBindings() diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/downloader/DownloaderActivity.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/downloader/DownloaderActivity.kt index 9305e98..90eda52 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/downloader/DownloaderActivity.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/downloader/DownloaderActivity.kt @@ -8,9 +8,9 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.github.salomonbrys.kodein.conf.KodeinGlobalAware import com.github.salomonbrys.kodein.instance import io.github.innoobwetrust.kintamanga.R +import io.github.innoobwetrust.kintamanga.databinding.ActivityDownloaderBinding import io.github.innoobwetrust.kintamanga.download.Downloader import io.github.innoobwetrust.kintamanga.service.DownloadService -import kotlinx.android.synthetic.main.activity_downloader.* import rx.android.schedulers.AndroidSchedulers import rx.subscriptions.CompositeSubscription @@ -24,16 +24,18 @@ class DownloaderActivity : AppCompatActivity(), KodeinGlobalAware { * Whether the download queue is running or not. */ private var isRunning: Boolean = false + private lateinit var binding: ActivityDownloaderBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_downloader) - setSupportActionBar(toolbar) + binding = ActivityDownloaderBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) supportActionBar?.apply { setDisplayHomeAsUpEnabled(true) setDisplayShowHomeEnabled(true) } - downloadList?.apply { + binding.downloadList.apply { layoutManager = LinearLayoutManager(this@DownloaderActivity) adapter = DownloadAdapter(this@DownloaderActivity) } @@ -44,7 +46,7 @@ class DownloaderActivity : AppCompatActivity(), KodeinGlobalAware { ) subscriptions.add(instance().queue.getUpdatedObservable() .observeOn(AndroidSchedulers.mainThread()) - .subscribe { downloadList?.adapter?.notifyDataSetChanged() } + .subscribe { binding.downloadList.adapter?.notifyDataSetChanged() } ) } @@ -67,8 +69,7 @@ class DownloaderActivity : AppCompatActivity(), KodeinGlobalAware { return true } - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - if (null == item) return false + override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.downloader_start -> { DownloadService.start(this) @@ -87,7 +88,7 @@ class DownloaderActivity : AppCompatActivity(), KodeinGlobalAware { R.id.downloader_stop, R.id.downloader_remove_all ) -> return true.also { - downloadList?.adapter?.notifyDataSetChanged() + binding.downloadList.adapter?.notifyDataSetChanged() } } return false @@ -99,7 +100,7 @@ class DownloaderActivity : AppCompatActivity(), KodeinGlobalAware { } override fun onStop() { - downloadList?.adapter = null + binding.downloadList.adapter = null super.onStop() } @@ -116,6 +117,6 @@ class DownloaderActivity : AppCompatActivity(), KodeinGlobalAware { private fun onQueueStatusChange(running: Boolean) { isRunning = running invalidateOptionsMenu() - downloadList?.adapter?.notifyDataSetChanged() + binding.downloadList.adapter?.notifyDataSetChanged() } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterActivity.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterActivity.kt index 6079d72..5c1161a 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterActivity.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterActivity.kt @@ -6,17 +6,16 @@ import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager -import com.crashlytics.android.Crashlytics import com.github.salomonbrys.kodein.conf.KodeinGlobalAware import com.github.salomonbrys.kodein.instance import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.typedToJson import com.google.gson.Gson import io.github.innoobwetrust.kintamanga.R +import io.github.innoobwetrust.kintamanga.databinding.ActivityFilterBinding import io.github.innoobwetrust.kintamanga.source.model.SourceSegment import io.github.innoobwetrust.kintamanga.ui.main.list.MangaListFragment import io.github.innoobwetrust.kintamanga.util.extension.toast -import kotlinx.android.synthetic.main.activity_filter.* import rx.Subscription import timber.log.Timber @@ -32,11 +31,13 @@ class FilterActivity : AppCompatActivity(), KodeinGlobalAware, FilterNetworkLoad private lateinit var userInput: MutableMap private lateinit var singleChoice: MutableMap private lateinit var multipleChoices: MutableSet> + private lateinit var binding: ActivityFilterBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_filter) - setSupportActionBar(toolbar) + binding = ActivityFilterBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) supportActionBar?.apply { setDisplayHomeAsUpEnabled(true) setDisplayShowHomeEnabled(true) @@ -46,19 +47,19 @@ class FilterActivity : AppCompatActivity(), KodeinGlobalAware, FilterNetworkLoad userInput = gson.fromJson( intent.getStringExtra( MangaListFragment.Companion.Intents.USER_INPUT.key - ) + ) ?: "" ) singleChoice = gson.fromJson( intent.getStringExtra( MangaListFragment.Companion.Intents.SINGLE_CHOICE.key - ) + ) ?: "" ) multipleChoices = gson.fromJson( intent.getStringExtra( MangaListFragment.Companion.Intents.MULTIPLE_CHOICES.key - ) + ) ?: "" ) } @@ -87,9 +88,9 @@ class FilterActivity : AppCompatActivity(), KodeinGlobalAware, FilterNetworkLoad } private val onRefreshedFilterData: (Boolean) -> Unit = { success -> - progress?.visibility = View.GONE + binding.progress.visibility = View.GONE if (success) { - filterButton?.setOnClickListener { + binding.filterButton.setOnClickListener { val resultIntent = Intent() .putExtra( MangaListFragment.Companion.Intents.USER_INPUT.key, @@ -107,8 +108,8 @@ class FilterActivity : AppCompatActivity(), KodeinGlobalAware, FilterNetworkLoad finish() } if (mangaSegment.filterByUserInput.isNotEmpty()) { - userInputRecyclerView?.visibility = View.VISIBLE - userInputRecyclerView?.apply { + binding.userInputRecyclerView.visibility = View.VISIBLE + binding.userInputRecyclerView.apply { layoutManager = LinearLayoutManager(this@FilterActivity) adapter = FilterUserInputAdapter( userInput = this@FilterActivity.userInput, @@ -118,11 +119,11 @@ class FilterActivity : AppCompatActivity(), KodeinGlobalAware, FilterNetworkLoad mangaSegment.filterRequiredDefaultUserInput ) } - userInputRecyclerView?.isNestedScrollingEnabled = false + binding.userInputRecyclerView.isNestedScrollingEnabled = false } if (mangaSegment.filterBySingleChoice.isNotEmpty()) { - singleChoiceRecyclerView?.visibility = View.VISIBLE - singleChoiceRecyclerView?.apply { + binding.singleChoiceRecyclerView.visibility = View.VISIBLE + binding.singleChoiceRecyclerView.apply { layoutManager = LinearLayoutManager(this@FilterActivity) adapter = FilterSingleChoiceAdapter( singleChoice = this@FilterActivity.singleChoice, @@ -132,11 +133,11 @@ class FilterActivity : AppCompatActivity(), KodeinGlobalAware, FilterNetworkLoad mangaSegment.filterRequiredDefaultSingleChoice ) } - singleChoiceRecyclerView?.isNestedScrollingEnabled = false + binding.singleChoiceRecyclerView.isNestedScrollingEnabled = false } if (mangaSegment.filterByMultipleChoices.isNotEmpty()) { - multipleChoicesRecyclerView?.visibility = View.VISIBLE - multipleChoicesRecyclerView?.apply { + binding.multipleChoicesRecyclerView.visibility = View.VISIBLE + binding.multipleChoicesRecyclerView.apply { layoutManager = LinearLayoutManager(this@FilterActivity) adapter = FilterMultipleChoicesAdapter( multipleChoices = multipleChoices, @@ -144,39 +145,38 @@ class FilterActivity : AppCompatActivity(), KodeinGlobalAware, FilterNetworkLoad filterByMultipleChoices = mangaSegment.filterByMultipleChoices ) } - multipleChoicesRecyclerView?.isNestedScrollingEnabled = false + binding.multipleChoicesRecyclerView.isNestedScrollingEnabled = false } } else { - filterButton?.visibility = View.GONE + binding.filterButton.visibility = View.GONE toast(R.string.filter_activity_load_filter_error_text) } - resetButton?.setOnClickListener { - userInputRecyclerView?.apply { + binding.resetButton.setOnClickListener { + binding.userInputRecyclerView.apply { mangaSegment.filterByUserInput.forEachIndexed { index, _ -> (findViewHolderForAdapterPosition(index) as? FilterUserInputAdapter.ViewHolder)?.reset() } } - singleChoiceRecyclerView?.apply { + binding.singleChoiceRecyclerView.apply { mangaSegment.filterBySingleChoice.toList().forEachIndexed { index, _ -> (findViewHolderForAdapterPosition(index) as? FilterSingleChoiceAdapter.ViewHolder)?.reset() } } - multipleChoicesRecyclerView?.apply { + binding.multipleChoicesRecyclerView.apply { mangaSegment.filterByMultipleChoices.toList().forEachIndexed { index, _ -> (findViewHolderForAdapterPosition(index) as? FilterMultipleChoicesAdapter.ViewHolder)?.reset() } } } - cancelButton?.setOnClickListener { onBackPressed() } - progress?.visibility = View.GONE + binding.cancelButton.setOnClickListener { onBackPressed() } + binding.progress.visibility = View.GONE } private val onRefreshError: (Throwable) -> Unit = { error -> toast(R.string.filter_activity_load_filter_error_text) Timber.e(error) - Crashlytics.logException(error) } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterMultipleChoicesAdapter.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterMultipleChoicesAdapter.kt index 2cc8849..08837e9 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterMultipleChoicesAdapter.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterMultipleChoicesAdapter.kt @@ -1,14 +1,11 @@ package io.github.innoobwetrust.kintamanga.ui.filter import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.widget.CheckBox -import android.widget.LinearLayout -import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import io.github.innoobwetrust.kintamanga.R -import kotlinx.android.synthetic.main.holder_filter_multiple_choices.view.* +import io.github.innoobwetrust.kintamanga.databinding.HolderFilterMultipleChoicesBinding class FilterMultipleChoicesAdapter( private val multipleChoices: MutableSet>, @@ -20,9 +17,8 @@ class FilterMultipleChoicesAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) - return ViewHolder( - layoutInflater.inflate(R.layout.holder_filter_multiple_choices, parent, false) - ) + val binding = HolderFilterMultipleChoicesBinding.inflate(layoutInflater, parent, false) + return ViewHolder(binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { @@ -39,11 +35,7 @@ class FilterMultipleChoicesAdapter( return filterList.size } - inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - private val multipleChoicesOptionLabel: AppCompatTextView = - view.multipleChoicesOption.multipleChoicesOptionLabel - private val multipleChoicesOptionValues: LinearLayout = - view.multipleChoicesOption.multipleChoicesOptionValues + inner class ViewHolder(val binding: HolderFilterMultipleChoicesBinding) : RecyclerView.ViewHolder(binding.root) { private lateinit var dataList: List> private var checkboxList: MutableList = mutableListOf() private var key: String = "" @@ -55,9 +47,9 @@ class FilterMultipleChoicesAdapter( multipleChoices: MutableSet> ) { this.key = key - multipleChoicesOptionLabel.text = label + binding.multipleChoicesOptionLabel.text = label dataList = dataMap.toList() - multipleChoicesOptionValues.let { linearLayout -> + binding.multipleChoicesOptionValues.let { linearLayout -> dataList.forEach { (label, value) -> val inflater = LayoutInflater.from(linearLayout.context) val checkBox = inflater.inflate( @@ -85,7 +77,7 @@ class FilterMultipleChoicesAdapter( } override fun toString(): String { - return super.toString() + multipleChoicesOptionLabel.text + return super.toString() + binding.multipleChoicesOptionLabel.text } } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterSingleChoiceAdapter.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterSingleChoiceAdapter.kt index 70a2ad4..cc937d9 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterSingleChoiceAdapter.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterSingleChoiceAdapter.kt @@ -5,11 +5,9 @@ import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.ArrayAdapter -import androidx.appcompat.widget.AppCompatSpinner -import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import io.github.innoobwetrust.kintamanga.R -import kotlinx.android.synthetic.main.holder_filter_single_choice.view.* +import io.github.innoobwetrust.kintamanga.databinding.HolderFilterSingleChoiceBinding class FilterSingleChoiceAdapter( private val singleChoice: MutableMap, @@ -21,7 +19,8 @@ class FilterSingleChoiceAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) - return ViewHolder(layoutInflater.inflate(R.layout.holder_filter_single_choice, parent, false)) + val binding = HolderFilterSingleChoiceBinding.inflate(layoutInflater, parent, false) + return ViewHolder(binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { @@ -32,7 +31,7 @@ class FilterSingleChoiceAdapter( dataMap = filterList[position].second, defaultMap = filterRequiredDefaultSingleChoice ) - holder.singleChoiceOption.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + holder.binding.singleChoiceOption.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onNothingSelected(parent: AdapterView<*>?) { } @@ -51,18 +50,14 @@ class FilterSingleChoiceAdapter( else -> indexOf(singleChoice[key]) } } - if (defaultIndex >= 0) holder.singleChoiceOption.setSelection(defaultIndex) + if (defaultIndex >= 0) holder.binding.singleChoiceOption.setSelection(defaultIndex) } override fun getItemCount(): Int { return filterList.size } - inner class ViewHolder(var view: View) : RecyclerView.ViewHolder(view) { - private val singleChoiceLabel: AppCompatTextView - get() = view.singleChoiceLabel - val singleChoiceOption: AppCompatSpinner - get() = view.singleChoiceOption + inner class ViewHolder(val binding: HolderFilterSingleChoiceBinding) : RecyclerView.ViewHolder(binding.root) { // List pair of label-value lateinit var dataList: List> private lateinit var defaultMap: Map @@ -75,14 +70,14 @@ class FilterSingleChoiceAdapter( defaultMap: Map ) { this.key = key - singleChoiceLabel.text = label + binding.singleChoiceLabel.text = label dataList = dataMap.toList() val singleChoiceDataAdapter: ArrayAdapter = ArrayAdapter( - view.context, + binding.root.context, R.layout.themed_spinner_item, dataList.map { it.first } ).also { it.setDropDownViewResource(R.layout.themed_spinner_dropdown_item) } - singleChoiceOption.adapter = singleChoiceDataAdapter + binding.singleChoiceOption.adapter = singleChoiceDataAdapter this.defaultMap = defaultMap } @@ -90,11 +85,11 @@ class FilterSingleChoiceAdapter( val defaultIndex = dataList .map { it.second } .indexOf(defaultMap[key]) - if (defaultIndex >= 0) singleChoiceOption.setSelection(defaultIndex) + if (defaultIndex >= 0) binding.singleChoiceOption.setSelection(defaultIndex) } override fun toString(): String { - return super.toString() + singleChoiceLabel.text + return super.toString() + binding.singleChoiceLabel.text } } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterUserInputAdapter.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterUserInputAdapter.kt index 264b8d3..9599b70 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterUserInputAdapter.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/filter/FilterUserInputAdapter.kt @@ -3,13 +3,9 @@ package io.github.innoobwetrust.kintamanga.ui.filter import android.text.Editable import android.text.TextWatcher import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.AppCompatEditText -import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView -import io.github.innoobwetrust.kintamanga.R -import kotlinx.android.synthetic.main.holder_filter_user_input.view.* +import io.github.innoobwetrust.kintamanga.databinding.HolderFilterUserInputBinding class FilterUserInputAdapter( private val userInput: MutableMap, @@ -20,7 +16,8 @@ class FilterUserInputAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) - return ViewHolder(layoutInflater.inflate(R.layout.holder_filter_user_input, parent, false)) + val binding = HolderFilterUserInputBinding.inflate(layoutInflater, parent, false) + return ViewHolder(binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { @@ -30,7 +27,7 @@ class FilterUserInputAdapter( label = filterKeyLabel[key] ?: key, inputValue = userInput[key] ) - holder.userInputInput.addTextChangedListener(object : TextWatcher { + holder.binding.userInputInput.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable?) {} override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { @@ -43,28 +40,26 @@ class FilterUserInputAdapter( return filterByUserInput.size } - inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - private var userInputLabel: AppCompatTextView = view.userInputLabel - var userInputInput: AppCompatEditText = view.userInputInput + inner class ViewHolder(val binding: HolderFilterUserInputBinding) : RecyclerView.ViewHolder(binding.root) { lateinit var key: String fun bind(key: String, label: String, inputValue: String?) { this.key = key - userInputLabel.text = label + binding.userInputLabel.text = label val defaultInputValue = inputValue ?: filterRequiredDefaultUserInput[label] - if (null != defaultInputValue) userInputInput.text = + if (null != defaultInputValue) binding.userInputInput.text = Editable.Factory.getInstance().newEditable(defaultInputValue) } fun reset() { val defaultInputValue = filterRequiredDefaultUserInput[key] - if (null != defaultInputValue) userInputInput.text = + if (null != defaultInputValue) binding.userInputInput.text = Editable.Factory.getInstance().newEditable(defaultInputValue) - else userInputInput.text = Editable.Factory.getInstance().newEditable("") + else binding.userInputInput.text = Editable.Factory.getInstance().newEditable("") } override fun toString(): String { - return super.toString() + userInputLabel.text + return super.toString() + binding.userInputLabel.text } } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/MainActivity.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/MainActivity.kt index f4d6850..9c22a85 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/MainActivity.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/MainActivity.kt @@ -15,11 +15,10 @@ import com.github.salomonbrys.kodein.instance import com.google.android.material.navigation.NavigationView import io.github.innoobwetrust.kintamanga.KINTAMAngaPreferences import io.github.innoobwetrust.kintamanga.R +import io.github.innoobwetrust.kintamanga.databinding.ActivityMainBinding import io.github.innoobwetrust.kintamanga.ui.downloader.DownloaderActivity import io.github.innoobwetrust.kintamanga.ui.main.favorite.FavoriteFragment import io.github.innoobwetrust.kintamanga.ui.main.list.MangaListFragment -import kotlinx.android.synthetic.main.activity_main.* -import kotlinx.android.synthetic.main.content_main.* class MainActivity : AppCompatActivity(), @@ -36,22 +35,24 @@ class MainActivity : } private var currentFragmentIndex: Int = 0 + lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - setSupportActionBar(toolbar) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.contentMain.toolbar) val toggle = ActionBarDrawerToggle( this, - drawer_layout, - toolbar, + binding.drawerLayout, + binding.contentMain.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close ) - drawer_layout.addDrawerListener(toggle) + binding.drawerLayout.addDrawerListener(toggle) toggle.syncState() - nav_view.setNavigationItemSelectedListener(this) + binding.navView.setNavigationItemSelectedListener(this) } override fun onPostCreate(savedInstanceState: Bundle?) { @@ -64,7 +65,7 @@ class MainActivity : } else { currentFragmentIndex = savedInstanceState.getInt(Preferences.CURRENT_FRAGMENT.key) } - nav_view.menu.getItem(currentFragmentIndex).isChecked = true + binding.navView.menu.getItem(currentFragmentIndex).isChecked = true } override fun onSaveInstanceState(outState: Bundle) { @@ -73,8 +74,8 @@ class MainActivity : } override fun onBackPressed() { - if (drawer_layout.isDrawerOpen(GravityCompat.START)) { - drawer_layout.closeDrawer(GravityCompat.START) + if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) { + binding.drawerLayout.closeDrawer(GravityCompat.START) } else { finish() } @@ -117,12 +118,12 @@ class MainActivity : startActivity(fanPageIntent) } } - drawer_layout.closeDrawer(GravityCompat.START) + binding.drawerLayout.closeDrawer(GravityCompat.START) return selected } private fun switchToFragmentIndex(fragmentIndex: Int): Boolean { - return if (fragmentIndex in 0 until fragmentTagList.size && + return if (fragmentIndex in fragmentTagList.indices && fragmentIndex != currentFragmentIndex) { currentFragmentIndex = fragmentIndex doFragmentTransaction() @@ -131,20 +132,19 @@ class MainActivity : } private fun doFragmentTransaction() { - val toBeAttachedFragment: Fragment? = - supportFragmentManager.findFragmentByTag(fragmentTagList[currentFragmentIndex]) + val toBeAttachedFragment: Fragment = + supportFragmentManager.findFragmentByTag(fragmentTagList[currentFragmentIndex]) ?: when (currentFragmentIndex) { + 1 -> FavoriteFragment.newInstance() + else -> MangaListFragment.newInstance() + } as Fragment supportFragmentManager .beginTransaction() .replace( R.id.content, - toBeAttachedFragment ?: when (currentFragmentIndex) { - 0 -> MangaListFragment.newInstance() - 1 -> FavoriteFragment.newInstance() - else -> MangaListFragment.newInstance() - } as Fragment, + toBeAttachedFragment, fragmentTagList[currentFragmentIndex] ).commit() - spinnerPrimary?.visibility = View.GONE - spinnerSecondary?.visibility = View.GONE + binding.contentMain.spinnerPrimary.visibility = View.GONE + binding.contentMain.spinnerSecondary.visibility = View.GONE } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/favorite/FavoriteFragment.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/favorite/FavoriteFragment.kt index d1fb6a3..ec7ff1d 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/favorite/FavoriteFragment.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/favorite/FavoriteFragment.kt @@ -8,19 +8,18 @@ import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.ArrayAdapter -import androidx.appcompat.widget.AppCompatSpinner import androidx.fragment.app.Fragment import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.load.model.GlideUrl -import com.crashlytics.android.Crashlytics import com.github.salomonbrys.kodein.conf.KodeinGlobalAware import com.github.salomonbrys.kodein.instance import io.github.innoobwetrust.kintamanga.R import io.github.innoobwetrust.kintamanga.database.DatabaseHelper import io.github.innoobwetrust.kintamanga.database.model.MangaDb +import io.github.innoobwetrust.kintamanga.databinding.FragmentMangaListBinding import io.github.innoobwetrust.kintamanga.source.SourceManager import io.github.innoobwetrust.kintamanga.source.model.CatalogPages import io.github.innoobwetrust.kintamanga.ui.main.ElementInfoInteractionListener @@ -31,9 +30,6 @@ import io.github.innoobwetrust.kintamanga.ui.manga.MangaInfoActivity import io.github.innoobwetrust.kintamanga.ui.model.ElementInfo import io.github.innoobwetrust.kintamanga.ui.model.MangaBinding import io.github.innoobwetrust.kintamanga.util.extension.toast -import kotlinx.android.synthetic.main.content_main.* -import kotlinx.android.synthetic.main.fragment_manga_list.* -import kotlinx.android.synthetic.main.fragment_manga_list.view.* import okhttp3.OkHttpClient import rx.Subscription import timber.log.Timber @@ -58,12 +54,14 @@ class FavoriteFragment : override var sourceNameFilter: String? = null + private lateinit var binding: FragmentMangaListBinding + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.fragment_manga_list, container, false) - setupFavoriteListView(view = view) + savedInstanceState: Bundle?): View { + binding = FragmentMangaListBinding.inflate(inflater, container, false) + setupFavoriteListView() setupToolbar() - return view + return binding.root } override fun onResume() { @@ -72,6 +70,7 @@ class FavoriteFragment : Glide.get(it.applicationContext).registry.replace( GlideUrl::class.java, InputStream::class.java, + // FIXME: custom headers per source OkHttpUrlLoader.Factory(instance("cover")) ) } @@ -79,67 +78,59 @@ class FavoriteFragment : override fun onPause() { disposeAllLoaderDisposables() - swipeRefreshLayout?.isRefreshing = false + binding.swipeRefreshLayout.isRefreshing = false super.onPause() } override fun onDestroy() { - listElementInfos?.adapter = null + binding.listElementInfos.adapter = null System.gc() super.onDestroy() } - private fun setupFavoriteListView(view: View) { - if (view.listElementInfos is RecyclerView) { - when (activity?.resources?.configuration?.orientation) { - Configuration.ORIENTATION_PORTRAIT -> - view.listElementInfos.layoutManager = - GridLayoutManager( - view.listElementInfos.context, - mColumnCount - 2, - RecyclerView.VERTICAL, - false - ) - Configuration.ORIENTATION_LANDSCAPE -> - view.listElementInfos.layoutManager = - GridLayoutManager( - view.listElementInfos.context, - mColumnCount, - RecyclerView.VERTICAL, - false - ) - } - view.listElementInfos.adapter = MangaListAdapter( - catalogPages = catalogPages, - listType = mListType, - elementInfoInteractionListener = this - ) - } + private fun setupFavoriteListView() { + if (activity?.resources?.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) binding.listElementInfos.layoutManager = + GridLayoutManager( + binding.listElementInfos.context, + mColumnCount, + RecyclerView.VERTICAL, + false + ) + else binding.listElementInfos.layoutManager = + GridLayoutManager( + binding.listElementInfos.context, + mColumnCount - 2, + RecyclerView.VERTICAL, + false + ) + binding.listElementInfos.adapter = MangaListAdapter( + catalogPages = catalogPages, + listType = mListType, + elementInfoInteractionListener = this + ) } private fun setupToolbar() { - (activity as? MainActivity)?.supportActionBar?.title = null - if (activity?.spinnerPrimary is AppCompatSpinner) { - if (groupSpinnerAdapter != activity?.spinnerPrimary?.adapter) - setupSpinners() + (activity as? MainActivity)?.let { + it.supportActionBar?.title = null + if (groupSpinnerAdapter != it.binding.contentMain.spinnerPrimary.adapter) + setupSpinners(it) else { - activity?.spinnerPrimary?.visibility = View.VISIBLE + it.binding.contentMain.spinnerPrimary.visibility = View.VISIBLE } } } - private fun setupSpinners() { - if (activity?.spinnerPrimary is AppCompatSpinner) { - activity?.spinnerSecondary?.let { - it.adapter = sourceSpinnerAdapter - it.onItemSelectedListener = sourceSpinnerOnItemSelectedListener - it.visibility = View.VISIBLE - } - activity?.spinnerPrimary?.let { - it.adapter = groupSpinnerAdapter - it.onItemSelectedListener = groupSpinnerOnItemSelectedListener - it.visibility = View.VISIBLE - } + private fun setupSpinners(mainActivity: MainActivity) { + mainActivity.binding.contentMain.spinnerSecondary.apply { + adapter = sourceSpinnerAdapter + onItemSelectedListener = sourceSpinnerOnItemSelectedListener + visibility = View.VISIBLE + } + mainActivity.binding.contentMain.spinnerPrimary.apply { + adapter = groupSpinnerAdapter + onItemSelectedListener = groupSpinnerOnItemSelectedListener + visibility = View.VISIBLE } } @@ -164,14 +155,14 @@ class FavoriteFragment : position: Int, id: Long ) { - swipeRefreshLayout?.isRefreshing = false + binding.swipeRefreshLayout.isRefreshing = false disposeAllLoaderDisposables() observeDataBase( groupType = position, onNextDatabaseChange = onNextDatabaseChange, onDatabaseError = onDatabaseError ) - swipeRefreshLayout?.setOnRefreshListener { + binding.swipeRefreshLayout.setOnRefreshListener { disposeAllLoaderDisposables() observeDataBase( groupType = position, @@ -179,7 +170,7 @@ class FavoriteFragment : onDatabaseError = onDatabaseError ) } - swipeRefreshLayout?.isRefreshing = true + binding.swipeRefreshLayout.isRefreshing = true } } } @@ -211,7 +202,7 @@ class FavoriteFragment : 0 -> null else -> sourceSpinnerAdapter.getItem(position) } - activity?.spinnerPrimary?.let { + (activity as? MainActivity)?.binding?.contentMain?.spinnerPrimary?.let { it.onItemSelectedListener?.onItemSelected( null, null, it.selectedItemPosition, it.selectedItemId ) @@ -230,14 +221,13 @@ class FavoriteFragment : itemThumbnailUri = mangaDb.mangaThumbnailUri } }) - listElementInfos?.adapter?.notifyDataSetChanged() - swipeRefreshLayout?.isRefreshing = false + binding.listElementInfos.adapter?.notifyDataSetChanged() + binding.swipeRefreshLayout.isRefreshing = false } private val onDatabaseError: (Throwable) -> Unit = { error -> context?.toast(R.string.refresh_database_manga_list_error) Timber.e(error) - Crashlytics.logException(error) } override fun onMangaCardClick(mangaBinding: MangaBinding) { diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/list/MangaListFragment.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/list/MangaListFragment.kt index c764faf..dd04a83 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/list/MangaListFragment.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/list/MangaListFragment.kt @@ -8,22 +8,21 @@ import android.os.Bundle import android.view.* import android.widget.AdapterView import android.widget.ArrayAdapter -import androidx.appcompat.widget.AppCompatSpinner import androidx.fragment.app.Fragment import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.bumptech.glide.Glide import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.load.model.GlideUrl -import com.crashlytics.android.Crashlytics import com.github.salomonbrys.kodein.conf.KodeinGlobalAware +import com.github.salomonbrys.kodein.factory import com.github.salomonbrys.kodein.instance import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.typedToJson import com.google.gson.Gson import io.github.innoobwetrust.kintamanga.KINTAMAngaPreferences import io.github.innoobwetrust.kintamanga.R +import io.github.innoobwetrust.kintamanga.databinding.FragmentMangaListBinding import io.github.innoobwetrust.kintamanga.source.SourceManager import io.github.innoobwetrust.kintamanga.source.model.CatalogPage import io.github.innoobwetrust.kintamanga.source.model.CatalogPages @@ -37,9 +36,8 @@ import io.github.innoobwetrust.kintamanga.ui.main.MangaListTypes import io.github.innoobwetrust.kintamanga.ui.manga.MangaInfoActivity import io.github.innoobwetrust.kintamanga.ui.model.MangaBinding import io.github.innoobwetrust.kintamanga.util.extension.toast -import kotlinx.android.synthetic.main.content_main.* -import kotlinx.android.synthetic.main.fragment_manga_list.* -import kotlinx.android.synthetic.main.fragment_manga_list.view.* +import okhttp3.Headers +import okhttp3.Interceptor import okhttp3.OkHttpClient import rx.Single import rx.Subscription @@ -115,7 +113,6 @@ class MangaListFragment : )!! } catch (e: Exception) { Timber.e(e, "source: $mangaSourceName, index: $segmentIndex") - Crashlytics.logException(e) throw e } } @@ -145,12 +142,13 @@ class MangaListFragment : setHasOptionsMenu(true) } + private lateinit var binding: FragmentMangaListBinding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.fragment_manga_list, container, false) - setupMangaListView(view = view) + savedInstanceState: Bundle?): View { + binding = FragmentMangaListBinding.inflate(inflater, container, false) + setupMangaListView() setupToolbar() - return view + return binding.root } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -187,89 +185,85 @@ class MangaListFragment : override fun onResume() { super.onResume() - this.activity?.let { + activity?.let { Glide.get(it.applicationContext).registry.replace( GlideUrl::class.java, InputStream::class.java, - OkHttpUrlLoader.Factory(instance("cover")) + OkHttpUrlLoader.Factory( + instance("cover") + .newBuilder() + .addInterceptor(factory("headers")(mangaInfoProcessor.headers())) + .build() + ) ) } } override fun onPause() { disposeAllLoaderDisposables() - swipeRefreshLayout?.isRefreshing = false + binding.swipeRefreshLayout.isRefreshing = false super.onPause() } override fun onDestroy() { - listElementInfos?.adapter = null + binding.listElementInfos.adapter = null System.gc() super.onDestroy() } - private fun setupMangaListView(view: View?) { - if (view?.swipeRefreshLayout is SwipeRefreshLayout) { - view.swipeRefreshLayout.setOnRefreshListener { - backgroundRefresh( - onRefreshedCatalogPage = onRefreshedCatalogPage, - onRefreshError = onRefreshError - ) - } - when (activity?.resources?.configuration?.orientation) { - Configuration.ORIENTATION_PORTRAIT -> - view.listElementInfos.layoutManager = - GridLayoutManager( - view.listElementInfos.context, - mColumnCount - 2, - RecyclerView.VERTICAL, - false - ) - Configuration.ORIENTATION_LANDSCAPE -> - view.listElementInfos.layoutManager = - GridLayoutManager( - view.listElementInfos.context, - mColumnCount, - RecyclerView.VERTICAL, - false - ) - } - view.listElementInfos.adapter = MangaListAdapter( - catalogPages = catalogPages, - listType = mListType, - elementInfoInteractionListener = this + private fun setupMangaListView() { + binding.swipeRefreshLayout.setOnRefreshListener { + backgroundRefresh( + onRefreshedCatalogPage = onRefreshedCatalogPage, + onRefreshError = onRefreshError ) } + binding.listElementInfos.layoutManager = if (activity?.resources?.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) + GridLayoutManager( + binding.listElementInfos.context, + mColumnCount, + RecyclerView.VERTICAL, + false + ) + else + GridLayoutManager( + binding.listElementInfos.context, + mColumnCount - 2, + RecyclerView.VERTICAL, + false + ) + binding.listElementInfos.adapter = MangaListAdapter( + catalogPages = catalogPages, + listType = mListType, + elementInfoInteractionListener = this + ) } private fun setupToolbar() { - (activity as? MainActivity)?.supportActionBar?.title = null - if (activity?.spinnerPrimary is AppCompatSpinner && - activity?.spinnerSecondary is AppCompatSpinner) { - if (sourceSpinnerAdapter != activity?.spinnerPrimary?.adapter || - segmentSpinnerAdapter != activity?.spinnerSecondary?.adapter) - setupSpinners() + (activity as? MainActivity)?.let { + it.supportActionBar?.title = null + if (sourceSpinnerAdapter != it.binding.contentMain.spinnerPrimary.adapter || + segmentSpinnerAdapter != it.binding.contentMain.spinnerSecondary.adapter) + setupSpinners(it) else { - activity?.spinnerPrimary?.visibility = View.VISIBLE - activity?.spinnerSecondary?.visibility = View.VISIBLE + it.binding.contentMain.spinnerPrimary.visibility = View.VISIBLE + it.binding.contentMain.spinnerSecondary.visibility = View.VISIBLE } } } - private fun setupSpinners() { - if (activity?.spinnerPrimary is AppCompatSpinner) { - val spinnerSource = activity!!.spinnerPrimary - spinnerSource.adapter = sourceSpinnerAdapter - spinnerSource.onItemSelectedListener = sourceSpinnerOnItemSelectedListener - val sourcePosition = SourceManager.sourceNameList.indexOf(mangaSourceName) - if (-1 < sourcePosition) spinnerSource?.setSelection(sourcePosition) - spinnerSource.visibility = View.VISIBLE - } + private fun setupSpinners(mainActivity: MainActivity) { + val spinnerSource = mainActivity.binding.contentMain.spinnerPrimary + spinnerSource.adapter = sourceSpinnerAdapter + spinnerSource.onItemSelectedListener = sourceSpinnerOnItemSelectedListener + val sourcePosition = SourceManager.sourceNameList.indexOf(mangaSourceName) + if (-1 < sourcePosition) spinnerSource.setSelection(sourcePosition) + spinnerSource.visibility = View.VISIBLE } private fun setupSegmentSpinner(resetIndex: Boolean) { - if (activity?.spinnerSecondary is AppCompatSpinner) { - val spinnerSegment = activity!!.spinnerSecondary + (activity as? MainActivity)?.let { + val spinnerSegment = it.binding.contentMain.spinnerSecondary spinnerSegment.adapter = segmentSpinnerAdapter if (resetIndex) segmentIndex = 0 spinnerSegment.onItemSelectedListener = segmentSpinnerOnItemSelectedListener @@ -301,7 +295,7 @@ class MangaListFragment : position: Int, id: Long ) { - swipeRefreshLayout?.isRefreshing = false + binding.swipeRefreshLayout.isRefreshing = false disposeAllLoaderDisposables() val newSourceName = SourceManager.sourceNameList.getOrElse( @@ -363,7 +357,7 @@ class MangaListFragment : mangaSegment.filterByUserInput.isNotEmpty()) { requestFilter() } else { - swipeRefreshLayout?.isRefreshing = true + binding.swipeRefreshLayout.isRefreshing = true backgroundRefresh( onRefreshedCatalogPage = onRefreshedCatalogPage, onRefreshError = onRefreshError @@ -422,10 +416,10 @@ class MangaListFragment : private val onRefreshedCatalogPage: (CatalogPage) -> Unit = { catalogPage -> catalogPages.setup(catalogPage = catalogPage) - listElementInfos?.adapter?.notifyDataSetChanged() - listElementInfos?.scrollToPosition(0) + binding.listElementInfos.adapter?.notifyDataSetChanged() + binding.listElementInfos.scrollToPosition(0) activity?.invalidateOptionsMenu() - swipeRefreshLayout?.isRefreshing = false + binding.swipeRefreshLayout.isRefreshing = false if (catalogPage.elementInfos.isEmpty()) context?.toast(R.string.refresh_manga_list_empty) else @@ -436,7 +430,7 @@ class MangaListFragment : } private val onRefreshError: (Throwable) -> Unit = { error -> - swipeRefreshLayout?.isRefreshing = false + binding.swipeRefreshLayout.isRefreshing = false context?.toast(R.string.refresh_manga_list_error) Timber.e(error) } @@ -444,9 +438,9 @@ class MangaListFragment : private val onNextCatalogPage: (CatalogPage?) -> Unit = { catalogPage -> if (null != catalogPage) { catalogPages.appendNextCatalogPage(catalogPage = catalogPage) - listElementInfos?.adapter?.notifyDataSetChanged() + binding.listElementInfos.adapter?.notifyDataSetChanged() } - swipeRefreshLayout?.isRefreshing = false + binding.swipeRefreshLayout.isRefreshing = false backgroundLoadMissingInfo( onNextMissingMangaInfoLoaded = onNextMissingMangaInfoLoaded, onMissingMangaInfoError = onMissingMangaInfoError @@ -460,7 +454,7 @@ class MangaListFragment : ?.let { it.itemThumbnailUri = info.mangaThumbnailUri it.itemDescription = info.mangaDescription - listElementInfos?.adapter + binding.listElementInfos.adapter ?.notifyItemChanged(catalogPages.elementInfos.indexOf(it)) } } @@ -480,8 +474,8 @@ class MangaListFragment : ) saveFilter() catalogPages.elementInfos.clear() - listElementInfos?.adapter?.notifyDataSetChanged() - swipeRefreshLayout?.isRefreshing = true + binding.listElementInfos.adapter?.notifyDataSetChanged() + binding.swipeRefreshLayout.isRefreshing = true backgroundRefresh( onRefreshedCatalogPage = onRefreshedCatalogPage, onRefreshError = onRefreshError @@ -509,7 +503,7 @@ class MangaListFragment : override fun onRequestMoreElement() { if (loadNextPageDisposable?.isUnsubscribed == false) return - if (swipeRefreshLayout?.isRefreshing == true) return + if (binding.swipeRefreshLayout.isRefreshing) return // Start loading next page when remaining manga is 5 or less val catalogHasNextPage = try { catalogPages.hasNextPage() @@ -517,7 +511,7 @@ class MangaListFragment : false } if (catalogHasNextPage) { - swipeRefreshLayout?.isRefreshing = true + binding.swipeRefreshLayout.isRefreshing = true backgroundLoadNextCatalogPage( onNextCatalogPage = onNextCatalogPage, onNextCatalogPageError = onNextCatalogPageError diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/list/MangaListNetworkLoader.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/list/MangaListNetworkLoader.kt index 98ca6dc..104a716 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/list/MangaListNetworkLoader.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/main/list/MangaListNetworkLoader.kt @@ -91,7 +91,7 @@ interface MangaListNetworkLoader { .parallelMap { elementInfo -> Observable.fromCallable { networkLoadMissingMangaInfo(elementInfo) } .onErrorReturn { MangaBinding() } - .filter { !it.mangaThumbnailUri.isBlank() } + .filter { it.mangaThumbnailUri.isNotBlank() } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -119,4 +119,4 @@ interface MangaListNetworkLoader { stopLoadingNextPage() stopLoadingMissingInfo() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/ChapterListAdapter.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/ChapterListAdapter.kt index b271f0a..2209eb8 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/ChapterListAdapter.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/ChapterListAdapter.kt @@ -3,12 +3,8 @@ package io.github.innoobwetrust.kintamanga.ui.manga import android.graphics.drawable.ColorDrawable import android.view.ContextThemeWrapper import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.widget.PopupMenu -import androidx.appcompat.widget.AppCompatImageButton -import androidx.appcompat.widget.AppCompatImageView -import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.afollestad.dragselectrecyclerview.IDragSelectAdapter @@ -18,7 +14,6 @@ import io.github.innoobwetrust.kintamanga.databinding.HolderChapterBinding import io.github.innoobwetrust.kintamanga.model.DownloadStatus import io.github.innoobwetrust.kintamanga.ui.model.ChapterBinding import io.github.innoobwetrust.kintamanga.ui.model.MangaBinding -import kotlinx.android.synthetic.main.holder_chapter.view.* class ChapterListAdapter( private var mangaInfoActivity: MangaInfoActivity?, @@ -44,7 +39,7 @@ class ChapterListAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) - val binding: HolderChapterBinding = + val binding = HolderChapterBinding.inflate(layoutInflater, parent, false) return ViewHolder(binding = binding) } @@ -53,72 +48,72 @@ class ChapterListAdapter( mangaBinding?.apply { val chapterBinding = chapters[chapters.size - position - 1] holder.bind(chapterBinding) - holder.root.chapterInfoLayout.setOnClickListener { - holder.binding.chapterBinding?.let { - mangaInfoActivity?.onChapterClick(chapterBinding = it) + holder.binding.let { binding -> + binding.chapterInfoLayout.setOnClickListener { + binding.chapterBinding?.let { + mangaInfoActivity?.onChapterClick(chapterBinding = it) + } } - } - holder.root.chapterInfoLayout.setOnLongClickListener { - mangaInfoActivity?.onLongClick(holder.adapterPosition) - true - } - holder.root.let { - it.chapterTitle.isSelected = true - it.chapterDescription.isSelected = true - it.chapterUpdateTime.isSelected = true - } - holder.chapterOfflinePin.setOnClickListener { - toggleSelected(holder.adapterPosition) - } - holder.chapterOfflinePin.setOnLongClickListener { - mangaInfoActivity?.onLongClick(holder.adapterPosition) - true - } - holder.chapterOption.setOnClickListener { - val chapterItemOption = - PopupMenu(contextWrapper!!, holder.chapterOption) - chapterItemOption.inflate(R.menu.menu_chapter_item) - chapterItemOption.menu.apply { - findItem(R.id.chapter_item_option_download)?.isVisible = - chapterBinding.chapterDownloadStatus == DownloadStatus.NOT_DOWNLOADED - findItem(R.id.chapter_item_option_delete)?.isVisible = - chapterBinding.chapterDownloadStatus == DownloadStatus.DOWNLOADED + binding.chapterInfoLayout.setOnLongClickListener { + mangaInfoActivity?.onLongClick(holder.adapterPosition) + true } - chapterItemOption.setOnMenuItemClickListener { item -> - when (item.itemId) { - R.id.chapter_item_option_download -> { - if (DownloadStatus.NOT_DOWNLOADED == chapterBinding.chapterDownloadStatus) { - mangaInfoActivity?.onDownloadRequest(listOf(chapterBinding), false) - return@setOnMenuItemClickListener true + binding.chapterTitle.isSelected = true + binding.chapterDescription.isSelected = true + binding.chapterUpdateTime.isSelected = true + binding.chapterOfflinePin.setOnClickListener { + toggleSelected(holder.adapterPosition) + } + binding.chapterOfflinePin.setOnLongClickListener { + mangaInfoActivity?.onLongClick(holder.adapterPosition) + true + } + binding.chapterOption.setOnClickListener { + val chapterItemOption = + PopupMenu(contextWrapper!!, binding.chapterOption) + chapterItemOption.inflate(R.menu.menu_chapter_item) + chapterItemOption.menu.apply { + findItem(R.id.chapter_item_option_download)?.isVisible = + chapterBinding.chapterDownloadStatus == DownloadStatus.NOT_DOWNLOADED + findItem(R.id.chapter_item_option_delete)?.isVisible = + chapterBinding.chapterDownloadStatus == DownloadStatus.DOWNLOADED + } + chapterItemOption.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.chapter_item_option_download -> { + if (DownloadStatus.NOT_DOWNLOADED == chapterBinding.chapterDownloadStatus) { + mangaInfoActivity?.onDownloadRequest(listOf(chapterBinding), false) + return@setOnMenuItemClickListener true + } } - } - R.id.chapter_item_option_delete -> { - if (DownloadStatus.DOWNLOADED == chapterBinding.chapterDownloadStatus) { - mangaInfoActivity?.onDeleteRequest(listOf(chapterBinding), false) + R.id.chapter_item_option_delete -> { + if (DownloadStatus.DOWNLOADED == chapterBinding.chapterDownloadStatus) { + mangaInfoActivity?.onDeleteRequest(listOf(chapterBinding), false) + return@setOnMenuItemClickListener true + } + } + R.id.chapter_item_option_toggle_read_status -> { + chapterBinding.chapterViewed = !chapterBinding.chapterViewed + notifyItemChanged(position) + mangaInfoActivity?.onReadStatusToggled(listOf(chapterBinding)) return@setOnMenuItemClickListener true } } - R.id.chapter_item_option_toggle_read_status -> { - chapterBinding.chapterViewed = !chapterBinding.chapterViewed - notifyItemChanged(position) - mangaInfoActivity?.onReadStatusToggled(listOf(chapterBinding)) - return@setOnMenuItemClickListener true - } + return@setOnMenuItemClickListener false } - return@setOnMenuItemClickListener false + chapterItemOption.show() } - chapterItemOption.show() + binding.chapterCard.foreground = + if (position in selectedIndices) + ColorDrawable( + ContextCompat.getColor( + binding.chapterCard.context, + R.color.color_overlay + ) + ) + else + null } - holder.chapterCard.foreground = - if (position in selectedIndices) - ColorDrawable( - ContextCompat.getColor( - holder.chapterCard.context, - R.color.color_overlay - ) - ) - else - null } } @@ -135,7 +130,7 @@ class ChapterListAdapter( } override fun onViewRecycled(holder: ViewHolder) { - holder.root.setOnClickListener(null) + holder.binding.root.setOnClickListener(null) holder.binding.chapterOfflinePin.setOnClickListener(null) holder.bind(null) System.gc() @@ -189,15 +184,6 @@ class ChapterListAdapter( inner class ViewHolder( val binding: HolderChapterBinding ) : RecyclerView.ViewHolder(binding.root) { - val root: View - get() = binding.root - val chapterCard: CardView - get() = binding.chapterCard - val chapterOfflinePin: AppCompatImageView - get() = binding.chapterOfflinePin - val chapterOption: AppCompatImageButton - get() = binding.chapterOption - fun bind(chapterBinding: ChapterBinding?) { binding.chapterBinding = chapterBinding binding.executePendingBindings() diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/MangaInfoActivity.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/MangaInfoActivity.kt index 013a42c..a5fb6c9 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/MangaInfoActivity.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/MangaInfoActivity.kt @@ -17,8 +17,8 @@ import com.afollestad.materialcab.MaterialCab import com.bumptech.glide.Glide import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.load.model.GlideUrl -import com.crashlytics.android.Crashlytics import com.github.salomonbrys.kodein.conf.KodeinGlobalAware +import com.github.salomonbrys.kodein.factory import com.github.salomonbrys.kodein.instance import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.typedToJson @@ -36,7 +36,8 @@ import io.github.innoobwetrust.kintamanga.ui.model.MangaBinding import io.github.innoobwetrust.kintamanga.ui.reader.ReaderActivity import io.github.innoobwetrust.kintamanga.util.extension.toast import io.github.innoobwetrust.kintamanga.util.extension.uriString -import kotlinx.android.synthetic.main.activity_manga_info.* +import okhttp3.Headers +import okhttp3.Interceptor import okhttp3.OkHttpClient import rx.Subscription import timber.log.Timber @@ -95,23 +96,23 @@ class MangaInfoActivity : } binding = DataBindingUtil.setContentView(this, R.layout.activity_manga_info) bind() - setSupportActionBar(toolbar) + setSupportActionBar(binding.toolbar) supportActionBar?.apply { setDisplayHomeAsUpEnabled(true) setDisplayShowHomeEnabled(true) } - listChapters.let { + binding.listChapters.let { it.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) it.adapter = ChapterListAdapter( mangaInfoActivity = this, mangaBinding = mangaBinding, selectedIndices = savedInstanceState ?.getString(SavedInstanceStates.SELECTED_INDICES.key) - ?.run { instance().fromJson>(this) } + ?.run { instance().fromJson(this) } ?: mutableSetOf() ) } - mangaMenuFab.setOnClickListener(menuFabOnClickListener) + binding.mangaMenuFab.setOnClickListener(menuFabOnClickListener) cab = MaterialCab.restoreState(savedInstanceState, this, this) } @@ -120,7 +121,12 @@ class MangaInfoActivity : Glide.get(applicationContext).registry.replace( GlideUrl::class.java, InputStream::class.java, - OkHttpUrlLoader.Factory(instance("cover")) + OkHttpUrlLoader.Factory( + instance("cover") + .newBuilder() + .addInterceptor(factory("headers")(mangaInfoProcessor.headers())) + .build() + ) ) if (mangaBinding.chapters.isEmpty()) { backgroundRefresh( @@ -140,7 +146,7 @@ class MangaInfoActivity : super.onSaveInstanceState(outState) outState.let { it.putSerializable(SavedInstanceStates.MANGA.key, mangaBinding) - (listChapters?.adapter as? ChapterListAdapter)?.selectedIndices?.run { + (binding.listChapters.adapter as? ChapterListAdapter)?.selectedIndices?.run { it.putString( SavedInstanceStates.SELECTED_INDICES.key, instance().typedToJson(this) @@ -168,7 +174,7 @@ class MangaInfoActivity : } override fun onBackPressed() { - (listChapters?.adapter as? ChapterListAdapter)?.let { + (binding.listChapters.adapter as? ChapterListAdapter)?.let { if (it.selectedIndices.isNotEmpty()) { it.clearSelected() return @@ -182,7 +188,7 @@ class MangaInfoActivity : } override fun onCabItemClicked(item: MenuItem?): Boolean { - (listChapters?.adapter as? ChapterListAdapter)?.let { + (binding.listChapters.adapter as? ChapterListAdapter)?.let { // Because chapter index is ordered in revert, we need to fix the list of indices val size = mangaBinding.chapters.size val chapterSelectedIndices = it.selectedIndices.map { index -> size - index - 1 } @@ -213,7 +219,7 @@ class MangaInfoActivity : } override fun onCabFinished(cab: MaterialCab?): Boolean { - (listChapters?.adapter as? ChapterListAdapter)?.clearSelected() + (binding.listChapters.adapter as? ChapterListAdapter)?.clearSelected() return true } @@ -225,18 +231,18 @@ class MangaInfoActivity : private fun showFABMenu() { isFABOpen = true - mangaClearFab?.animate()?.translationX(-resources.getDimension(R.dimen.standard_305)) - mangaShareFab?.animate()?.translationX(-resources.getDimension(R.dimen.standard_205)) - mangaFavoriteFab?.animate()?.translationX(-resources.getDimension(R.dimen.standard_105)) - mangaMenuFab?.setImageResource(R.drawable.ic_cancel_white_24dp) + binding.mangaClearFab.animate()?.translationX(-resources.getDimension(R.dimen.standard_305)) + binding.mangaShareFab.animate()?.translationX(-resources.getDimension(R.dimen.standard_205)) + binding.mangaFavoriteFab.animate()?.translationX(-resources.getDimension(R.dimen.standard_105)) + binding.mangaMenuFab.setImageResource(R.drawable.ic_cancel_white_24dp) } private fun closeFABMenu() { isFABOpen = false - mangaClearFab?.animate()?.translationX(0f) - mangaShareFab?.animate()?.translationX(0f) - mangaFavoriteFab?.animate()?.translationX(0f) - mangaMenuFab?.setImageResource(R.drawable.ic_menu_white_24dp) + binding.mangaClearFab.animate()?.translationX(0f) + binding.mangaShareFab.animate()?.translationX(0f) + binding.mangaFavoriteFab.animate()?.translationX(0f) + binding.mangaMenuFab.setImageResource(R.drawable.ic_menu_white_24dp) } private val menuFabOnClickListener: (View) -> Unit = { _ -> @@ -300,12 +306,12 @@ class MangaInfoActivity : } private val onRefreshSuccess: (Boolean) -> Unit = { success -> - progress?.visibility = View.GONE - mangaClearFab?.setOnClickListener(mangaClearFabOnClickListener) - mangaShareFab?.setOnClickListener(shareFabOnClickListener) - mangaFavoriteFab?.setOnClickListener(favoriteFabOnClickListener) + binding.progress.visibility = View.GONE + binding.mangaClearFab.setOnClickListener(mangaClearFabOnClickListener) + binding.mangaShareFab.setOnClickListener(shareFabOnClickListener) + binding.mangaFavoriteFab.setOnClickListener(favoriteFabOnClickListener) if (success) { - listChapters?.adapter?.notifyDataSetChanged() + binding.listChapters.adapter?.notifyDataSetChanged() } else { toast(R.string.manga_info_refresh_failed_text) } @@ -314,7 +320,6 @@ class MangaInfoActivity : private val onRefreshError: (Throwable) -> Unit = { error -> toast(R.string.manga_info_refresh_error_text) Timber.e(error) - Crashlytics.logException(error) } private val onDownloadStatusChange: (Download) -> Unit = { download -> @@ -329,14 +334,12 @@ class MangaInfoActivity : } ?: Exception("Can not find chapter: ${download.chapter.chapterUri}").let { Timber.e(it) - Crashlytics.logException(it) } } private val onObservableDownloadError: (Throwable) -> Unit = { error -> toast(R.string.observe_download_failed_text) Timber.e(error) - Crashlytics.logException(error) } @Throws(Exception::class) @@ -345,7 +348,12 @@ class MangaInfoActivity : Glide.get(applicationContext).registry.replace( GlideUrl::class.java, InputStream::class.java, - OkHttpUrlLoader.Factory(instance("cover")) + OkHttpUrlLoader.Factory( + instance("cover") + .newBuilder() + .addInterceptor(factory("headers")(mangaInfoProcessor.headers())) + .build() + ) ) if (0 == requestCode && Activity.RESULT_OK == resultCode && null != data) { val updatedMangaBinding = @@ -362,7 +370,7 @@ class MangaInfoActivity : } override fun onLongClick(index: Int) { - listChapters?.setDragSelectActive(true, index) + binding.listChapters.setDragSelectActive(true, index) } override fun onSelectionChanged(count: Int) { @@ -388,7 +396,7 @@ class MangaInfoActivity : } override fun onChapterClick(chapterBinding: ChapterBinding) { - if (View.VISIBLE == progress?.visibility) return + if (View.VISIBLE == binding.progress.visibility) return val readerIntent: Intent = Intent(this, ReaderActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) @@ -409,7 +417,6 @@ class MangaInfoActivity : onError = { error -> toast(R.string.download_request_prepare_failed_text) Timber.e(error) - Crashlytics.logException(error) } ) chapterBindings.forEach { it.chapterDownloadStatus = DownloadStatus.QUEUE } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/MangaInfoLoader.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/MangaInfoLoader.kt index 9867af9..8ff54ce 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/MangaInfoLoader.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/manga/MangaInfoLoader.kt @@ -1,6 +1,5 @@ package io.github.innoobwetrust.kintamanga.ui.manga -import com.crashlytics.android.Crashlytics import com.github.salomonbrys.kodein.conf.KodeinGlobalAware import com.github.salomonbrys.kodein.instance import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects @@ -62,7 +61,6 @@ interface MangaInfoLoader : KodeinGlobalAware { .fromCallable { putMangaDb() } .doOnError { Timber.e(it) - Crashlytics.logException(it) } .subscribeOn(Schedulers.io()) @@ -76,7 +74,6 @@ interface MangaInfoLoader : KodeinGlobalAware { } .doOnError { Timber.e(it) - Crashlytics.logException(it) } .subscribeOn(Schedulers.io()) @@ -140,7 +137,6 @@ interface MangaInfoLoader : KodeinGlobalAware { } .doOnError { Timber.e(it) - Crashlytics.logException(it) } .subscribeOn(Schedulers.io()) @@ -149,7 +145,6 @@ interface MangaInfoLoader : KodeinGlobalAware { .flatMap { saveChapters() } .doOnError { Timber.e(it) - Crashlytics.logException(it) } .subscribeOn(Schedulers.io()) @@ -187,7 +182,6 @@ interface MangaInfoLoader : KodeinGlobalAware { } .doOnError { Timber.e(it) - Crashlytics.logException(it) } .subscribeOn(Schedulers.io()) @@ -223,7 +217,6 @@ interface MangaInfoLoader : KodeinGlobalAware { .observeOn(AndroidSchedulers.mainThread()) .doOnError { Timber.e(it) - Crashlytics.logException(it) } .subscribe( { download -> onDownloadStatusChange(download) }, @@ -244,7 +237,6 @@ interface MangaInfoLoader : KodeinGlobalAware { saveMangaObservable .doOnError { Timber.e(it) - Crashlytics.logException(it) } .observeOn(AndroidSchedulers.mainThread()) .subscribe( @@ -281,7 +273,6 @@ interface MangaInfoLoader : KodeinGlobalAware { } .doOnError { Timber.e(it) - Crashlytics.logException(it) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -310,7 +301,6 @@ interface MangaInfoLoader : KodeinGlobalAware { } .doOnError { Timber.e(it) - Crashlytics.logException(it) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ChapterInfoLoader.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ChapterInfoLoader.kt index 9371993..361b046 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ChapterInfoLoader.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ChapterInfoLoader.kt @@ -1,14 +1,11 @@ package io.github.innoobwetrust.kintamanga.ui.reader import android.content.Context -import com.crashlytics.android.Crashlytics -import com.github.piasy.biv.BigImageViewer import io.github.innoobwetrust.kintamanga.download.DownloadProvider import io.github.innoobwetrust.kintamanga.model.DownloadStatus import io.github.innoobwetrust.kintamanga.source.processor.ChapterInfoProcessor import io.github.innoobwetrust.kintamanga.ui.model.ChapterBinding import io.github.innoobwetrust.kintamanga.ui.model.MangaBinding -import io.github.innoobwetrust.kintamanga.util.GlideBitmapImageLoader import rx.Single import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -31,7 +28,6 @@ interface ChapterInfoLoader { chapterInfoProcessor.fetchPageList(chapterBinding) } catch (e: Exception) { Timber.e(e) - Crashlytics.logException(e) return false } chapterBinding.chapterPages = pages.onEach { it.chapterIndex = chapterBinding.chapterIndex } @@ -57,7 +53,6 @@ interface ChapterInfoLoader { onChapterLoadError: (Throwable) -> Unit ) { // Prevent conflict - (BigImageViewer.imageLoader() as? GlideBitmapImageLoader)?.cancelPrefetch() disposeAllLoaderDisposables() // Real job loadChapterDisposable = Single.fromCallable { @@ -73,4 +68,4 @@ interface ChapterInfoLoader { fun disposeAllLoaderDisposables() { loadChapterDisposable?.let { if (!it.isUnsubscribed) it.unsubscribe() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ImageViewerAdapter.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ImageViewerAdapter.kt index d74c1d4..07e6b0e 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ImageViewerAdapter.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ImageViewerAdapter.kt @@ -1,27 +1,28 @@ package io.github.innoobwetrust.kintamanga.ui.reader +import android.graphics.drawable.Drawable import android.net.Uri import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.AppCompatButton -import androidx.appcompat.widget.AppCompatImageView -import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.bumptech.glide.Glide +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.load.model.Headers +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView -import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator -import com.github.piasy.biv.loader.ImageLoader -import com.github.piasy.biv.view.BigImageView import io.github.innoobwetrust.kintamanga.R +import io.github.innoobwetrust.kintamanga.databinding.HolderImageViewerBinding import io.github.innoobwetrust.kintamanga.model.Page import io.github.innoobwetrust.kintamanga.ui.model.ChapterBinding -import kotlinx.android.synthetic.main.holder_image_viewer.view.* import java.io.File class ImageViewerAdapter( private var viewer: ViewerFragment?, private var chapterBinding: ChapterBinding?, + private val glideHeaders: Headers?, private val viewerType: ViewerTypes ) : RecyclerView.Adapter() { @@ -38,33 +39,24 @@ class ImageViewerAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val inflatedView = LayoutInflater.from(parent.context) - .inflate(R.layout.holder_image_viewer, parent, false) - if (viewerType == ViewerTypes.WEBTOON) { - inflatedView.imageViewerLayout?.layoutParams?.height = - ViewGroup.LayoutParams.WRAP_CONTENT - } - return ViewHolder(inflatedView = inflatedView) + val layoutInflater = LayoutInflater.from(parent.context) + val binding = HolderImageViewerBinding.inflate(layoutInflater, parent, false) + return ViewHolder(binding = binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { chapterBinding?.let { binding -> - holder.setImage(binding.chapterPages[position]) - holder.touchOverlay.setOnClickListener { - (viewer?.activity as? ViewerFragmentListener) - ?.onViewerToggleControl() - } - holder.failureImage.setOnClickListener { - (viewer?.activity as? ViewerFragmentListener) - ?.onViewerToggleControl() + if (viewerType == ViewerTypes.WEBTOON) { + holder.binding.imageViewerLayout.layoutParams.height = + ViewGroup.LayoutParams.WRAP_CONTENT } - holder.reloadImageButton.setOnClickListener { - holder.touchOverlay.visibility = View.VISIBLE - holder.failureView.visibility = View.GONE - holder.setImage(binding.chapterPages[position]) - } - holder.chapterImageView.ssiv.setOnTouchListener { _, motionEvent -> - viewer?.gestureDetector?.onTouchEvent(motionEvent) ?: true + holder.bind(binding.chapterPages[position], viewer?.serverIndex + ?: 0) + holder.binding.chapterImage.apply { + setOnTouchListener { _, motionEvent -> + viewer?.view?.performClick() + viewer?.gestureDetector?.onTouchEvent(motionEvent) ?: true + } } } } @@ -76,81 +68,64 @@ class ImageViewerAdapter( override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { viewer = null chapterBinding = null - System.gc() +// System.gc() super.onDetachedFromRecyclerView(recyclerView) } override fun onViewRecycled(holder: ViewHolder) { - holder.let { - it.touchOverlay.setOnClickListener(null) - it.failureImage.setOnClickListener(null) - it.reloadImageButton.setOnClickListener(null) - it.chapterImageView.setOnClickListener(null) - it.chapterImageView.ssiv.setOnTouchListener(null) - it.chapterImageView.ssiv.recycle() + holder.binding.apply { + chapterImage.setOnClickListener(null) + chapterImage.recycle() } - System.gc() +// System.gc() super.onViewRecycled(holder) } inner class ViewHolder( - inflatedView: View - ) : RecyclerView.ViewHolder(inflatedView) { - val chapterImageView: BigImageView = inflatedView.chapterImage - val touchOverlay: View = inflatedView.touchOverlay - val failureView: ConstraintLayout = inflatedView.failureView - val failureImage: AppCompatImageView = inflatedView.failureImage - val reloadImageButton: AppCompatButton = inflatedView.reloadImageButton + val binding: HolderImageViewerBinding + ) : RecyclerView.ViewHolder(binding.root) { + private lateinit var page: Page - init { - chapterImageView.apply { - ssiv.setMinimumDpi(90) - ssiv.setMinimumTileDpi(180) - ssiv.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) - ssiv.setOnImageEventListener( - object : SubsamplingScaleImageView.OnImageEventListener { - override fun onReady() {} - override fun onTileLoadError(p0: Exception?) {} - override fun onPreviewReleased() {} - override fun onPreviewLoadError(p0: Exception?) {} - override fun onImageLoaded() { - this@ViewHolder.touchOverlay.visibility = View.GONE - this@ViewHolder.failureView.visibility = View.GONE - } + fun bind(page: Page, serverIndex: Int) { + this.page = page + binding.chapterImage.apply { + recycle() + setMinimumDpi(90) + setMinimumTileDpi(180) + setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE) + setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) + } + if (page.imageFileUri.isBlank() && page.imageUrls[serverIndex].isBlank()) { + binding.chapterImage.setImage(ImageSource.resource(R.drawable.broken_image_white_192x192)) + return + } + var loader = Glide.with(binding.chapterImage).downloadOnly() + when { + page.imageFileUri.isNotBlank() -> loader = loader.load(page.imageFileUri) + page.imageUrls[serverIndex].isNotBlank() -> loader = loader.load(GlideUrl(page.imageUrls[serverIndex], glideHeaders)) + } + val circularProgressDrawable = CircularProgressDrawable(binding.root.context).apply { + strokeWidth = 5f + centerRadius = 30f + start() + } + loader + .placeholder(circularProgressDrawable) + .into(object : CustomTarget() { + override fun onLoadCleared(placeholder: Drawable?) { + circularProgressDrawable.start() + } - override fun onImageLoadError(p0: Exception?) { - this@ViewHolder.touchOverlay.visibility = View.VISIBLE - this@ViewHolder.failureView.visibility = View.VISIBLE - onFail(Exception("Failed to load image from file")) - } + override fun onLoadFailed(errorDrawable: Drawable?) { + circularProgressDrawable.stop() + binding.chapterImage.setImage(ImageSource.resource(R.drawable.broken_image_white_192x192)) } - ) - setImageLoaderCallback(object : ImageLoader.Callback { - override fun onFinish() {} - override fun onCacheHit(image: File?) {} - override fun onCacheMiss(image: File?) {} - override fun onProgress(progress: Int) {} - override fun onStart() {} - override fun onSuccess(image: File?) {} - override fun onFail(error: java.lang.Exception?) { - this@ViewHolder.touchOverlay.visibility = View.VISIBLE - this@ViewHolder.failureView.visibility = View.VISIBLE - } - }) - setProgressIndicator(ProgressPieIndicator()) - } - } - fun setImage(page: Page) { - chapterImageView.apply { - Glide.with(this).clear(this) - ssiv.recycle() - if (!page.imageFileUri.isBlank()) { - showImage(Uri.parse(page.imageFileUri)) - } else if (!page.imageUrls[viewer!!.serverIndex!!].isBlank()) { - showImage(Uri.parse(page.imageUrls[viewer!!.serverIndex!!])) - } - } + override fun onResourceReady(resource: File, transition: Transition?) { + circularProgressDrawable.stop() + binding.chapterImage.setImage(ImageSource.uri(Uri.fromFile(resource))) + } + }) } } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ReaderActivity.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ReaderActivity.kt index 9867140..2b58122 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ReaderActivity.kt @@ -17,20 +17,14 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment -import com.crashlytics.android.Crashlytics -import com.github.piasy.biv.BigImageViewer import com.github.salomonbrys.kodein.conf.KodeinGlobalAware -import com.github.salomonbrys.kodein.instance import io.github.innoobwetrust.kintamanga.R import io.github.innoobwetrust.kintamanga.databinding.ActivityReaderBinding -import io.github.innoobwetrust.kintamanga.model.DownloadStatus import io.github.innoobwetrust.kintamanga.source.SourceManager import io.github.innoobwetrust.kintamanga.source.processor.ChapterInfoProcessor import io.github.innoobwetrust.kintamanga.ui.manga.MangaInfoActivity import io.github.innoobwetrust.kintamanga.ui.model.MangaBinding -import io.github.innoobwetrust.kintamanga.util.GlideBitmapImageLoader import io.github.innoobwetrust.kintamanga.util.extension.toast -import kotlinx.android.synthetic.main.activity_reader.* import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -49,13 +43,13 @@ class ReaderActivity : * If [.AUTO_HIDE] is set, the number of milliseconds to wait after * user interaction before hiding the system UI. */ - private const val AUTO_HIDE_DELAY_MILLIS = 3000 + private const val AUTO_HIDE_DELAY_MILLIS = 3000L /** * Some older devices needs a small delay between UI widget updates * and a change of the status and navigation bar. */ - private const val UI_ANIMATION_DELAY = 300 + private const val UI_ANIMATION_DELAY = 150L // Fragment tag private const val CONTENT_VIEW_FRAGMENT_TAG = "CONTENT_VIEW_FRAGMENT_TAG" @@ -92,6 +86,8 @@ class ReaderActivity : private var startPage: Int? = null private var timerDisposable: Subscription? = null + private lateinit var binding: ActivityReaderBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (null == savedInstanceState) { @@ -109,31 +105,31 @@ class ReaderActivity : ) as? MangaBinding ?: throw Exception("Error! mangaBinding not saved properly") chapterIndex = savedInstanceState.getInt(SavedInstanceStates.CHAPTER_INDEX.key) } - val binding: ActivityReaderBinding = + binding = DataBindingUtil.setContentView(this, R.layout.activity_reader) binding.mangaBinding = mangaBinding binding.executePendingBindings() - setSupportActionBar(toolbar) + setSupportActionBar(binding.toolbar) supportActionBar?.apply { setDisplayHomeAsUpEnabled(true) setDisplayShowHomeEnabled(true) } - fullscreen_content.setOnClickListener { toggle() } + binding.fullscreenContent.setOnClickListener { toggle() } mVisible = true setupChapterNavigationButtons() - previousChapterButton.setOnClickListener { + binding.previousChapterButton.setOnClickListener { --chapterIndex reloadViewer() startPage = -1 - delayHide() + hide(AUTO_HIDE_DELAY_MILLIS) } - nextChapterButton.setOnClickListener { + binding.nextChapterButton.setOnClickListener { ++chapterIndex reloadViewer() startPage = 0 - delayHide() + hide(AUTO_HIDE_DELAY_MILLIS) } - chapterSelectButton.setOnClickListener { + binding.chapterSelectButton.setOnClickListener { AlertDialog.Builder(this, R.style.alertDialogStyle) .setItems( mangaBinding.chapters.map { it.chapterTitle }.toTypedArray() @@ -141,12 +137,12 @@ class ReaderActivity : chapterIndex = i reloadViewer() startPage = 0 - delayHide() + hide(AUTO_HIDE_DELAY_MILLIS) } .create() .show() } - changeViewerTypeButton.setOnClickListener { + binding.changeViewerTypeButton.setOnClickListener { if ((0..(ViewerTypes.values().size - 2)).contains(mangaBinding.mangaViewer)) { ++mangaBinding.mangaViewer } else { @@ -154,9 +150,9 @@ class ReaderActivity : } doFragmentTransaction() onViewerTypeChanged() - delayHide() + hide(AUTO_HIDE_DELAY_MILLIS) } - pageSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + binding.pageSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged( seekBar: SeekBar?, progress: Int, @@ -168,38 +164,10 @@ class ReaderActivity : newPosition = progress, progressFeedback = false ) - delayHide() + hide(AUTO_HIDE_DELAY_MILLIS) } - pageIndicator.text = + binding.pageIndicator.text = getString(R.string.reader_page_indicator_text, progress + 1, max + 1) - if (chapterBinding.chapterPages.isNotEmpty() && - DownloadStatus.DOWNLOADED != chapterBinding.chapterDownloadStatus) { - try { - BigImageViewer.prefetch( - *chapterBinding.chapterPages - .map { Uri.parse(it.imageUrls[serverIndex]) } - .filterIndexed { index, _ -> - index in ((progress - 2).coerceAtLeast(0)).. - ((progress + 2).coerceAtMost(chapterBinding.chapterPages.size - 1)) - } - .foldIndexed( - Array(5) { Uri.EMPTY }, - { i: Int, acc: Array, uri: Uri -> - acc[when (i) { - 2 -> 0 - 3 -> 1 - 4 -> 2 - 1 -> 3 - 0 -> 4 - else -> 0 - }] = uri - acc - } - ) - ) - } catch (e: Exception) { - } - } } } @@ -211,9 +179,6 @@ class ReaderActivity : override fun onResume() { super.onResume() - BigImageViewer.initialize( - GlideBitmapImageLoader.with(applicationContext, instance("chapter")) - ) doFragmentTransaction() if (chapterBinding.chapterPages.isEmpty()) { backgroundRefresh( @@ -257,7 +222,6 @@ class ReaderActivity : override fun onPause() { disposeAllLoaderDisposables() timerDisposable?.run { if (!isUnsubscribed) unsubscribe() } - (BigImageViewer.imageLoader() as? GlideBitmapImageLoader)?.cancelPrefetch() mContentFragment?.let { supportFragmentManager.beginTransaction() .remove(it) @@ -269,23 +233,24 @@ class ReaderActivity : } private fun doFragmentTransaction() { - mContentFragment = ViewerFragment.newInstance() + val viewerFragment = ViewerFragment.newInstance() + mContentFragment = viewerFragment supportFragmentManager .beginTransaction() - .replace(R.id.fullscreen_content, mContentFragment as ViewerFragment, CONTENT_VIEW_FRAGMENT_TAG) + .replace(binding.fullscreenContent.id, viewerFragment, CONTENT_VIEW_FRAGMENT_TAG) .commitNowAllowingStateLoss() } private fun setupChapterNavigationButtons() { if (chapterIndex > 0) { - previousChapterButton?.visibility = View.VISIBLE + binding.previousChapterButton.visibility = View.VISIBLE } else { - previousChapterButton?.visibility = View.INVISIBLE + binding.previousChapterButton.visibility = View.INVISIBLE } if (chapterIndex < mangaBinding.chapters.size - 1) { - nextChapterButton?.visibility = View.VISIBLE + binding.nextChapterButton.visibility = View.VISIBLE } else { - nextChapterButton?.visibility = View.INVISIBLE + binding.nextChapterButton.visibility = View.INVISIBLE } } @@ -300,8 +265,8 @@ class ReaderActivity : .toList() ) chapterServerChooserAdapter.setDropDownViewResource(R.layout.themed_spinner_dropdown_item) - chapterServerChooser?.adapter = chapterServerChooserAdapter - chapterServerChooser?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + binding.chapterServerChooser.adapter = chapterServerChooserAdapter + binding.chapterServerChooser.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onNothingSelected(parent: AdapterView<*>?) {} override fun onItemSelected( parent: AdapterView<*>?, @@ -315,7 +280,7 @@ class ReaderActivity : } private fun onViewerTypeChanged() { - changeViewerTypeButton?.setImageResource( + binding.changeViewerTypeButton.setImageResource( when (mangaBinding.mangaViewer) { ViewerTypes.PAGER_HORIZONTAL_LEFT_TO_RIGHT.ordinal, ViewerTypes.PAGER_HORIZONTAL_RIGHT_TO_LEFT.ordinal -> @@ -325,18 +290,18 @@ class ReaderActivity : else -> R.drawable.ic_view_carousel_white_24dp } ) - viewerTypeIndicator?.setImageResource( + binding.viewerTypeIndicator.setImageResource( when (mangaBinding.mangaViewer) { ViewerTypes.PAGER_HORIZONTAL_LEFT_TO_RIGHT.ordinal -> if (ViewCompat.LAYOUT_DIRECTION_RTL - == ViewCompat.getLayoutDirection(viewerTypeIndicator)) { + == ViewCompat.getLayoutDirection(binding.viewerTypeIndicator)) { R.drawable.ic_arrow_back_white_24dp } else { R.drawable.ic_arrow_forward_white_24dp } ViewerTypes.PAGER_HORIZONTAL_RIGHT_TO_LEFT.ordinal -> if (ViewCompat.LAYOUT_DIRECTION_RTL - == ViewCompat.getLayoutDirection(viewerTypeIndicator)) { + == ViewCompat.getLayoutDirection(binding.viewerTypeIndicator)) { R.drawable.ic_arrow_forward_white_24dp } else { R.drawable.ic_arrow_back_white_24dp @@ -346,17 +311,17 @@ class ReaderActivity : else -> R.drawable.ic_arrow_forward_white_24dp } ) - viewerTypeIndicatorCard?.visibility = View.VISIBLE + binding.viewerTypeIndicatorCard.visibility = View.VISIBLE timerDisposable?.run { if (!isUnsubscribed) unsubscribe() } timerDisposable = Observable.timer(1, TimeUnit.SECONDS) .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { viewerTypeIndicatorCard?.visibility = View.GONE } + .subscribe { binding.viewerTypeIndicatorCard.visibility = View.GONE } } private fun reloadViewer() { setupChapterNavigationButtons() - chapterStatusAndTitle?.text = getText(R.string.chapter_loading_text) + binding.chapterStatusAndTitle.text = getText(R.string.chapter_loading_text) backgroundRefresh( context = this, onChapterLoaded = onChapterLoaded, @@ -367,16 +332,16 @@ class ReaderActivity : private val onChapterLoaded: (Boolean) -> Unit = { success -> if (success) { if (chapterBinding.chapterPages.isEmpty()) { - chapterStatusAndTitle?.text = getText(R.string.chapter_load_empty_text) + binding.chapterStatusAndTitle.text = getText(R.string.chapter_load_empty_text) toast(R.string.chapter_load_empty_text) } else { onViewerTypeChanged() - chapterStatusAndTitle?.text = chapterBinding.chapterTitle - chapterCommentFab?.setOnClickListener { + binding.chapterStatusAndTitle.text = chapterBinding.chapterTitle + binding.chapterCommentFab.setOnClickListener { val commentIntent = Intent(Intent.ACTION_VIEW, Uri.parse(chapterBinding.chapterUri)) startActivity(commentIntent) } - pageSeekBar?.max = chapterBinding.chapterPages.size - 1 + binding.pageSeekBar.max = chapterBinding.chapterPages.size - 1 when (startPage) { -1 -> chapterBinding.chapterLastPageRead = chapterBinding.chapterPages.size - 1 0 -> chapterBinding.chapterLastPageRead = 0 @@ -389,22 +354,21 @@ class ReaderActivity : ?.size ?: 1 Timber.v("number of servers: $numServer") - delayedHide(1000) + hide(1000) } } else { - chapterStatusAndTitle?.text = getText(R.string.chapter_load_failed_text) + binding.chapterStatusAndTitle.text = getText(R.string.chapter_load_failed_text) toast(R.string.chapter_load_failed_text) } } private val onChapterLoadError: (Throwable) -> Unit = { error -> - chapterStatusAndTitle?.text = getText(R.string.chapter_load_error_text) + binding.chapterStatusAndTitle.text = getText(R.string.chapter_load_error_text) toast(R.string.chapter_load_error_text) Timber.e(error) - Crashlytics.logException(error) } override fun onViewerPageChanged(newPagePosition: Int) { - pageSeekBar?.progress = newPagePosition + binding.pageSeekBar.progress = newPagePosition } override fun onTapPreviousPage() { @@ -423,75 +387,54 @@ class ReaderActivity : private val mHideHandler = Handler() private val mHidePart2Runnable = Runnable { - // Delayed removal of status and navigation bar - // Note that some of these constants are new as of API 16 (Jelly Bean) // and API 19 (KitKat). It is safe to use them, as they are inlined // at compile-time and do nothing on earlier devices. - fullscreen_content?.systemUiVisibility = + binding.fullscreenContent.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + supportActionBar?.hide() + binding.fullscreenStatus.visibility = View.GONE + binding.fullscreenContentControls.visibility = View.GONE + binding.chapterCommentFab.hide() } private val mShowPart2Runnable = Runnable { // Delayed display of UI elements + binding.fullscreenContent.systemUiVisibility = + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION supportActionBar?.show() - fullscreen_status?.visibility = View.VISIBLE - mangaTitle?.isSelected = true - chapterStatusAndTitle?.isSelected = true - fullscreen_content_controls?.visibility = View.VISIBLE - chapterCommentFab?.show() + binding.fullscreenStatus.visibility = View.VISIBLE + binding.mangaTitle.isSelected = true + binding.chapterStatusAndTitle.isSelected = true + binding.fullscreenContentControls.visibility = View.VISIBLE + binding.chapterCommentFab.show() } + private var mVisible: Boolean = false - private val mHideRunnable = Runnable { hide() } - /** - * Delay hiding the system UI. This is to prevent the - * jarring behavior of controls going away - * while interacting with activity UI. - */ - private fun delayHide() { - delayedHide(AUTO_HIDE_DELAY_MILLIS) - } private fun toggle() = when (mVisible) { true -> hide() false -> show() } - private fun hide() { - // Hide UI first - supportActionBar?.hide() - fullscreen_status?.visibility = View.GONE - fullscreen_content_controls?.visibility = View.GONE - chapterCommentFab?.hide() + private fun hide(delay: Long = UI_ANIMATION_DELAY) { mVisible = false - // Schedule a runnable to remove the status and navigation bar after a delay mHideHandler.removeCallbacks(mShowPart2Runnable) - mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY.toLong()) + mHideHandler.postDelayed(mHidePart2Runnable, delay) } @SuppressLint("InlinedApi") - private fun show() { + private fun show(delay: Long = UI_ANIMATION_DELAY) { // Show the system bar - fullscreen_content?.systemUiVisibility = - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION mVisible = true // Schedule a runnable to display UI elements after a delay mHideHandler.removeCallbacks(mHidePart2Runnable) - mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY.toLong()) - } - - /** - * Schedules a call to hide() in [delayMillis] milliseconds, canceling any - * previously scheduled calls. - */ - private fun delayedHide(delayMillis: Int) { - mHideHandler.removeCallbacks(mHideRunnable) - mHideHandler.postDelayed(mHideRunnable, delayMillis.toLong()) + mHideHandler.postDelayed(mShowPart2Runnable, delay) } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ViewerFragment.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ViewerFragment.kt index d9ec8e7..1d9ac59 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ViewerFragment.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/ui/reader/ViewerFragment.kt @@ -1,16 +1,24 @@ package io.github.innoobwetrust.kintamanga.ui.reader import android.content.Context +import android.net.Uri import android.os.Bundle import android.view.* import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.RecyclerView -import io.github.innoobwetrust.kintamanga.R +import com.bumptech.glide.Glide +import com.bumptech.glide.ListPreloader +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader +import com.bumptech.glide.load.model.Headers +import com.bumptech.glide.load.model.LazyHeaders +import com.bumptech.glide.util.ViewPreloadSizeProvider +import io.github.innoobwetrust.kintamanga.databinding.FragmentImageViewerBinding +import io.github.innoobwetrust.kintamanga.model.Page import io.github.innoobwetrust.kintamanga.ui.model.ChapterBinding -import kotlinx.android.synthetic.main.fragment_image_viewer.* -import kotlinx.android.synthetic.main.fragment_image_viewer.view.* +import java.io.File import kotlin.math.abs class ViewerFragment : Fragment(), ReaderActivityListener { @@ -24,8 +32,18 @@ class ViewerFragment : Fragment(), ReaderActivityListener { get() = mReader?.mangaBinding?.mangaViewer private val chapterBinding: ChapterBinding? get() = mReader?.chapterBinding - val serverIndex: Int? - get() = mReader?.serverIndex + private val glideHeaders: Headers? + get() = (activity as? ReaderActivity) + ?.chapterInfoProcessor + ?.headers() + ?.fold( + LazyHeaders.Builder(), + { builder: LazyHeaders.Builder, pair: Pair -> + builder.addHeader(pair.first, pair.second) + }) + ?.build() + val serverIndex: Int + get() = mReader?.serverIndex ?: 0 private val pagerSnapHelper: PagerSnapHelper by lazy { object : PagerSnapHelper() { override fun findTargetSnapPosition( @@ -115,33 +133,12 @@ class ViewerFragment : Fragment(), ReaderActivityListener { } } + private lateinit var binding: FragmentImageViewerBinding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - return inflateView(inflater = inflater, container = container) - } - - override fun onResume() { - super.onResume() - syncPosition(newPosition = null) - } - - override fun onDestroyView() { - imageViewerRecyclerView?.adapter = null - super.onDestroyView() - System.gc() - } - - override fun onDetach() { - mReader = null - imageViewerRecyclerView?.adapter = null - super.onDetach() - System.gc() - } - - private fun inflateView(inflater: LayoutInflater?, container: ViewGroup?): View? { + savedInstanceState: Bundle?): View { // Always re-create RecyclerVew to sync position, this won't require much computation - val view = inflater!!.inflate(R.layout.fragment_image_viewer, container, false) - view.imageViewerRecyclerView?.apply { + binding = FragmentImageViewerBinding.inflate(inflater, container, false) + binding.imageViewerRecyclerView.apply { setHasFixedSize(true) layoutManager = when (mViewerType) { ViewerTypes.PAGER_HORIZONTAL_LEFT_TO_RIGHT.ordinal -> @@ -158,6 +155,7 @@ class ViewerFragment : Fragment(), ReaderActivityListener { adapter = ImageViewerAdapter( viewer = this@ViewerFragment, chapterBinding = chapterBinding!!, + glideHeaders = glideHeaders, viewerType = ViewerTypes.values()[mViewerType ?: 0] ) // Sync position @@ -179,9 +177,47 @@ class ViewerFragment : Fragment(), ReaderActivityListener { } } }) + addOnScrollListener(RecyclerViewPreloader(Glide.with(this), object : ListPreloader.PreloadModelProvider { + override fun getPreloadItems(position: Int): List { + return chapterBinding?.chapterPages?.get(position)?.run { listOf(this) } + ?: emptyList() + } + + override fun getPreloadRequestBuilder(page: Page): RequestBuilder { + return Glide.with(this@ViewerFragment).downloadOnly().load(when { + page.imageFileUri.isNotBlank() -> { + Uri.parse(page.imageFileUri) + } + page.imageUrls[this@ViewerFragment.serverIndex].isNotBlank() -> { + Uri.parse(page.imageUrls[this@ViewerFragment.serverIndex]) + } + else -> { + Uri.EMPTY + } + }) + } + }, ViewPreloadSizeProvider(), 10)) } } - return view + return binding.root + } + + override fun onResume() { + super.onResume() + syncPosition(newPosition = null) + } + + override fun onDestroyView() { + binding.imageViewerRecyclerView.adapter = null + super.onDestroyView() + System.gc() + } + + override fun onDetach() { + mReader = null + binding.imageViewerRecyclerView.adapter = null + super.onDetach() + System.gc() } fun syncPosition(newPosition: Int?, progressFeedback: Boolean = true) { @@ -197,13 +233,13 @@ class ViewerFragment : Fragment(), ReaderActivityListener { mReader?.onViewerPageChanged(newPagePosition = it.chapterLastPageRead) } if (pagesJump == 1) - imageViewerRecyclerView?.smoothScrollToPosition(it.chapterLastPageRead) + binding.imageViewerRecyclerView.smoothScrollToPosition(it.chapterLastPageRead) else - imageViewerRecyclerView?.scrollToPosition(it.chapterLastPageRead) + binding.imageViewerRecyclerView.scrollToPosition(it.chapterLastPageRead) } } override fun onImageLoaded(position: Int) { - imageViewerRecyclerView?.adapter?.notifyItemChanged(position) + binding.imageViewerRecyclerView.adapter?.notifyItemChanged(position) } } diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/util/GlideBitmapImageLoader.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/util/GlideBitmapImageLoader.kt deleted file mode 100644 index 70f30df..0000000 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/util/GlideBitmapImageLoader.kt +++ /dev/null @@ -1,286 +0,0 @@ -package io.github.innoobwetrust.kintamanga.util - -import android.content.Context -import android.graphics.drawable.Drawable -import android.net.Uri -import android.view.LayoutInflater -import android.view.View -import android.widget.ImageView -import com.bumptech.glide.Glide -import com.bumptech.glide.RequestManager -import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.request.target.CustomTarget -import com.bumptech.glide.request.transition.Transition -import com.crashlytics.android.Crashlytics -import com.github.piasy.biv.loader.ImageLoader -import com.github.piasy.biv.view.BigImageView -import io.github.innoobwetrust.kintamanga.R -import okhttp3.* -import okhttp3.ResponseBody.Companion.toResponseBody -import okio.* -import rx.Observable -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import timber.log.Timber -import java.io.File -import java.io.IOException -import java.io.InputStream -import java.util.* - -class GlideBitmapImageLoader private constructor( - context: Context, - okHttpClient: OkHttpClient? -) : ImageLoader { - private val mRequestManager: RequestManager - private val prefetchTargets: MutableList>> = mutableListOf() - - init { - GlideProgressSupport.init(Glide.get(context), okHttpClient) - mRequestManager = Glide.with(context) - } - - override fun loadImage(uri: Uri, callback: ImageLoader.Callback) { - mRequestManager - .downloadOnly() - .load(uri) - .into(object : ImageDownloadTarget(uri.toString()) { - override fun onResourceReady(resource: File, transition: Transition?) { - resource.let { - Observable - .fromCallable { - ImageConverter.convertToSupportedImage(source = it) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { success -> - if (success) { - callback.onCacheHit(it) - callback.onSuccess(it) - } else { - callback.onFail(Exception("Failed to convert image to bitmap")) - } - }, - { error -> - Timber.e(error) - Crashlytics.logException(error) - callback.onFail(Exception("Failed to convert image to bitmap")) - } - ) - } - } - - override fun onLoadFailed(errorDrawable: Drawable?) { - callback.onFail(Exception("Failed to load image")) - } - - override fun onDownloadStart() { - callback.onStart() - } - - override fun onProgress(progress: Int) { - callback.onProgress(progress) - } - - override fun onDownloadFinish() { - callback.onFinish() - } - }) - } - - override fun showThumbnail(parent: BigImageView, thumbnail: Uri, scaleType: Int): View { - val thumbnailView = LayoutInflater.from(parent.context) - .inflate(R.layout.ui_glide_thumbnail, parent, false) as ImageView - when (scaleType) { - BigImageView.INIT_SCALE_TYPE_CENTER_CROP -> - thumbnailView.scaleType = ImageView.ScaleType.CENTER_CROP - BigImageView.INIT_SCALE_TYPE_CENTER_INSIDE -> - thumbnailView.scaleType = ImageView.ScaleType.CENTER_INSIDE - } - mRequestManager - .load(thumbnail) - .into(thumbnailView) - return thumbnailView - } - - override fun prefetch(uri: Uri) { - if(prefetchTargets.none { it.first == uri }) { - mRequestManager - .downloadOnly() - .load(uri) - .into(object : CustomTarget() { - override fun onResourceReady(resource: File, transition: Transition?) { - ImageConverter.convertToSupportedImage(resource) - } - - override fun onLoadCleared(placeholder: Drawable?) {} - }.also { - if (prefetchTargets.size > 7) - mRequestManager.clear(prefetchTargets.removeAt(0).second) - prefetchTargets.add(uri to it) - }) - } - } - - fun cancelPrefetch() { - prefetchTargets.forEach { - mRequestManager.clear(it.second) - } - prefetchTargets.clear() - } - - companion object { - @JvmOverloads - fun with( - context: Context, - okHttpClient: OkHttpClient? = null - ): GlideBitmapImageLoader { - return GlideBitmapImageLoader(context, okHttpClient) - } - } -} - -object GlideProgressSupport { - private fun createInterceptor(listener: ResponseProgressListener): Interceptor { - return Interceptor { chain -> - val request = chain.request() - val response = chain.proceed(request) - response.newBuilder() - .body(OkHttpProgressResponseBody( - request.url, - response.body ?: "".toResponseBody(null), - listener - )) - .build() - } - } - - fun init(glide: Glide, okHttpClient: OkHttpClient?) { - val builder: OkHttpClient.Builder = okHttpClient?.newBuilder() ?: OkHttpClient.Builder() - builder.addNetworkInterceptor(createInterceptor(DispatchingProgressListener())) - glide.registry.replace( - GlideUrl::class.java, - InputStream::class.java, - OkHttpUrlLoader.Factory(builder.build()) - ) - } - - fun forget(url: String) { - DispatchingProgressListener.forget(url) - } - - fun expect(url: String, listener: ProgressListener) { - DispatchingProgressListener.expect(url, listener) - } - - interface ProgressListener { - fun onDownloadStart() - - fun onProgress(progress: Int) - - fun onDownloadFinish() - } - - private interface ResponseProgressListener { - fun update(url: HttpUrl, bytesRead: Long, contentLength: Long) - } - - private class DispatchingProgressListener : ResponseProgressListener { - - override fun update(url: HttpUrl, bytesRead: Long, contentLength: Long) { - val key = url.toString() - val listener = LISTENERS[key] ?: return - - val lastProgress = PROGRESSES[key] - if (lastProgress == null) { - // ensure `onStart` is called before `onProgress` and `onFinish` - listener.onDownloadStart() - } - if (contentLength <= bytesRead) { - listener.onDownloadFinish() - forget(key) - return - } - val progress = (bytesRead.toFloat() / contentLength * 100).toInt() - if (lastProgress == null || progress != lastProgress) { - PROGRESSES[key] = progress - listener.onProgress(progress) - } - } - - companion object { - private val LISTENERS = HashMap() - private val PROGRESSES = HashMap() - - internal fun forget(url: String) { - LISTENERS.remove(url) - PROGRESSES.remove(url) - } - - internal fun expect(url: String, listener: ProgressListener) { - LISTENERS[url] = listener - } - } - } - - private class OkHttpProgressResponseBody internal constructor( - private val mUrl: HttpUrl, - private val mResponseBody: ResponseBody, - private val mProgressListener: ResponseProgressListener - ) : ResponseBody() { - private var mBufferedSource: BufferedSource? = null - - override fun contentType(): MediaType? { - return mResponseBody.contentType() - } - - override fun contentLength(): Long { - return mResponseBody.contentLength() - } - - override fun source(): BufferedSource { - return mBufferedSource - ?: source(mResponseBody.source()).buffer() - .also { mBufferedSource = it } - } - - private fun source(source: Source): Source { - return object : ForwardingSource(source) { - private var mTotalBytesRead = 0L - - @Throws(IOException::class) - override fun read(sink: Buffer, byteCount: Long): Long { - val bytesRead = super.read(sink, byteCount) - val fullLength = mResponseBody.contentLength() - if (bytesRead == -1L) { // this source is exhausted - mTotalBytesRead = fullLength - } else { - mTotalBytesRead += bytesRead - } - mProgressListener.update(mUrl, mTotalBytesRead, fullLength) - return bytesRead - } - } - } - } -} - -abstract class ImageDownloadTarget protected constructor(private val mUrl: String) : - CustomTarget(), - GlideProgressSupport.ProgressListener { - - override fun onLoadCleared(placeholder: Drawable?) { - GlideProgressSupport.forget(mUrl) - } - - override fun onLoadStarted(placeholder: Drawable?) { - super.onLoadStarted(placeholder) - GlideProgressSupport.expect(mUrl, this) - } - - override fun onLoadFailed(errorDrawable: Drawable?) { - super.onLoadFailed(errorDrawable) - GlideProgressSupport.forget(mUrl) - } -} diff --git a/app/src/main/java/io/github/innoobwetrust/kintamanga/util/extension/StringExtensions.kt b/app/src/main/java/io/github/innoobwetrust/kintamanga/util/extension/StringExtensions.kt index cd9308c..073a143 100644 --- a/app/src/main/java/io/github/innoobwetrust/kintamanga/util/extension/StringExtensions.kt +++ b/app/src/main/java/io/github/innoobwetrust/kintamanga/util/extension/StringExtensions.kt @@ -1,6 +1,5 @@ package io.github.innoobwetrust.kintamanga.util.extension -import com.crashlytics.android.Crashlytics import timber.log.Timber import java.net.URI import java.net.URL @@ -17,7 +16,6 @@ val String.uriString: String } } catch (e: Exception) { Timber.e("$this: $e") - Crashlytics.logException(e) "" } @@ -32,7 +30,6 @@ fun String.uriString(context: URL): String = if (isBlank()) "" else try { } } catch (e: Exception) { Timber.e("$this: $e") - Crashlytics.logException(e) "" } @@ -47,4 +44,4 @@ fun String.chop(count: Int, replacement: String = "..."): String { else this -} \ 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 index 88cf31e..810c6c7 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -9,7 +9,8 @@ android:fitsSystemWindows="true" tools:openDrawer="start"> - + - - - - - - - - - - diff --git a/build.gradle b/build.gradle index 28f4612..d7cb800 100644 --- a/build.gradle +++ b/build.gradle @@ -1,37 +1,37 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' - ext.gradle_version = '3.6.3' + ext.kotlin_version = '1.4.21' + ext.gradle_version = '4.1.1' repositories { jcenter() maven { url "https://maven.google.com" } - maven { url 'https://maven.fabric.io/public' } google() } dependencies { - classpath "com.android.tools.build:gradle:$gradle_version" + classpath "com.android.tools.build:gradle:${gradle_version}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - classpath 'com.google.gms:google-services:4.3.3' - classpath 'com.google.firebase:firebase-plugins:2.0.0' - classpath 'io.fabric.tools:gradle:1.28.1' } } allprojects { repositories { + // Note: please put this download url at the first of your repositories part, otherwise, gradle may search in wrong place. + maven { url "http://dl.bintray.com/piasy/maven" } jcenter() + google() + mavenCentral() maven { url "https://jitpack.io" } - maven { url "http://dl.bintray.com/piasy/maven" } maven { url "https://maven.google.com" } - google() } } +apply from: 'versioning.gradle' + task clean(type: Delete) { delete rootProject.buildDir } diff --git a/gradle.properties b/gradle.properties index 2898ad2..f3f1c4c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,3 +21,5 @@ org.gradle.daemon=true org.gradle.configureondemand=true android.useAndroidX=true android.enableJetifier=true + +version=1.3.12 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4ec43c0..f44da4b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Apr 18 20:24:19 ICT 2020 +#Wed Dec 09 23:32:10 ICT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/versioning.gradle b/versioning.gradle new file mode 100644 index 0000000..80ddc88 --- /dev/null +++ b/versioning.gradle @@ -0,0 +1,37 @@ +// Source: https://github.com/jayway/AndroidGradleExample/blob/master/versioning.gradle +ext { + /** + * Builds an Android version code from the version of the project. + * This is designed to handle the -SNAPSHOT and -RC format. + * + * I.e. during development the version ends with -SNAPSHOT. As the code stabilizes and release nears + * one or many Release Candidates are tagged. These all end with "-RC1", "-RC2" etc. + * And the final release is without any suffix. + * @return + */ + buildVersionCode = { + //The rules is as follows: + //-SNAPSHOT counts as 0 + //-RC* counts as the RC number, i.e. 1 to 98 + //final release counts as 99. + //Thus you can only have 98 Release Candidates, which ought to be enough for everyone + + def candidate = "99" + def (major, minor, patch) = version.toLowerCase().replaceAll('-', '').tokenize('.') + if (patch.endsWith("snapshot")) { + candidate = "0" + patch = patch.replaceAll("[^0-9]","") + } else { + def rc + (patch, rc) = patch.tokenize("rc") + if (rc) { + candidate = rc + } + } + + (major, minor, patch, candidate) = [major, minor, patch, candidate].collect{it.toInteger()} + + (major * 1000000) + (minor * 10000) + (patch * 100) + candidate + } +} +