├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── .gitignore
├── .project
├── .settings
└── org.eclipse.buildship.core.prefs
├── .travis.yml
├── LICENSE.md
├── README.md
├── app
├── .gitignore
├── appcenter-pre-build.sh
├── build.gradle
├── debug
│ └── output.json
├── myapps.jks.enc
├── proguard-rules.pro
├── schemas
│ └── com.github.ttdyce.nhviewer.model.room.AppDatabase
│ │ ├── 1.json
│ │ └── 2.json
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── github
│ │ └── ttdyce
│ │ └── nhviewer
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── ic_launcher_nhviewer-web.png
│ ├── java
│ │ └── com
│ │ │ └── github
│ │ │ └── ttdyce
│ │ │ └── nhviewer
│ │ │ ├── model
│ │ │ ├── CookieStringRequest.java
│ │ │ ├── CustomGlideModule.java
│ │ │ ├── MyDistributeListener.java
│ │ │ ├── api
│ │ │ │ ├── NHAPI.java
│ │ │ │ ├── PopularType.java
│ │ │ │ └── ResponseCallback.java
│ │ │ ├── comic
│ │ │ │ ├── Comic.java
│ │ │ │ ├── ComicCollection.java
│ │ │ │ └── factory
│ │ │ │ │ ├── ComicFactory.java
│ │ │ │ │ ├── DBComicFactory.java
│ │ │ │ │ └── NHApiComicFactory.java
│ │ │ ├── proxy
│ │ │ │ └── NHVProxyStack.java
│ │ │ └── room
│ │ │ │ ├── AppDatabase.java
│ │ │ │ ├── ComicBookmarkDao.java
│ │ │ │ ├── ComicBookmarkEntity.java
│ │ │ │ ├── ComicCachedDao.java
│ │ │ │ ├── ComicCachedEntity.java
│ │ │ │ ├── ComicCollectionDao.java
│ │ │ │ ├── ComicCollectionEntity.java
│ │ │ │ └── DateConverter.java
│ │ │ ├── presenter
│ │ │ ├── ComicCollectionPresenter.java
│ │ │ ├── ComicListPresenter.java
│ │ │ └── ComicPresenter.java
│ │ │ └── view
│ │ │ ├── BackupActivity.java
│ │ │ ├── ComicActivity.java
│ │ │ ├── ComicCollectionFragment.java
│ │ │ ├── ComicCollectionViewHolder.java
│ │ │ ├── ComicListFragment.java
│ │ │ ├── ComicListViewHolder.java
│ │ │ ├── ComicViewHolder.java
│ │ │ ├── MainActivity.java
│ │ │ ├── ProxySettingsFragment.java
│ │ │ ├── RefreshCookieActivity.java
│ │ │ ├── SearchingFragment.java
│ │ │ ├── SettingsFragment.java
│ │ │ ├── SplashActivity.java
│ │ │ └── component
│ │ │ └── ZoomRecyclerView.java
│ └── res
│ │ ├── anim
│ │ └── splash_loading.xml
│ │ ├── drawable-v24
│ │ ├── ic_launcher_background.xml
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_add_to_photos_24dp.xml
│ │ ├── ic_check_box_24dp.xml
│ │ ├── ic_delete_24dp.xml
│ │ ├── ic_done_24dp.xml
│ │ ├── ic_favorite_black_24dp.xml
│ │ ├── ic_favorite_border_24dp.xml
│ │ ├── ic_folder_black_24dp.xml
│ │ ├── ic_home_black_24dp.xml
│ │ ├── ic_keyboard_arrow_right_24dp.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_launcher_nhviewer_background.xml
│ │ ├── ic_nhlogo.xml
│ │ ├── ic_search_black_24dp.xml
│ │ ├── ic_settings_black_24dp.xml
│ │ ├── ic_sort_24dp.xml
│ │ └── rectangle.xml
│ │ ├── layout
│ │ ├── activity_backup.xml
│ │ ├── activity_comic.xml
│ │ ├── activity_main.xml
│ │ ├── activity_refresh_cookie.xml
│ │ ├── activity_splash.xml
│ │ ├── fragment_comic_list.xml
│ │ ├── item_comic.xml
│ │ ├── item_comic_collection_list.xml
│ │ └── item_comic_list.xml
│ │ ├── menu
│ │ ├── app_bar_items_main.xml
│ │ ├── app_bar_items_searching.xml
│ │ ├── app_bar_selection_mode.xml
│ │ └── bottom_navigation_items.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ ├── ic_launcher_nhviewer.xml
│ │ ├── ic_launcher_nhviewer_round.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ ├── ic_launcher_nhviewer.png
│ │ ├── ic_launcher_nhviewer_foreground.png
│ │ ├── ic_launcher_nhviewer_round.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ ├── ic_launcher_nhviewer.png
│ │ ├── ic_launcher_nhviewer_foreground.png
│ │ ├── ic_launcher_nhviewer_round.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ ├── ic_launcher_nhviewer.png
│ │ ├── ic_launcher_nhviewer_foreground.png
│ │ ├── ic_launcher_nhviewer_round.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ ├── ic_launcher_nhviewer.png
│ │ ├── ic_launcher_nhviewer_foreground.png
│ │ ├── ic_launcher_nhviewer_round.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ ├── ic_launcher_nhviewer.png
│ │ ├── ic_launcher_nhviewer_foreground.png
│ │ ├── ic_launcher_nhviewer_round.png
│ │ └── ic_launcher_round.png
│ │ ├── navigation
│ │ └── nav_app.xml
│ │ ├── values-zh-rCN
│ │ └── strings.xml
│ │ ├── values-zh-rTW
│ │ └── strings.xml
│ │ ├── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ic_launcher_nhviewer_background.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ ├── firebase_default_config.xml
│ │ ├── preferences.xml
│ │ └── proxy_preferences.xml
│ └── test
│ └── java
│ └── com
│ └── github
│ └── ttdyce
│ └── nhviewer
│ ├── APIUnitTest.java
│ ├── ComicCollectionUnitTest.java
│ ├── ComicUnitTest.java
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── collection.png
├── deprecated
│ ├── V2
│ │ ├── collection.png
│ │ ├── comic.png
│ │ ├── favorite.png
│ │ ├── index.png
│ │ ├── search.png
│ │ └── setting.png
│ ├── collection_list.png
│ ├── favorite_list.png
│ └── navigation_view.png
├── search.png
└── setting.png
└── settings.gradle
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [ttdyce] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: ttdyce # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | name: ''
5 | labels: bug
6 | assignees: ttdyce
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Exception thrown**
24 | Copy the whole exception hints here, usually it is very helpful for debugging!
25 |
26 | **Screenshots**
27 | If applicable, add screenshots to help explain your problem.
28 |
29 | **Desktop (please complete the following information):**
30 | - OS: [e.g. iOS]
31 | - Version [e.g. 22]
32 |
33 | **Smartphone (please complete the following information):**
34 | - Device: [e.g. iPhone6]
35 | - OS: [e.g. iOS8.1]
36 | - Version [e.g. 22]
37 |
38 | **Additional context**
39 | Add any other context about the problem here.
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 | *.aab
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 | release/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # IntelliJ
38 | *.iml
39 | .idea/workspace.xml
40 | .idea/tasks.xml
41 | .idea/gradle.xml
42 | .idea/assetWizardSettings.xml
43 | .idea/dictionaries
44 | .idea/libraries
45 | # Android Studio 3 in .gitignore file.
46 | .idea/caches
47 | .idea/modules.xml
48 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
49 | .idea/navEditor.xml
50 |
51 | # Keystore files
52 | # Uncomment the following lines if you do not want to check your keystore files in.
53 | *.jks
54 | #*.keystore
55 | keystore.properties
56 |
57 | # External native build folder generated in Android Studio 2.2 and later
58 | .externalNativeBuild
59 |
60 | # Google Services (e.g. APIs or Firebase)
61 | # google-services.json
62 |
63 | # Freeline
64 | freeline.py
65 | freeline/
66 | freeline_project_description.json
67 |
68 | # fastlane
69 | fastlane/report.xml
70 | fastlane/Preview.html
71 | fastlane/screenshots
72 | fastlane/test_output
73 | fastlane/readme.md
74 |
75 | # Version control
76 | vcs.xml
77 |
78 | # lint
79 | lint/intermediates/
80 | lint/generated/
81 | lint/outputs/
82 | lint/tmp/
83 | # lint/reports/
84 | .idea
85 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | NHViewer
4 | Project NHentaiViewer created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
19 | 1602326476666
20 |
21 | 30
22 |
23 | org.eclipse.core.resources.regexFilterMatcher
24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/Library/Java/JavaVirtualMachines/microsoft-17.jdk/Contents/Home
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | dist: trusty
3 | os: linux
4 | env:
5 | global:
6 | - BUILD_TOOL_VERSION=29.0.2
7 | - ANDROID_API=29
8 | - secure: jqQJ3WXC94shPZSwQX6SnLMGoMGt7xZfUcSV2KgQ648R47kaSMnwt7kyh1X7wXX2ttL1BG4Mkii4JSsEzehzLfA0AJN9hRwdY7EctMPE3QTPfHXKL/CSQBc+2vh2OPpP0iw3FNHxL6uS/TT3zydy6m8aRxwvXPoZ4d7r0l6TnQG2MVzC4eufyZj7Ms2cXY57aZDDbramTFIIYzLGtCX+aGgCdFGQ/fWbNnVBprHdNbeRq0VqlhUl2Cc+GCZEVYzjbwIhRq3SEdmV5W0uHZR6+2kqf6pigyVdDvxcigKF/jzRzwkADzn/nTpBcBHcQ03sxtCBs2mBF0YDd0fNMzyX9i7SZliiGIzJo08zWqRoqipWd29fGHiJoM4VeSEparY/G+EPNzuA0IdnCgKzG8e6BLpo0cJ16CxVrj28lUel10VKihbJcvgyS0OaUpba57/5hplEw49Ldvj9jIf8nICSXLftrn25uDkhorOFnztAUo/R8F6QA5zzv+dska58y+jriAM/rhEA5xLzpLWmcJcph5abRvurhceAVI/s6gaj7MOJCjwTrJNyizQo3tof/wZlI4iXfBEbOuB3jo1rkxrrMHtRXLwewjpezm7GWiDcGBD49tgigV2B0VfCYjIM0MEm9oucLpB8Dp2e4VbFRxRuM6TkxfC5iZIVX1SL4jk2TlY=
9 | android:
10 | components:
11 | - build-tools-${BUILD_TOOL_VERSION}
12 | - android-${ANDROID_API}
13 | - tools
14 | - platform-tools
15 | before_cache:
16 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
17 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
18 | cache:
19 | directories:
20 | - $HOME/.gradle/caches/
21 | - $HOME/.gradle/wrapper/
22 | - $HOME/.android/build-cache
23 | before_install:
24 | - openssl aes-256-cbc -k "$keystore_d" -in app/myapps.jks.enc -out app/myapps.jks -d
25 | - chmod +x gradlew
26 | script:
27 | - ./gradlew assembleDebug
28 | - ./gradlew assembleRelease
29 | - ls -l app/build/outputs/apk/debug
30 | - ls -l app/build/outputs/apk/release
31 | before_deploy:
32 | - mv app/build/outputs/apk/debug/app-debug.apk app/build/outputs/apk/debug/nhviewer-${TRAVIS_TAG}.apk
33 | - mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/nhviewer-${TRAVIS_TAG}-signed.apk
34 | - ls -l app/build/outputs/apk/debug
35 | - ls -l app/build/outputs/apk/release
36 | deploy:
37 | provider: releases
38 | skip_cleanup: true
39 | overwrite: true
40 | draft: true
41 | api_key:
42 | secure: V4d6VAD4MBk3Uk9DlizxOCXEFgJGtsSMuTOddtJAT/ETByCfrk09u3FjRCPPzRipfnPwrtIbNtVOLI7/joR6amErxoyPg0kLeARFDIh8lcRhbELv+ybuDNRM/FvRlSc0+RtXZ5XYlU+cVAtUH3gnnv3smxFTZvLHM4ZD4f5ND6Q4AlemWqy7gpqYobxw8bFEhOTWKB+HUeBsNBdqPCSjWrmcvTWYdE0WNb8h5N03ncnJ1PySu4FM+UD1bOGvSgE/ACRcNG0ASCNwChrhokz/RnVfhujIhgL4G3bK5unJqTqgHeE76R9zV4pKdsHsiy0B1S/6IkAyiDFDgRtU9qfPWD1tc9d9E+xsoMiyX0h9B9EK+aoTu2wnZqwHVa5gk63eO0kPDjlU3SY/zeJ5DTMUTOdJVkvDPJrApsDs4H4yGQmCXuaG6i6kqICGjFLfB2bLIaKNbobIyxd5g2B9CqHn2eHNXT9LDUVUXDIT38LNvDf9a+2o9feRX5hwkj1FHEAXD/R67AljrLKsDWHgZJLi6xP9wjD2qFHsCZKIBrvw/+R3xoZkql37WS1QI6TjpcvaJ02dJJuOGoLs4L+SYb5tLS5eXGFb2L0ezzUOeutvzYtxT+rswmrY7rdqeJV1UZtLWvy6f+CiH4i1+YzT/qcDrSEM3Ly/AyzpT6oXLdpj7aw=
43 | file:
44 | - app/build/outputs/apk/debug/nhviewer-${TRAVIS_TAG}.apk
45 | - app/build/outputs/apk/release/nhviewer-${TRAVIS_TAG}-signed.apk
46 | on:
47 | repo: ttdyce/NHentai-NHViewer
48 | tags: true
49 |
50 | # jdk: oraclejdk8
51 | # android:
52 | # components:
53 | # - tools
54 | # - platform-tools
55 | # - build-tools-29.0.2
56 | # - android-${ANDROID_API}
57 | # - extra-android-m2repository
58 | # licenses:
59 | # - android-sdk-license-.+
60 |
61 | # before_cache: "-rm -f $HOME/.gradle/caches/modules-2/modules-2.lock -rm -fr $HOME/.gradle/caches/*/plugin-resolution/"
62 | # cache:
63 | # directories:
64 | # - "$HOME/.gradle/caches/"
65 | # - "$HOME/.gradle/wrapper/"
66 | # - "$HOME/.android/build-cache"
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 ttdyce
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NHViewer
2 |
3 | [](https://github.com/ttdyce/NHentaiViewer/blob/master/LICENSE.md)
4 |
5 | Simple third-party application for browsing nhentai.net.
6 |
7 | > Just a little app for Android
8 |
9 | ## Important Note: Visual Studio App Center is retiring soon!
10 |
11 | written on 2024-12-23
12 |
13 | Visual Studio App Center will be retired on March 31, 2025, which means this app won't receive auto update afterwards.
14 | To provide a wider support for both iOS and Android, I am working on a new solution on [ttdyce/nhviewer-universal](https://github.com/ttdyce/nhviewer-universal) as a replacement of this app. It's still under development but you can find the latest news there.
15 |
16 | ---
17 |
18 | ## Download
19 |
20 | [Download From VS App Center (retiring on March 31, 2025!)](https://install.appcenter.ms/users/ttdyce/apps/nhviewer-1/distribution_groups/public)
21 |
22 | ### Important note
23 |
24 | - Download from VS App Center is recommended for auto-update
25 | - To support this project, you can...
26 | 1. Sponsor this project on GitHub
27 |
28 | 3. Or... star the replacement app 🌟 [ttdyce/nhviewer-universal](https://github.com/ttdyce/nhviewer-universal)
29 |
30 | ## Look and Feel - in demo mode( •̀ ω •́ )y
31 |
32 | 

33 |
34 | ## Features
35 |
36 | - Collection system
37 | - Add / remove comic from Favorite / Read later / History
38 | - Backup collection content to desktop (by scanning QRCode with [NHV-Backup, Java program](https://github.com/ttdyce/NHV-Backup))
39 | - General
40 | - Basic proxy
41 | - Vertical scrolling
42 | - Comic list sorting (by popularity / uploaded recently)
43 | - Search with specific language (Chinese / English / Japanese)
44 |
45 | ## V3 Overview
46 |
47 | - Dark theme (good for your eyes😉, or, at least for me...)
48 |
49 | - Better auto-update
50 |
51 | More new features (see #Roadmap)
52 |
53 | ### Version 2 overview
54 |
55 | - [M-V-P](https://stackoverflow.com/questions/2056/what-are-mvp-and-mvc-and-what-is-the-difference)
56 | - Retrieve data from [JSON API](https://github.com/NHMoeDev/NHentai-android/issues/27) (that page was removed, you may need to read the code/google it at this point...)
57 | - the closest document I could find is [this](https://hentaichan.pythonanywhere.com/projects/hentai/api-endpoints) by [@hentai-chan](https://github.com/hentai-chan)
58 | - [Android Jetpack Components](https://developer.android.com/jetpack)
59 |
60 | ### Icon (Version 2) & Splash screen
61 |
62 | I would like to include NHentai’s icon and slogan in this project since it is an application about their site.
63 | I have sent them an email to ask for permission but there is still no reply yet.
64 | Please contact me if there are any issue, thanks.
65 |
66 | ---
67 |
68 | ## Getting Started
69 |
70 | The application is using the [JSON API](https://github.com/NHMoeDev/NHentai-android/issues/27) (page was removed😥) and parse the response data into Java class from version 2.
71 |
72 | - the closest document I could find is [this](https://hentaichan.pythonanywhere.com/projects/hentai/api-endpoints) by [@hentai-chan](https://github.com/hentai-chan)
73 |
74 |
75 | 😣~~For more information about coding, see [the wiki](https://github.com/ttdyce/NHentaiViewer/wiki) (which is not yet ready '_' please come back later).~~
76 |
77 | ## Deployment
78 |
79 | Build and run the project inside Android Studio.
80 |
81 | ## Built With
82 |
83 | - [Android Studio](https://developer.android.com/studio)
84 | - Any version after Android Studio 3.5 should be fine
85 | - Run on an Android device (tested on Android 8.0 Oreo)
86 |
87 | ---
88 |
89 | ## Versioning
90 |
91 | - [SemVer](http://semver.org/)
92 |
93 | For the versions available, see the [tags on this repository](https://github.com/ttdyce/nhviewer/tags)
94 |
95 | ## Authors
96 |
97 | - **ttdyce** - *Author and maintainer* - [github](https://github.com/ttdyce)
98 |
99 | ## Acknowledgments
100 |
101 | - Thanks for
102 | - Simplified Chinese translate from [@History-exe](https://github.com/History-exe)
103 | - Traditional Chinese translate from [@neslxzhen](https://github.com/neslxzhen)
104 | - Beautiful badges displaying GitHub data from [Shields.io](https://github.com/badges/shields)
105 |
106 | - Inspired by
107 | - [nhentai.net](https://nhentai.net)
108 | - [NHBooks](https://github.com/NHMoeDev/NHentai-android)
109 | - [EhViewer](https://github.com/seven332/EhViewer)
110 |
111 | - Dependencies
112 | - Image blur: [glide-transformations](https://github.com/wasabeef/glide-transformations) from [@wasabeef](https://github.com/wasabeef)
113 | - [QRCodeReaderView](https://github.com/dlazaro66/QRCodeReaderView) from [@dlazaro66](https://github.com/dlazaro66)
114 | - [Gson](https://github.com/google/gson)
115 | - [jsoup](https://jsoup.org/download)
116 | - [Glide](http://bumptech.github.io/glide/doc/download-setup.html)
117 | - [Volley](https://developer.android.com/training/volley)
118 | - Android's libraries
119 |
120 | ## License
121 |
122 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
123 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/appcenter-pre-build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | openssl aes-256-cbc -d -a -in myapps.jks.enc -out myapps.jks -k "$keystore_d"
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: "org.ajoberstar.grgit"
3 |
4 | import org.ajoberstar.grgit.Grgit
5 | import org.ajoberstar.grgit.Tag
6 |
7 | ext {
8 | //Git tag as version name, using org.ajoberstar.grgit.Grgit
9 | git = Grgit.open(currentDir: project.rootDir)
10 | tags = git.tag.list()
11 |
12 | // sort tags in integer way, default in string way
13 | // e.g. after sort: (latest)2.10.0 > 2.9.0 > 2.8.0, default: (latest)2.9.0 > ... > 2.2.0 > 2.10.0 😑
14 | Collections.sort(tags, new Comparator() {
15 | @Override
16 | int compare(Tag a, Tag b) {
17 | ArrayList aNames = a.name.split("\\.")
18 | ArrayList bNames = b.name.split("\\.")
19 |
20 | int bigVersionDiff = Integer.parseInt(aNames[0]) - Integer.parseInt(bNames[0])
21 | int featureVersionDiff = Integer.parseInt(aNames[1]) - Integer.parseInt(bNames[1])
22 | int bugfixVersionDiff = Integer.parseInt(aNames[2]) - Integer.parseInt(bNames[2])
23 |
24 | return bigVersionDiff > 0 || featureVersionDiff > 0 || bugfixVersionDiff > 0 ? 1 : 0
25 | }
26 | })
27 |
28 | gitVersionCode = tags.size()
29 | gitVersionName = tags[tags.size()-1].getName()
30 |
31 | println 'gitVersionCode'
32 | println 'gitVersionName'
33 | println gitVersionCode
34 | println gitVersionName
35 | }
36 |
37 | android {
38 | compileSdkVersion 29
39 | buildToolsVersion "29.0.2"
40 | defaultConfig {
41 | applicationId "com.github.ttdyce.nhviewer"
42 | minSdkVersion 21
43 | targetSdkVersion 29
44 | versionCode gitVersionCode
45 | versionName gitVersionName
46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
47 |
48 | //for blur
49 | renderscriptTargetApi 28
50 | renderscriptSupportModeEnabled true
51 | signingConfig signingConfigs.debug
52 | //export AppDatabase (Room database) to /schemas/*.json
53 | javaCompileOptions {
54 | annotationProcessorOptions {
55 | arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
56 | }
57 | }
58 | }
59 | signingConfigs {
60 | config
61 | release
62 | }
63 | buildTypes {
64 | debug {
65 | signingConfig signingConfigs.config
66 | applicationIdSuffix ".debug"
67 | }
68 | release {
69 | minifyEnabled false
70 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
71 | signingConfig signingConfigs.release
72 | }
73 | }
74 |
75 | //signing with travis config for release
76 | def isAppCenter = System.getenv("isAppCenter") == "true"
77 | if (isAppCenter) {
78 | // configure keystore
79 | signingConfigs.release.storeFile = signingConfigs.config.storeFile = file("myapps.jks")
80 | signingConfigs.release.storePassword = signingConfigs.config.storePassword = System.getenv("keystore_password")
81 | signingConfigs.release.keyAlias = signingConfigs.config.keyAlias = System.getenv("keystore_alias")
82 | signingConfigs.release.keyPassword = signingConfigs.config.keyPassword = System.getenv("keystore_alias_password")
83 | }else{
84 | //signing with {project_root}/keystore.properties if exists
85 | def ksFile = rootProject.file('keystore.properties')
86 | def props = new Properties()
87 |
88 | if (ksFile.canRead()) {
89 | props.load(new FileInputStream(ksFile))
90 |
91 | if (props != null) {
92 | android.signingConfigs.config.storeFile file(props['KEYSTORE_FILE'])
93 | android.signingConfigs.config.storePassword props['KEYSTORE_PASSWORD']
94 | android.signingConfigs.config.keyAlias props['KEYSTORE_ALIAS']
95 | android.signingConfigs.config.keyPassword props['KEYSTORE_ALIAS_PASSWORD']
96 | } else {
97 | println 'some entries in \'keystore.properties\' not found!'
98 | }
99 | } else {
100 | println '\'keystore.properties\' not found! Using default signing config'
101 | android.signingConfigs.config.storeFile android.signingConfigs.debug.storeFile
102 | android.signingConfigs.config.storePassword android.signingConfigs.debug.storePassword
103 | android.signingConfigs.config.keyAlias android.signingConfigs.debug.keyAlias
104 | android.signingConfigs.config.keyPassword android.signingConfigs.debug.keyPassword
105 | }
106 | }
107 |
108 |
109 | compileOptions {
110 | sourceCompatibility JavaVersion.VERSION_1_8
111 | targetCompatibility JavaVersion.VERSION_1_8
112 | }
113 | }
114 |
115 | dependencies {
116 | def nav_version = '2.2.2'
117 | def room_version = '2.4.0'
118 | def preference_version = '1.1.1'
119 | def appCenterSdkVersion = '4.1.0'
120 |
121 | // Visual Studio App Center
122 | implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}"
123 | implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
124 | implementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}"
125 |
126 | implementation "com.github.bumptech.glide:volley-integration:4.11.0"
127 |
128 | implementation 'com.google.android.material:material:1.1.0'
129 | //image transformations for Glide (for blur image)
130 | implementation 'jp.wasabeef:glide-transformations:4.3.0'
131 | //QRCode scanner
132 | implementation 'com.dlazaro66.qrcodereaderview:qrcodereaderview:2.0.2'
133 | //ROOM
134 | implementation "androidx.room:room-runtime:$room_version"
135 | annotationProcessor "androidx.room:room-compiler:$room_version"
136 | //Glide
137 | implementation 'com.github.bumptech.glide:glide:4.11.0'
138 | annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
139 | //preferenceFragment
140 | implementation "androidx.preference:preference:$preference_version"
141 | //for Jetpack navigation
142 | implementation "androidx.navigation:navigation-fragment:$nav_version"
143 | implementation "androidx.navigation:navigation-ui:$nav_version"
144 | implementation 'com.google.code.gson:gson:2.8.6'
145 | implementation 'com.android.volley:volley:1.2.1'
146 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
147 | testImplementation 'org.json:json:20200518'//for unit test using json
148 | implementation fileTree(dir: 'libs', include: ['*.jar'])
149 | implementation 'androidx.appcompat:appcompat:1.1.0'
150 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
151 | testImplementation 'junit:junit:4.13'
152 | androidTestImplementation 'androidx.test:runner:1.2.0'
153 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
154 | }
155 |
156 | //apply plugin: 'com.google.gms.google-services' // seems not using anymore
157 |
--------------------------------------------------------------------------------
/app/debug/output.json:
--------------------------------------------------------------------------------
1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-debug.apk","fullName":"debug","baseName":"debug"},"path":"app-debug.apk","properties":{}}]
--------------------------------------------------------------------------------
/app/myapps.jks.enc:
--------------------------------------------------------------------------------
1 | U2FsdGVkX1+HbtGCWHdmojGSH6BZncaviW8e02gplw1v5OnwPs1MfPU71Qm+jgni
2 | XyB1pLRW1vcHOVgK3ycnAssVin38jISNM88JY0SS+qSka9R5nqAJyyUgl9LjYoJC
3 | CC7zM9nJ08Sa0w0rMY593NXr+O8xhAwEftBwWiyepVl0NYJJNG1bUETwfb3KE2VS
4 | jE3ILxiRgeLEKJvIOd8vwyEJw7cjDV1CTf7JeDmuzpnChMz5lp9yj1LbKO8+BF2L
5 | i9rDiJFE2xz3bSPJBrV8q1kWemQuJ9V4c5LVTevXDzazWuEJuYDSnfU/CzPF+h6Y
6 | iAeNNjW0K5S6EuxyL3ao9k3KrovIJl0K+CbWqqCpb5Mxq1B3BJzMW3n42HE2sdD/
7 | YlrRDlSZq/uMJjkJNCRaEdGTU8CB29ayVhuOIz+r/wcJhAlOY+aKhBZElmk6sW+1
8 | 8nbwCi8uiTDv7dn0DYj/l4yv1r03AcgkmnkHHWgpmpnQfs4fM9qSbPDsEVEeJkZ8
9 | AgK8EwHNww98UCvweS/qO3tjkVBvpW8lm7U20D3GBDQ3vgGnG1YC+FQY1CAGlmja
10 | UX9jMbhL2KEEpozAJqGLvlpz8cBs6xkUGjoWbOUHcmH8O0HY8NOErTxB+SxuVnrz
11 | 7JlsbUJORWvvA0MGmqe18EazJm427+kmncZR7leOf7EIBZiAWxVqAfZXCny8h/cm
12 | 0+zXjgq8mVo0eTkyzOnee3xuKfyHTi7IsCzR4HksfwWRCq8iiASyfwn65yEJgQ9I
13 | aofPssTefYoYupkG/g4OS10GTr9dtUa41XJvYgTlN6DD9QIUqLSHFKQf91/d2w8z
14 | ioa749Ij8f0j/4Stkc8ixjr0WLl/tVQyNewc/HOLPf8L7ldIAhlpfnrnXxc0BKBu
15 | sxmy6V0L7+yfpAhGyqY9f6qXZQB4s2wu07Y+DFe3spEpL5kgMWo5EvSlzL/nIV+e
16 | GIXJ8GKSxUg620538Zk6ge5nuSggNHVVrJufCFKgzTFVrcCjxnz1sL3B5TD2Wqaq
17 | vdpeyyyoJ/fvh4AZu7rpkBgt8QRRk1plWCa3+6KPoylMaJ+hwzy0ZnwByvTcc+QU
18 | zQ0JIgn1hJRFARW3/1bXO2g8uFUryym7bT56tReQ57b/owiU5LDJm8nE8k8bsZx5
19 | ERpvgKNDY5HfkUBJpLnqGMX95mS620WQFtEyUzIyE2A9RcXm6mu98zAox0Y+EPEo
20 | D8ddr83jWqdF4W5mmTS9xLN8UfEmdzyjA5SFBTAvNRHpXCC+M8odS2eRDS/9y9CU
21 | s44PV750Jf4PI/zdXtqjq36eolIhL18gp1hPJAvq7gmz948WVEY3sjtoVHVikE+U
22 | /6rfoKaDgZ4vd5MAMlV/5On+Zo6kG7OzlcTkR3UGDXCkOU75g80DfPbf2OqaROdl
23 | 8Q2W6stYT0X8BFUzv/ZCkWh4QOC/J/vNb1r5igj369RKl3cw7gixEJbJW0coHVy3
24 | uAuuk44PBntQHoa/xTG5tSn5KJ7Xnqs5u91ddLxj53ehIt+PCKjgm6jBPDAoQcbL
25 | 8GYRDqJgeV1IUikFQWudzXb5IuuByKsVMhUuZjUgUcaUjwoTzkZ7MmGVU31daK6C
26 | kNplMxvBGnmGXSplS+RVC2uQi7ZEa+vrAReHG3vTpSaEaevZoN1B0P0X8aKg/mPz
27 | 2KiBmm+Sh3M9mdxhvbNPykkmq2xL+oOi7Qaogrj9/+Aok3o/GoPEU2UdzCeGQN2z
28 | QQ/u4kjfFs54A642gb+OyW99wA8ZObXW5MTVLRxGj3/3IAbBXqXKT635AMCRhIu8
29 | JTPR6FFCk5s1lomdanhOLqqm8Mhj6kncMEhSIW0oMKRgqOxJuTPdsm6vofHVCHNQ
30 | eKvKUP5kljiMW2LOWGLU7e/YwvjvMvJHFK8dbOvFKWz1rpOud9IaW9F2FpQdwWV1
31 | C8lRA03x4ZFcXaBEoxQL0ZArD3KCpwyyUXBb8zQxzbFdmOPeoV6AbcqLsjBt6X4E
32 | T0eDq1oJuMPFF33CnMnZb1jqcKf4vBttIEKnYe9XUmIRUMSfgSq7qM6be3aU/Fp2
33 | 5b8Ocy2Fc168tikP4L90X61l+W009OPuNpJcNe5IcipBl5Fzr8oGVaU9xGyVJx38
34 | v77PzwzyB+ElC1U4y7MdzRi+Vgwp04KefJw0mhbkZJLg+MbbRsQnqZ2VGMDv2F/Q
35 | nEjrtimvBEP22TtOjBxoxkjK8Sc3eLMR+5qAHgfzwffNEQbLPTC4XPiGMCXVGpXQ
36 | JGxkT6wyyTNm8qCStfuuhQ0SpVmvn6g7VrNH9kJkiSJD/DfLpcT/mA1fX/WCLtIB
37 | NNB42qWlPd0ym9U4Vo6G7SfbCdULvaDmAgLStEAgPY8v6u1aax/QYb8WrYIpV/2X
38 | xIFipm4Rt1i9eqqHKcD3LadGwAZ6sSPY2bGLiGwqao8OZe6mNCwOVGcpDAcfLm/C
39 | K/+2/mtaa7CyeWfWp05AxdzEhOFKuuu7rIr7e4xcgpWIZuK+MtbCfunaDFaIQ13L
40 | mZj6681AL1Ue7fAr6OVFCGi/rC0sP9J2m1d4PvmMiMsguqEYcw8cT//1ysSsFW7Z
41 | pP7ZgPzNdBcyEC0hCy1TCALyTTvtrt9tEMJpeHAIx10ydmA5BfkQ0C++0KHRbfdD
42 | IkAZgVrUuNd3NjIs/I1PeqLskYANOhjI4cobLnu/h35nTBLDF+4H+fsG3jfT43Ww
43 | C2CjKvU0pZIxO4F+60shVv5+w8f4Dt1cFosviyZ8zvAqduaafJUeuJ59ezeciwHS
44 | F3eMXeq/zS6g43asnXgrAyMGMAcIVzyy08wSGnKu1WR07N4emVj49RtoKeyPVglb
45 | /i+DiRClk4V9pgtPl4/MVv8f+HrnCq4VUtEArDXYf/kLxmwDU41TgvbhkwpJS1D8
46 | TE93MNqX0DYZW+goLAikQOxHdt0SiyOzXxH+d9CpH5PIdWIKWS5NdR5JzPFnmgsE
47 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/schemas/com.github.ttdyce.nhviewer.model.room.AppDatabase/1.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 1,
5 | "identityHash": "b0fd0de536c780c474fb1a1caaeb3786",
6 | "entities": [
7 | {
8 | "tableName": "ComicCollection",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `id` INTEGER NOT NULL, `dateCreated` TEXT NOT NULL, PRIMARY KEY(`name`, `id`))",
10 | "fields": [
11 | {
12 | "fieldPath": "name",
13 | "columnName": "name",
14 | "affinity": "TEXT",
15 | "notNull": true
16 | },
17 | {
18 | "fieldPath": "id",
19 | "columnName": "id",
20 | "affinity": "INTEGER",
21 | "notNull": true
22 | },
23 | {
24 | "fieldPath": "dateCreated",
25 | "columnName": "dateCreated",
26 | "affinity": "TEXT",
27 | "notNull": true
28 | }
29 | ],
30 | "primaryKey": {
31 | "columnNames": [
32 | "name",
33 | "id"
34 | ],
35 | "autoGenerate": false
36 | },
37 | "indices": [],
38 | "foreignKeys": []
39 | },
40 | {
41 | "tableName": "ComicCached",
42 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `mid` TEXT NOT NULL, `title` TEXT NOT NULL, `pageTypes` TEXT NOT NULL, `numOfPages` INTEGER NOT NULL, PRIMARY KEY(`id`))",
43 | "fields": [
44 | {
45 | "fieldPath": "id",
46 | "columnName": "id",
47 | "affinity": "INTEGER",
48 | "notNull": true
49 | },
50 | {
51 | "fieldPath": "mid",
52 | "columnName": "mid",
53 | "affinity": "TEXT",
54 | "notNull": true
55 | },
56 | {
57 | "fieldPath": "title",
58 | "columnName": "title",
59 | "affinity": "TEXT",
60 | "notNull": true
61 | },
62 | {
63 | "fieldPath": "pageTypes",
64 | "columnName": "pageTypes",
65 | "affinity": "TEXT",
66 | "notNull": true
67 | },
68 | {
69 | "fieldPath": "numOfPages",
70 | "columnName": "numOfPages",
71 | "affinity": "INTEGER",
72 | "notNull": true
73 | }
74 | ],
75 | "primaryKey": {
76 | "columnNames": [
77 | "id"
78 | ],
79 | "autoGenerate": false
80 | },
81 | "indices": [],
82 | "foreignKeys": []
83 | },
84 | {
85 | "tableName": "ComicBookmark",
86 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`page` INTEGER NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`, `page`))",
87 | "fields": [
88 | {
89 | "fieldPath": "page",
90 | "columnName": "page",
91 | "affinity": "INTEGER",
92 | "notNull": true
93 | },
94 | {
95 | "fieldPath": "id",
96 | "columnName": "id",
97 | "affinity": "INTEGER",
98 | "notNull": true
99 | }
100 | ],
101 | "primaryKey": {
102 | "columnNames": [
103 | "id",
104 | "page"
105 | ],
106 | "autoGenerate": false
107 | },
108 | "indices": [],
109 | "foreignKeys": []
110 | }
111 | ],
112 | "views": [],
113 | "setupQueries": [
114 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
115 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b0fd0de536c780c474fb1a1caaeb3786')"
116 | ]
117 | }
118 | }
--------------------------------------------------------------------------------
/app/schemas/com.github.ttdyce.nhviewer.model.room.AppDatabase/2.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 2,
5 | "identityHash": "5522322ef61d43afac94f7fa9c9c083e",
6 | "entities": [
7 | {
8 | "tableName": "ComicCollection",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `id` INTEGER NOT NULL, `dateCreated` TEXT NOT NULL, PRIMARY KEY(`name`, `id`))",
10 | "fields": [
11 | {
12 | "fieldPath": "name",
13 | "columnName": "name",
14 | "affinity": "TEXT",
15 | "notNull": true
16 | },
17 | {
18 | "fieldPath": "id",
19 | "columnName": "id",
20 | "affinity": "INTEGER",
21 | "notNull": true
22 | },
23 | {
24 | "fieldPath": "dateCreated",
25 | "columnName": "dateCreated",
26 | "affinity": "TEXT",
27 | "notNull": true
28 | }
29 | ],
30 | "primaryKey": {
31 | "columnNames": [
32 | "name",
33 | "id"
34 | ],
35 | "autoGenerate": false
36 | },
37 | "indices": [],
38 | "foreignKeys": []
39 | },
40 | {
41 | "tableName": "ComicCached",
42 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `mid` TEXT NOT NULL, `title` TEXT NOT NULL, `pageTypes` TEXT NOT NULL, `numOfPages` INTEGER NOT NULL, PRIMARY KEY(`id`))",
43 | "fields": [
44 | {
45 | "fieldPath": "id",
46 | "columnName": "id",
47 | "affinity": "INTEGER",
48 | "notNull": true
49 | },
50 | {
51 | "fieldPath": "mid",
52 | "columnName": "mid",
53 | "affinity": "TEXT",
54 | "notNull": true
55 | },
56 | {
57 | "fieldPath": "title",
58 | "columnName": "title",
59 | "affinity": "TEXT",
60 | "notNull": true
61 | },
62 | {
63 | "fieldPath": "pageTypes",
64 | "columnName": "pageTypes",
65 | "affinity": "TEXT",
66 | "notNull": true
67 | },
68 | {
69 | "fieldPath": "numOfPages",
70 | "columnName": "numOfPages",
71 | "affinity": "INTEGER",
72 | "notNull": true
73 | }
74 | ],
75 | "primaryKey": {
76 | "columnNames": [
77 | "id"
78 | ],
79 | "autoGenerate": false
80 | },
81 | "indices": [],
82 | "foreignKeys": []
83 | },
84 | {
85 | "tableName": "ComicBookmark",
86 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`page` INTEGER NOT NULL, `id` INTEGER NOT NULL, `dateOfCreate` TEXT, PRIMARY KEY(`id`, `page`))",
87 | "fields": [
88 | {
89 | "fieldPath": "page",
90 | "columnName": "page",
91 | "affinity": "INTEGER",
92 | "notNull": true
93 | },
94 | {
95 | "fieldPath": "id",
96 | "columnName": "id",
97 | "affinity": "INTEGER",
98 | "notNull": true
99 | },
100 | {
101 | "fieldPath": "dateOfCreate",
102 | "columnName": "dateOfCreate",
103 | "affinity": "TEXT",
104 | "notNull": false
105 | }
106 | ],
107 | "primaryKey": {
108 | "columnNames": [
109 | "id",
110 | "page"
111 | ],
112 | "autoGenerate": false
113 | },
114 | "indices": [],
115 | "foreignKeys": []
116 | }
117 | ],
118 | "views": [],
119 | "setupQueries": [
120 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
121 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5522322ef61d43afac94f7fa9c9c083e')"
122 | ]
123 | }
124 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/github/ttdyce/nhviewer/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer;
2 |
3 | import android.content.Context;
4 | import androidx.test.platform.app.InstrumentationRegistry;
5 | import androidx.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("com.github.ttdyce.nhviewer", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
42 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/ic_launcher_nhviewer-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/ic_launcher_nhviewer-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/CookieStringRequest.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model;
2 |
3 | import androidx.annotation.Nullable;
4 |
5 | import com.android.volley.AuthFailureError;
6 | import com.android.volley.Response;
7 | import com.android.volley.toolbox.StringRequest;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | public class CookieStringRequest extends StringRequest {
13 | public static String challengeCookies;
14 | public static String userAgent;
15 | private Map headers = new HashMap<>();
16 |
17 | public CookieStringRequest(int method, String url, Response.Listener listener, @Nullable Response.ErrorListener errorListener) {
18 | super(method, url, listener, errorListener);
19 | headers.put("cookie", challengeCookies);
20 | headers.put("user-agent", userAgent);
21 | }
22 |
23 | // not in use (as for 8/1/2022)
24 | public CookieStringRequest(String url, Response.Listener listener, @Nullable Response.ErrorListener errorListener) {
25 | super(url, listener, errorListener);
26 | headers.put("cookie", challengeCookies);
27 | headers.put("user-agent", userAgent);
28 | }
29 |
30 | @Override
31 | public Map getHeaders() throws AuthFailureError {
32 | return headers;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/CustomGlideModule.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.android.volley.toolbox.Volley;
8 | import com.bumptech.glide.Glide;
9 | import com.bumptech.glide.GlideBuilder;
10 | import com.bumptech.glide.Registry;
11 | import com.bumptech.glide.annotation.GlideModule;
12 | import com.bumptech.glide.integration.volley.VolleyUrlLoader;
13 | import com.bumptech.glide.load.engine.DiskCacheStrategy;
14 | import com.bumptech.glide.load.model.GlideUrl;
15 | import com.bumptech.glide.module.AppGlideModule;
16 | import com.bumptech.glide.request.RequestOptions;
17 | import com.github.ttdyce.nhviewer.model.proxy.NHVProxyStack;
18 | import com.github.ttdyce.nhviewer.view.MainActivity;
19 |
20 | import java.io.InputStream;
21 |
22 | @GlideModule
23 | public class CustomGlideModule extends AppGlideModule {
24 |
25 | @Override
26 | public void registerComponents(Context context, Glide glide, Registry registry) {
27 | VolleyUrlLoader.Factory factory = new VolleyUrlLoader.Factory(MainActivity.isProxied()
28 | ? Volley.newRequestQueue(context, new NHVProxyStack(MainActivity.proxyHost, MainActivity.proxyPort))
29 | : Volley.newRequestQueue(context)
30 | );
31 | glide.getRegistry().replace(GlideUrl.class, InputStream.class, factory);
32 | }
33 |
34 | @Override
35 | public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
36 | // disable glide/volley disk cache to avoid duplicate data...?
37 | builder.setDefaultRequestOptions(
38 | new RequestOptions()
39 | .diskCacheStrategy(DiskCacheStrategy.NONE)
40 | );
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/MyDistributeListener.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model;
2 |
3 | import android.app.Activity;
4 | import android.content.DialogInterface;
5 | import android.widget.Toast;
6 |
7 | import com.github.ttdyce.nhviewer.R;
8 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
9 | import com.microsoft.appcenter.distribute.Distribute;
10 | import com.microsoft.appcenter.distribute.DistributeListener;
11 | import com.microsoft.appcenter.distribute.ReleaseDetails;
12 | import com.microsoft.appcenter.distribute.UpdateAction;
13 |
14 | public class MyDistributeListener implements DistributeListener {
15 |
16 | @Override
17 | public boolean onReleaseAvailable(Activity activity, ReleaseDetails releaseDetails) {
18 |
19 | // Look at releaseDetails public methods to get version information, release notes text or release notes URL
20 | String versionName = releaseDetails.getShortVersion();
21 | // int versionCode = releaseDetails.getVersion();
22 | String releaseNotes = releaseDetails.getReleaseNotes();
23 | // Uri releaseNotesUrl = releaseDetails.getReleaseNotesUrl();
24 |
25 | // Build our own dialog title and message
26 | new MaterialAlertDialogBuilder(activity, R.style.MaterialDialogTheme)
27 | .setTitle(activity.getString(R.string.new_version_available, versionName))
28 | .setMessage(releaseNotes)
29 | .setPositiveButton(activity.getString(com.microsoft.appcenter.distribute.R.string.appcenter_distribute_update_dialog_download),
30 | new DialogInterface.OnClickListener() {
31 | @Override
32 | public void onClick(DialogInterface dialog, int which) {
33 | Distribute.notifyUpdateAction(UpdateAction.UPDATE);
34 | }
35 | }).setNegativeButton(activity.getString(com.microsoft.appcenter.distribute.R.string.appcenter_distribute_update_dialog_postpone),
36 | new DialogInterface.OnClickListener() {
37 | @Override
38 | public void onClick(DialogInterface dialog, int which) {
39 | Distribute.notifyUpdateAction(UpdateAction.POSTPONE);
40 | }
41 | })
42 | .setOnCancelListener(new DialogInterface.OnCancelListener() {
43 | @Override
44 | public void onCancel(DialogInterface dialog) {
45 | Distribute.notifyUpdateAction(UpdateAction.POSTPONE);
46 | }
47 | })
48 | .create().show();
49 |
50 | // Return true if you're using your own dialog, false otherwise
51 | return true;
52 |
53 |
54 |
55 | }
56 |
57 | @Override
58 | public void onNoReleaseAvailable(Activity activity) {
59 | Toast.makeText(activity.getApplicationContext(), "no updates available", Toast.LENGTH_LONG).show();
60 | }
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/api/NHAPI.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.api;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.util.Log;
6 |
7 | import com.android.volley.Request;
8 | import com.android.volley.RequestQueue;
9 | import com.android.volley.Response;
10 | import com.android.volley.VolleyError;
11 | import com.android.volley.toolbox.Volley;
12 | import com.github.ttdyce.nhviewer.R;
13 | import com.github.ttdyce.nhviewer.model.CookieStringRequest;
14 | import com.github.ttdyce.nhviewer.model.proxy.NHVProxyStack;
15 | import com.github.ttdyce.nhviewer.view.MainActivity;
16 | import com.github.ttdyce.nhviewer.view.SettingsFragment;
17 | import com.google.gson.JsonArray;
18 | import com.google.gson.JsonParser;
19 |
20 | import java.util.Locale;
21 |
22 | public class NHAPI {
23 | private static final String TAG = "NHAPI";
24 | private Context context;
25 | private String proxyHost;
26 | private int proxyPort;
27 | private RequestQueue requestQueue;
28 | private RequestQueue requestQueueProxied;
29 |
30 | public NHAPI(Context context, String proxyHost, int proxyPort) {
31 | this.context = context;
32 | this.proxyHost = proxyHost;
33 | this.proxyPort = proxyPort;
34 | requestQueue = Volley.newRequestQueue(context);
35 | requestQueueProxied = Volley.newRequestQueue(context, new NHVProxyStack(proxyHost, proxyPort));
36 | }
37 |
38 | /*
39 | * Return a JsonArray string containing 25 Comic object, as [ {"id": 284928,"media_id": "1483523",...}, ...]
40 | * */
41 | public void getComicList(String query, int page, PopularType popularType, final ResponseCallback callback, SharedPreferences pref) {
42 | String languageId = pref.getString(MainActivity.KEY_PREF_DEFAULT_LANGUAGE, SettingsFragment.Language.notSet.toString());
43 | int languageIdInt = Integer.parseInt(languageId);
44 |
45 | final String[] languageArray = context.getResources().getStringArray(R.array.key_languages);
46 | String language = languageArray[languageIdInt];
47 |
48 | // choose the RequestQueue.
49 | RequestQueue queue = MainActivity.isProxied() ? requestQueueProxied : requestQueue;
50 | String url = URLs.search("language:" + language + " " + query, page, popularType);
51 | Log.d(TAG, "getComicList: loading from url " + url);
52 | Log.d(TAG, "getComicList: language id = " + languageId);
53 | if (languageIdInt == SettingsFragment.Language.all.getInt() || languageIdInt == SettingsFragment.Language.notSet.getInt())// TODO: 2019/10/1 Function is limited if language = all
54 | url = URLs.getIndex(page);
55 |
56 | while (CookieStringRequest.challengeCookies == null) {
57 | try {
58 | Thread.sleep(300);
59 | } catch (InterruptedException e) {
60 | e.printStackTrace();
61 | Log.d(TAG, "getComicList: CookieStringRequest.challengeCookies is still null!");
62 | }
63 | }
64 |
65 | Log.i(TAG, "getComicList: CookieStringRequest.challengeCookies is ready (SplashScreen/RefreshCookieScreen is ok) ");
66 | // Request a string response from the provided URL.
67 | CookieStringRequest stringRequest = new CookieStringRequest(Request.Method.GET, url,
68 | new Response.Listener() {
69 | @Override
70 | public void onResponse(String response) {
71 | JsonArray result = JsonParser.parseString(response).getAsJsonObject().get("result").getAsJsonArray();
72 | callback.onReponse(result.toString());
73 | }
74 | }, new Response.ErrorListener() {
75 | @Override
76 | public void onErrorResponse(VolleyError error) {
77 | callback.onErrorResponse(error);
78 | }
79 | });
80 |
81 | // Add the request to the RequestQueue.
82 | queue.add(stringRequest);
83 |
84 | }
85 |
86 | public void getComic(int id, final ResponseCallback callback) {
87 | Log.d(TAG, "nhapi: getting comic");
88 | // Get the RequestQueue.
89 | RequestQueue queue = MainActivity.isProxied() ? requestQueueProxied : requestQueue;
90 | String url = URLs.getComic(id);
91 |
92 | while (CookieStringRequest.challengeCookies == null) {
93 | try {
94 | Thread.sleep(300);
95 | } catch (InterruptedException e) {
96 | e.printStackTrace();
97 | Log.d(TAG, "getComic: CookieStringRequest.challengeCookies is still null!");
98 | }
99 | }
100 |
101 | Log.i(TAG, "getComic: CookieStringRequest.challengeCookies is ready (SplashScreen/RefreshCookieScreen is ok) ");
102 |
103 | // Request a string response from the provided URL.
104 | CookieStringRequest stringRequest = new CookieStringRequest(Request.Method.GET, url,
105 | new Response.Listener() {
106 | @Override
107 | public void onResponse(String response) {
108 | Log.d(TAG, "onResponse: got comic");
109 |
110 | callback.onReponse(response);
111 | }
112 | }, new Response.ErrorListener() {
113 | @Override
114 | public void onErrorResponse(VolleyError error) {
115 | callback.onErrorResponse(error);
116 | }
117 | });
118 |
119 | // Add the request to the RequestQueue.
120 | queue.add(stringRequest);
121 | }
122 |
123 | //https://nhentai.net/api/galleries/search?query=language:chinese&page=1&sort=popular
124 | //https://nhentai.net/api/gallery/284987
125 | public static class URLs {
126 | private static String searchPrefix = "https://nhentai.net/api/galleries/search?query=";
127 | private static String getComicPrefix = "https://nhentai.net/api/gallery/";
128 | private static String[] types = {"jpg", "png", "gif", "webp"};
129 |
130 | public static String search(String query, int page, PopularType popularType) {
131 | if (popularType == PopularType.none)
132 | return searchPrefix + query + "&page=" + page;
133 | if (popularType == PopularType.allTime)
134 | return searchPrefix + query + "&page=" + page + "&sort=popular";
135 | if (popularType == PopularType.month)
136 | return searchPrefix + query + "&page=" + page + "&sort=popular-month";
137 | if (popularType == PopularType.week)
138 | return searchPrefix + query + "&page=" + page + "&sort=popular-week";
139 | if (popularType == PopularType.today)
140 | return searchPrefix + query + "&page=" + page + "&sort=popular-today";
141 |
142 | Log.w(TAG, "search: popular-type not found");
143 | return searchPrefix + query + "&page=" + page;// should be not needed
144 | }
145 |
146 | public static String getComic(int id) {
147 | return getComicPrefix + id;
148 | }
149 |
150 | public static String getThumbnail(String mid, String type) {
151 | for (String t : types) {
152 | if (t.charAt(0) == type.charAt(0))
153 | return String.format(Locale.ENGLISH, "https://t1.nhentai.net/galleries/%s/thumb.%s", mid, t);
154 | }
155 |
156 | return "";//should be not needed
157 | }
158 |
159 | public static String getPage(String mid, int page, String type) {
160 | for (String t : types) {
161 | if (t.charAt(0) == type.charAt(0))
162 | return String.format(Locale.ENGLISH, "https://i1.nhentai.net/galleries/%s/%d.%s", mid, page, t);
163 | }
164 |
165 | return "";//should be not needed
166 | }
167 |
168 | public static String getIndex(int page) {
169 | return "https://nhentai.net/api/galleries/all?page=" + page;
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/api/PopularType.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.api;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public enum PopularType {
7 | none, allTime, month, week, today;
8 |
9 | @SuppressWarnings("UnusedAssignment")
10 | public static PopularType get(int which) {
11 | Map typeOptions = new HashMap<>();
12 | int index = 0;
13 | typeOptions.put(index++, none);
14 | typeOptions.put(index++, allTime);
15 | typeOptions.put(index++, month);
16 | typeOptions.put(index++, week);
17 | typeOptions.put(index++, today);
18 |
19 | if(typeOptions.containsKey(which))
20 | return typeOptions.get(which);
21 | else
22 | return none;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/api/ResponseCallback.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.api;
2 |
3 | import android.util.Log;
4 |
5 | import com.android.volley.VolleyError;
6 |
7 | public abstract class ResponseCallback {
8 | public abstract void onReponse(String response);
9 | public void onErrorResponse(VolleyError error){
10 | Log.e("NH ResponseCallback", "onErrorResponse: ", error);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/comic/Comic.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.comic;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import com.google.gson.annotations.SerializedName;
6 |
7 | public class Comic {
8 | private int id;
9 |
10 | @SerializedName("num_pages")
11 | private int numOfPages;
12 |
13 | @SerializedName("num_favorites")
14 | private int numOfFavorites;
15 |
16 | @SerializedName("media_id")
17 | private String mid;
18 |
19 | //included 3 title: eng, jp, pretty
20 | private Title title;
21 | //included 3 images: pages, cover, thumbnail
22 | private Images images;
23 |
24 | @SerializedName("upload_date")
25 | private int uploadDate;
26 | private Tag[] tags;
27 |
28 | public Comic() {
29 | }
30 |
31 | public Comic(int id) {
32 | this.id = id;
33 | }
34 |
35 | public Comic(int id, String mid, Title title, int numOfPages, String[] pageTypes) {
36 | this.id = id;
37 | this.mid = mid;
38 | this.title = title;
39 | this.numOfPages = numOfPages;
40 | this.setPageTypes(pageTypes);
41 | }
42 |
43 | public int getId() {
44 | return id;
45 | }
46 |
47 | public int getNumOfPages() {
48 | return numOfPages;
49 | }
50 |
51 | public int getNumOfFavorites() {
52 | return numOfFavorites;
53 | }
54 |
55 | public String getMid() {
56 | return mid;
57 | }
58 |
59 | public Title getTitle() {
60 | return title;
61 | }
62 |
63 | public Images getImages() {
64 | return images;
65 | }
66 |
67 | public int getUploadDate() {
68 | return uploadDate;
69 | }
70 |
71 | public Tag[] getTags() {
72 | return tags;
73 | }
74 |
75 | public String[] getPageTypes() {
76 | String[] types = new String[numOfPages];
77 | Image[] pages = getImages().getPages();
78 |
79 | for (int i = 0; i < types.length; i++) {
80 | types[i] = pages[i].getType();
81 | }
82 |
83 | return types;
84 | }
85 |
86 | public void setPageTypes(String [] pageTypes) {
87 | if(getImages() == null)
88 | images = new Images();
89 |
90 | Images images = getImages();
91 | Image[] pages = images.getPages();
92 | Image thumbnail = images.getThumbnail();
93 |
94 | for (int i = 0; i < pageTypes.length; i++) {
95 | pages[i].type = pageTypes[i];
96 | }
97 | // TODO: 2019/9/28 Hardcoded thumbnail type, using first page of inner page type
98 | thumbnail.type = pageTypes[0];
99 | }
100 |
101 | /*Inner classes*/
102 |
103 | //Comic Title
104 | public static class Title {
105 | String english, japanese, pretty;
106 |
107 | public Title(String english) {
108 | this.english = english;
109 | }
110 |
111 | @NonNull
112 | @Override
113 | public String toString() {
114 | return english;
115 | }
116 | }
117 |
118 | public class Images {
119 | Image[] pages;
120 | Image cover;
121 | Image thumbnail;
122 |
123 | public Images() {
124 | pages = new Image[numOfPages];
125 | for (int i = 0; i < pages.length; i++)
126 | pages[i] = new Image();
127 |
128 | cover = new Image();
129 | thumbnail = new Image();
130 | }
131 |
132 | public Image[] getPages() {
133 | return pages;
134 | }
135 |
136 | public Image getCover() {
137 | return cover;
138 | }
139 |
140 | public Image getThumbnail() {
141 | return thumbnail;
142 | }
143 | }
144 |
145 | //Comic Image
146 | public class Image {
147 | @SerializedName("t")
148 | String type;
149 | @SerializedName("w")
150 | int width;
151 | @SerializedName("h")
152 | int height;
153 |
154 | public Image(String type, int width, int height) {
155 | this.type = type;
156 | this.width = width;
157 | this.height = height;
158 | }
159 |
160 | public Image() {
161 |
162 | }
163 |
164 | public String getType() {
165 | return type;
166 | }
167 |
168 | public int getWidth() {
169 | return width;
170 | }
171 |
172 | public int getHeight() {
173 | return height;
174 | }
175 | }
176 |
177 | //Comic Tag
178 | public class Tag {
179 | int id, count;
180 | String type, name, url;
181 |
182 | public Tag(int id, int count, String type, String name, String url) {
183 | this.id = id;
184 | this.count = count;
185 | this.type = type;
186 | this.name = name;
187 | this.url = url;
188 | }
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/comic/ComicCollection.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.comic;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class ComicCollection {
7 | private String name;
8 | private List comicList;
9 |
10 | public ComicCollection(String name, List comicList) {
11 | this.name = name;
12 | this.comicList = comicList;
13 | }
14 |
15 | public ComicCollection(List ids, String name) {
16 | this.name = name;
17 | this.comicList = new ArrayList<>();
18 | for (int id :ids) {
19 | if(id != -1)
20 | //there is at least a comic with id -1 in a empty collection
21 | comicList.add(new Comic(id));
22 | }
23 | }
24 |
25 | public String getName() {
26 | return name;
27 | }
28 |
29 | public void setName(String name) {
30 | this.name = name;
31 | }
32 |
33 | public List getComicList() {
34 | return comicList;
35 | }
36 |
37 | public void setComicList(List comicList) {
38 | this.comicList = comicList;
39 | }
40 |
41 | public int getComicCount(){
42 | return comicList.size() ;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/comic/factory/ComicFactory.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.comic.factory;
2 |
3 | import com.github.ttdyce.nhviewer.model.api.PopularType;
4 |
5 | public interface ComicFactory {
6 | void requestComicList();
7 |
8 | void setPage(int page);
9 | void setSortBy(PopularType sortBy);
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/comic/factory/DBComicFactory.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.comic.factory;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import com.github.ttdyce.nhviewer.model.api.PopularType;
6 | import com.github.ttdyce.nhviewer.model.api.ResponseCallback;
7 | import com.github.ttdyce.nhviewer.model.comic.Comic;
8 | import com.github.ttdyce.nhviewer.model.room.AppDatabase;
9 | import com.github.ttdyce.nhviewer.model.room.ComicCachedDao;
10 | import com.github.ttdyce.nhviewer.model.room.ComicCachedEntity;
11 | import com.github.ttdyce.nhviewer.model.room.ComicCollectionDao;
12 | import com.github.ttdyce.nhviewer.model.room.ComicCollectionEntity;
13 | import com.google.gson.Gson;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | public class DBComicFactory implements ComicFactory {
19 | public static final int SORT_BY_DEFAULT = 0;
20 | public static final int SORT_BY_COLLECTION_DATE = 1;
21 | public static final int SORT_BY_COLLECTION_DATE_DESC = -1;
22 |
23 | private String collectionName;
24 | private AppDatabase db;
25 | private int page;
26 | private boolean sortedPopular;
27 | private ResponseCallback callback;
28 |
29 | public DBComicFactory(String collectionName, AppDatabase db, int page, boolean sortedPopular, ResponseCallback callback) {
30 | this.collectionName = collectionName;
31 | this.db = db;
32 | this.page = page;
33 | this.sortedPopular = sortedPopular;
34 | this.callback = callback;
35 | }
36 |
37 | @Override
38 | public void requestComicList() {
39 | new DisplayCollectionTask(db, collectionName, callback).execute();
40 | }
41 |
42 | @Override
43 | public void setPage(int page) {
44 | this.page = page;
45 | }
46 |
47 | @Override
48 | public void setSortBy(PopularType sortBy) {
49 | // TODO: 2019/9/28 Not yet supported sorting in Collection
50 | }
51 |
52 | private static class DisplayCollectionTask extends AsyncTask{
53 |
54 | private AppDatabase db;
55 | private String collectionName;
56 | private ResponseCallback callback;
57 |
58 | public DisplayCollectionTask(AppDatabase db, String collectionName, ResponseCallback callback) {
59 | this.db = db;
60 | this.collectionName = collectionName;
61 | this.callback = callback;
62 | }
63 |
64 | @Override
65 | protected String doInBackground(Void... voids) {
66 |
67 | ComicCachedDao cachedDao = db.comicCachedDao();
68 | ComicCollectionDao collectionDao = db.comicCollectionDao();
69 | List ids = new ArrayList<>();
70 | List comics = new ArrayList<>();
71 |
72 | for (ComicCollectionEntity entity:collectionDao.getAllByName(collectionName))
73 | ids.add(entity.getId());
74 |
75 | for (int id : ids) {
76 | ComicCachedEntity entity = cachedDao.findById(id);
77 | if(entity == null)
78 | continue;
79 | int numOfPages = entity.getNumOfPages();
80 | String mid = entity.getMid();
81 | Comic.Title title = new Comic.Title(entity.getTitle());
82 | String[] pageTypes = entity.getPageTypes().split("(?!^)");
83 | comics.add(new Comic(id, mid, title, numOfPages, pageTypes));
84 | }
85 |
86 | //create api-like json object
87 | Gson gson = new Gson();
88 |
89 | return gson.toJson(comics);
90 | }
91 |
92 | @Override
93 | protected void onPostExecute(String result) {
94 | super.onPostExecute(result);
95 |
96 | callback.onReponse(result);
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/comic/factory/NHApiComicFactory.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.comic.factory;
2 |
3 | import android.content.SharedPreferences;
4 |
5 | import com.github.ttdyce.nhviewer.model.api.NHAPI;
6 | import com.github.ttdyce.nhviewer.model.api.PopularType;
7 | import com.github.ttdyce.nhviewer.model.api.ResponseCallback;
8 |
9 | public class NHApiComicFactory implements ComicFactory {
10 | private NHAPI nhapi;
11 | private String query;
12 | private int page;
13 | private PopularType popularType;
14 | private ResponseCallback callback;
15 | private final SharedPreferences pref;
16 |
17 | public NHApiComicFactory(NHAPI nhapi, String query, int page, PopularType popularType, ResponseCallback callback, SharedPreferences pref) {
18 | this.nhapi = nhapi;
19 | this.query = query;
20 | this.page = page;
21 | this.popularType = popularType;
22 | this.callback = callback;
23 | this.pref = pref;
24 | }
25 |
26 | @Override
27 | public void requestComicList() {
28 | nhapi.getComicList(query, page, popularType, callback, pref);
29 |
30 | }
31 |
32 | @Override
33 | public void setPage(int page) {
34 | this.page = page;
35 | }
36 |
37 | @Override
38 | public void setSortBy(PopularType popularType) {
39 | this.popularType = popularType;
40 | }
41 |
42 | public static void getComicById(NHAPI api, int id, ResponseCallback callback){
43 | api.getComic(id, callback);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/proxy/NHVProxyStack.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.proxy;
2 |
3 | import com.android.volley.toolbox.HurlStack;
4 |
5 | import java.io.IOException;
6 | import java.net.HttpURLConnection;
7 | import java.net.InetSocketAddress;
8 | import java.net.Proxy;
9 | import java.net.URL;
10 |
11 | public class NHVProxyStack extends HurlStack {
12 | private String proxyHost;
13 | private int proxyPort;
14 |
15 | public NHVProxyStack(String proxyHost, int proxyPort) {
16 | super();
17 | this.proxyHost = proxyHost;
18 | this.proxyPort = proxyPort;
19 | }
20 |
21 | @Override
22 | protected HttpURLConnection createConnection(URL url) throws IOException {
23 | // Start the connection by specifying a proxy server
24 | Proxy proxy = new Proxy(Proxy.Type.HTTP,
25 | InetSocketAddress.createUnresolved(proxyHost, proxyPort ));//proxy server
26 | return (HttpURLConnection) url
27 | .openConnection(proxy);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/room/AppDatabase.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.room;
2 |
3 | import androidx.room.Database;
4 | import androidx.room.RoomDatabase;
5 | import androidx.room.TypeConverters;
6 | import androidx.room.migration.Migration;
7 | import androidx.sqlite.db.SupportSQLiteDatabase;
8 |
9 | @Database(entities = {ComicCollectionEntity.class, ComicCachedEntity.class, ComicBookmarkEntity.class}
10 | , version = 2)
11 | @TypeConverters({DateConverter.class})
12 | public abstract class AppDatabase extends RoomDatabase {
13 | public static final String COL_COLLECTION_HISTORY = "History";
14 | public static final String COL_COLLECTION_FAVORITE = "Favorite";
15 | public static final String COL_COLLECTION_NEXT = "Next";
16 | public static final String DB_NAME = "Nhviewer";
17 |
18 | public abstract ComicCollectionDao comicCollectionDao();
19 |
20 | public abstract ComicCachedDao comicCachedDao();
21 |
22 | public abstract ComicBookmarkDao comicBookmarkDao();
23 |
24 | public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
25 | @Override
26 | public void migrate(SupportSQLiteDatabase database) {
27 | // add comic bookmark table
28 | database.execSQL("CREATE TABLE IF NOT EXISTS `ComicBookmark` (`page` INTEGER NOT NULL, `id` INTEGER NOT NULL, `dateOfCreate` TEXT, PRIMARY KEY(`id`, `page`))");
29 | }
30 | };
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/room/ComicBookmarkDao.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.room;
2 |
3 | import androidx.room.Dao;
4 | import androidx.room.Delete;
5 | import androidx.room.Insert;
6 | import androidx.room.Query;
7 | import androidx.room.Update;
8 |
9 | import java.util.List;
10 |
11 | @Dao
12 | public interface ComicBookmarkDao {
13 | String ORDER_BY_DEFAULT = "dateOfCreate DESC";
14 |
15 | @Query("SELECT * FROM ComicBookmark WHERE id = :id ORDER BY " + ORDER_BY_DEFAULT)
16 | List getById(int id);
17 |
18 | @Query("SELECT * FROM ComicBookmark ORDER BY " + ORDER_BY_DEFAULT)
19 | List getAll();
20 |
21 | @Query("SELECT count(*) == 0 FROM ComicBookmark Where id = :id AND page = :page")
22 | boolean notExist(int id, int page);
23 |
24 | @Insert
25 | void insertAll(ComicBookmarkEntity... comicBookmark);
26 |
27 | @Insert
28 | void insert(ComicBookmarkEntity comicBookmark);
29 |
30 | @Delete
31 | void delete(ComicBookmarkEntity comicBookmark);
32 |
33 | @Update
34 | void update(ComicBookmarkEntity comicBookmark);
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/room/ComicBookmarkEntity.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.room;
2 |
3 | import androidx.room.Entity;
4 |
5 | import java.util.Date;
6 |
7 | @Entity(tableName = "ComicBookmark", primaryKeys = {"id", "page"})
8 | public class ComicBookmarkEntity {
9 |
10 | private int page;
11 | private int id;
12 | private Date dateOfCreate;
13 |
14 | public ComicBookmarkEntity(int page, int id) {
15 | this.page = page;
16 | this.id = id;
17 | this.dateOfCreate = new Date();
18 | }
19 |
20 | // Room uses this factory method to create ComicCollectionEntity objects.
21 | public static ComicBookmarkEntity create(int page, int id) {
22 | return new ComicBookmarkEntity(page, id);
23 | }
24 |
25 | public int getPage() {
26 | return page;
27 | }
28 |
29 | public void setPage(int page) {
30 | this.page = page;
31 | }
32 |
33 | public int getId() {
34 | return id;
35 | }
36 |
37 | public void setId(int id) {
38 | this.id = id;
39 | }
40 |
41 | public Date getDateOfCreate() {
42 | return dateOfCreate;
43 | }
44 |
45 | public void setDateOfCreate(Date dateOfCreate) {
46 | this.dateOfCreate = dateOfCreate;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/room/ComicCachedDao.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.room;
2 |
3 | import androidx.room.Dao;
4 | import androidx.room.Delete;
5 | import androidx.room.Insert;
6 | import androidx.room.Query;
7 |
8 | import java.util.List;
9 |
10 | @Dao
11 | public interface ComicCachedDao {
12 |
13 | @Query("SELECT * FROM ComicCached")
14 | List getAll();
15 |
16 | @Query("SELECT * FROM ComicCached WHERE id = :id")
17 | ComicCachedEntity findById(int id);
18 |
19 | @Query("SELECT * FROM ComicCached WHERE id In(:ids)")
20 | List findById(List ids);
21 |
22 | @Query("SELECT count(*) == 0 FROM ComicCached Where id = :id")
23 | boolean notExist(int id);
24 |
25 | @Insert
26 | void insertAll(ComicCachedEntity... comic);
27 |
28 | @Insert
29 | void insert(ComicCachedEntity comic);
30 |
31 | @Delete
32 | void delete(ComicCachedEntity comic);
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/room/ComicCachedEntity.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.room;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.room.Entity;
5 |
6 | import com.google.gson.Gson;
7 |
8 | import java.io.Serializable;
9 |
10 | @Entity(tableName = "ComicCached", primaryKeys = {"id"})
11 | public class ComicCachedEntity implements Serializable {
12 | private static final long serialVersionUID = 3001880502226771220L;
13 | @NonNull
14 | private int id;
15 | @NonNull
16 | private String mid;
17 | @NonNull
18 | private String title;
19 | @NonNull
20 | private String pageTypes;
21 | @NonNull
22 | private int numOfPages;
23 |
24 | public ComicCachedEntity(int id, @NonNull String mid, @NonNull String title, @NonNull String pageTypes, int numOfPages) {
25 | this.id = id;
26 | this.mid = mid;
27 | this.title = title;
28 | this.pageTypes = pageTypes;
29 | this.numOfPages = numOfPages;
30 | }
31 |
32 | // Room uses this factory method to create ComicCollectionEntity objects.
33 | public static ComicCachedEntity create(int id, String mid, String title, String pageTypes, int numOfPages) {
34 | return new ComicCachedEntity(id, mid, title, pageTypes, numOfPages);
35 | }
36 |
37 | public int getId() {
38 | return id;
39 | }
40 |
41 | public void setId(int id) {
42 | this.id = id;
43 | }
44 |
45 | @NonNull
46 | public String getMid() {
47 | return mid;
48 | }
49 |
50 | public void setMid(@NonNull String mid) {
51 | this.mid = mid;
52 | }
53 |
54 | @NonNull
55 | public String getTitle() {
56 | return title;
57 | }
58 |
59 | public void setTitle(@NonNull String title) {
60 | this.title = title;
61 | }
62 |
63 | @NonNull
64 | public String getPageTypes() {
65 | return pageTypes;
66 | }
67 |
68 | public void setPageTypes(@NonNull String pageTypes) {
69 | this.pageTypes = pageTypes;
70 | }
71 |
72 | public int getNumOfPages() {
73 | return numOfPages;
74 | }
75 |
76 | public void setNumOfPages(int numOfPages) {
77 | this.numOfPages = numOfPages;
78 | }
79 |
80 | public String toJson() {
81 | Gson gson = new Gson();
82 | return gson.toJson(this);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/room/ComicCollectionDao.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.room;
2 |
3 | import androidx.room.Dao;
4 | import androidx.room.Delete;
5 | import androidx.room.Insert;
6 | import androidx.room.Query;
7 | import androidx.room.Update;
8 |
9 | import java.util.List;
10 |
11 | @Dao
12 | public interface ComicCollectionDao {
13 | String ORDER_BY_DEFAULT = "dateCreated DESC";
14 |
15 | @Query("SELECT * FROM ComicCollection ORDER BY " + ORDER_BY_DEFAULT)
16 | List getAll();
17 |
18 | @Query("SELECT * FROM ComicCollection WHERE name = :name ORDER BY " + ORDER_BY_DEFAULT)
19 | List getAllByName(String name);
20 |
21 | @Query("SELECT * FROM ComicCollection WHERE name = :name ORDER BY " + ORDER_BY_DEFAULT)
22 | ComicCollectionEntity findByName(String name);
23 |
24 | @Query("SELECT count(*) == 0 FROM ComicCollection Where id = :id AND name = :collectionName")
25 | boolean notExist(String collectionName, int id);
26 |
27 | @Insert
28 | void insertAll(ComicCollectionEntity... comicCollection);
29 |
30 | @Insert
31 | void insert(ComicCollectionEntity comicCollection);
32 |
33 | @Delete
34 | void delete(ComicCollectionEntity comicCollection);
35 |
36 | @Update
37 | void update(ComicCollectionEntity comicCollection);
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/room/ComicCollectionEntity.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.room;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.room.Entity;
5 |
6 | import com.google.gson.Gson;
7 |
8 | import java.io.Serializable;
9 | import java.util.Date;
10 |
11 | @Entity(tableName = "ComicCollection", primaryKeys = {"name", "id"})
12 | public class ComicCollectionEntity implements Serializable {
13 | private static final long serialVersionUID = 3001880502226771220L;
14 | @NonNull
15 | private String name;
16 |
17 | @NonNull
18 | private int id;
19 |
20 | @NonNull
21 | private Date dateCreated;
22 |
23 | public ComicCollectionEntity(@NonNull String name, @NonNull int id, @NonNull Date dateCreated) {
24 | this.name = name;
25 | this.id = id;
26 | this.dateCreated = dateCreated;
27 | }
28 |
29 | // Room uses this factory method to create ComicCollectionEntity objects.
30 | public static ComicCollectionEntity create(String name, int id, Date dateCreated) {
31 | return new ComicCollectionEntity(name, id, dateCreated);
32 | }
33 |
34 | @NonNull
35 | public String getName() {
36 | return name;
37 | }
38 |
39 | public void setName(@NonNull String name) {
40 | this.name = name;
41 | }
42 |
43 | @NonNull
44 | public int getId() {
45 | return id;
46 | }
47 |
48 | public void setId(@NonNull int id) {
49 | this.id = id;
50 | }
51 |
52 | @NonNull
53 | public Date getDateCreated() {
54 | return dateCreated;
55 | }
56 |
57 | public void setDateCreated(@NonNull Date dateCreated) {
58 | this.dateCreated = dateCreated;
59 | }
60 |
61 | public String toJson(){
62 | Gson gson = new Gson();
63 | return gson.toJson(this);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/model/room/DateConverter.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.model.room;
2 |
3 | import androidx.room.TypeConverter;
4 |
5 | import java.text.DateFormat;
6 | import java.text.ParseException;
7 | import java.text.SimpleDateFormat;
8 | import java.util.Date;
9 | import java.util.Locale;
10 |
11 | class DateConverter {
12 | static DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
13 |
14 | @TypeConverter
15 | public static Date fromDate(String value) {
16 | if (value != null) {
17 | try {
18 | return df.parse(value);
19 | } catch (ParseException e) {
20 | e.printStackTrace();
21 | }
22 | return null;
23 | } else {
24 | return null;
25 | }
26 | }
27 |
28 | @TypeConverter
29 | public static String dateToString(Date value) {
30 | if (value != null) {
31 | return df.format(value);
32 | } else {
33 | return null;
34 | }
35 | }
36 |
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/presenter/ComicCollectionPresenter.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.presenter;
2 |
3 | import android.content.Context;
4 | import android.os.AsyncTask;
5 | import android.os.Bundle;
6 | import android.util.Log;
7 | import android.view.ViewGroup;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.navigation.NavController;
11 | import androidx.recyclerview.widget.RecyclerView;
12 |
13 | import com.github.ttdyce.nhviewer.R;
14 | import com.github.ttdyce.nhviewer.model.api.NHAPI;
15 | import com.github.ttdyce.nhviewer.model.api.ResponseCallback;
16 | import com.github.ttdyce.nhviewer.model.comic.Comic;
17 | import com.github.ttdyce.nhviewer.model.comic.ComicCollection;
18 | import com.github.ttdyce.nhviewer.model.room.AppDatabase;
19 | import com.github.ttdyce.nhviewer.model.room.ComicCollectionDao;
20 | import com.github.ttdyce.nhviewer.model.room.ComicCollectionEntity;
21 | import com.github.ttdyce.nhviewer.view.ComicCollectionViewHolder;
22 | import com.github.ttdyce.nhviewer.view.MainActivity;
23 | import com.google.gson.Gson;
24 | import com.google.gson.JsonObject;
25 | import com.google.gson.JsonParser;
26 |
27 | import java.util.ArrayList;
28 | import java.util.Collections;
29 | import java.util.HashMap;
30 | import java.util.List;
31 | import java.util.Map;
32 |
33 | public class ComicCollectionPresenter {
34 | private ComicCollectionView comicCollectionView;
35 | private AppDatabase db;
36 | private ComicCollectionAdapter adapter;
37 | private NavController navController;
38 |
39 | public ComicCollectionPresenter(ComicCollectionView view, NavController navController) {
40 | this.comicCollectionView = view;
41 | this.db = MainActivity.getAppDatabase();
42 | this.adapter = new ComicCollectionAdapter();
43 | this.navController = navController;
44 |
45 | new LoadComicCollectionTask(db, view, adapter).execute();
46 |
47 | }
48 |
49 | public ComicCollectionAdapter getAdapter() {
50 | return adapter;
51 | }
52 |
53 | public void onItemClick(int position) {
54 | String collectionName = adapter.get(position).getName();
55 |
56 | Bundle bundle = new Bundle();
57 | bundle.putString("collectionName", collectionName);
58 |
59 | navController.navigate(R.id.comicListFragment, bundle);
60 | }
61 |
62 | private class ComicCollectionAdapter extends RecyclerView.Adapter {
63 | private ArrayList comicCollections = new ArrayList<>();
64 |
65 | @NonNull
66 | @Override
67 | public ComicCollectionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
68 | return comicCollectionView.onCreateViewHolder(parent, viewType);
69 | }
70 |
71 | @Override
72 | public void onBindViewHolder(@NonNull final ComicCollectionViewHolder holder, final int position) {
73 | ComicCollection cc = comicCollections.get(position);
74 | final String name = cc.getName();
75 | final int numOfComics = cc.getComicCount();
76 |
77 | if (numOfComics != 0) {
78 | Comic latestComic = cc.getComicList().get(0);
79 | NHAPI nhapi = new NHAPI(comicCollectionView.getContext(), MainActivity.proxyHost, MainActivity.proxyPort);
80 | ResponseCallback callback = new ResponseCallback() {
81 | @Override
82 | public void onReponse(String response) {
83 | JsonObject obj = JsonParser.parseString(response).getAsJsonObject();
84 | Gson gson = new Gson();
85 | Comic c = gson.fromJson(obj, Comic.class);
86 | final String thumbUrl = NHAPI.URLs.getThumbnail(c.getMid(), c.getImages().getThumbnail().getType());
87 |
88 | if (c.getId() != -1)//id -1 is for empty comic collection
89 | comicCollectionView.onBindViewHolder(holder, position, name, thumbUrl, numOfComics);
90 | }
91 | };
92 |
93 | nhapi.getComic(latestComic.getId(), callback);
94 | }
95 |
96 | comicCollectionView.onBindViewHolder(holder, position, name, "", numOfComics);
97 | }
98 |
99 | @Override
100 | public int getItemCount() {
101 | return comicCollections.size();
102 | }
103 |
104 | public void add(ComicCollection cc) {
105 | comicCollections.add(cc);
106 | }
107 |
108 | public void clear() {
109 | comicCollections.clear();
110 | }
111 |
112 | public ComicCollection get(int position) {
113 | return comicCollections.get(position);
114 | }
115 | }
116 |
117 | public interface ComicCollectionView {
118 |
119 | ComicCollectionViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
120 |
121 | void onBindViewHolder(ComicCollectionViewHolder holder, int position, String name, String thumbUrl, int numOfPages);
122 |
123 | void updateList();
124 |
125 | Context getContext();
126 |
127 | }
128 |
129 |
130 | private static class LoadComicCollectionTask extends AsyncTask {
131 | private AppDatabase db;
132 | private ComicCollectionView view;
133 | private ComicCollectionAdapter adapter;
134 |
135 | public LoadComicCollectionTask(AppDatabase db, ComicCollectionView view, ComicCollectionAdapter adapter) {
136 | this.db = db;
137 | this.view = view;
138 | this.adapter = adapter;
139 | }
140 |
141 | protected Void doInBackground(Void... voids) {
142 | ComicCollectionDao dao = db.comicCollectionDao();
143 | List entities = dao.getAll();
144 | HashMap> comicCollections = new HashMap<>();
145 |
146 |
147 | for (ComicCollectionEntity e : entities) {
148 | final String name = e.getName();
149 | int id = e.getId();
150 |
151 | if (comicCollections.get(name) == null)
152 | comicCollections.put(name, new ArrayList<>(Collections.singletonList(id)));
153 | else {
154 | List ids = comicCollections.get(name);
155 | ids.add(id);
156 | comicCollections.put(name, ids);
157 | }
158 | }
159 |
160 | for (Map.Entry> entry : comicCollections.entrySet()) {
161 | String name = entry.getKey();
162 | List ids = entry.getValue();
163 |
164 | adapter.add(new ComicCollection(ids, name));
165 | }
166 | return null;
167 | }
168 |
169 | protected void onPostExecute(Void result) {
170 | Log.i("asyncTask", "onPostExecute: Finished task");
171 | view.updateList();
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/ComicActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.content.SharedPreferences;
4 | import android.graphics.Color;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 | import android.preference.PreferenceManager;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.ProgressBar;
12 |
13 | import androidx.appcompat.app.AppCompatActivity;
14 | import androidx.appcompat.widget.Toolbar;
15 | import androidx.recyclerview.widget.LinearLayoutManager;
16 | import androidx.recyclerview.widget.RecyclerView;
17 | import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
18 |
19 | import com.bumptech.glide.Glide;
20 | import com.bumptech.glide.request.RequestOptions;
21 | import com.github.ttdyce.nhviewer.R;
22 | import com.github.ttdyce.nhviewer.presenter.ComicPresenter;
23 | import com.github.ttdyce.nhviewer.view.component.ZoomRecyclerView;
24 |
25 | import jp.wasabeef.glide.transformations.BlurTransformation;
26 |
27 | public class ComicActivity extends AppCompatActivity implements ComicPresenter.ComicView {
28 | private int id;
29 | private String mid;
30 | private String title;
31 | private int numOfPages;
32 | private String[] pageTypes;
33 | private ComicPresenter presenter;
34 |
35 | private RecyclerView rvComic;
36 | private ProgressBar pbComic;
37 | private LinearLayoutManager layoutManager;
38 | private CircularProgressDrawable circularProgressDrawable;
39 | private int lastVisibleItemPosition;
40 |
41 | @Override
42 | protected void onCreate(Bundle savedInstanceState) {
43 | super.onCreate(savedInstanceState);
44 | setContentView(R.layout.activity_comic);
45 | init();
46 | }
47 |
48 | @Override
49 | protected void onStop() {
50 | //save this comic to history
51 | presenter.onStop();
52 |
53 | super.onStop();
54 | }
55 |
56 |
57 | @Override
58 | public boolean onSupportNavigateUp() {
59 | onBackPressed();
60 | return true;
61 | }
62 |
63 | private int getComicIdFromBrowser() {
64 | int comicid = -1;
65 | Uri browserData = getIntent().getData();//data from browser, contains only comicid (from url)
66 |
67 | if (browserData != null && browserData.isHierarchical()) {//using id from browser
68 | comicid = Integer.parseInt(browserData.getLastPathSegment());
69 | }
70 |
71 | return comicid;
72 | }
73 |
74 |
75 | private void init() {
76 | if (getIntent().getExtras() == null)
77 | return;
78 | final Bundle extras = getIntent().getExtras();
79 | final int idFromBrowser = getComicIdFromBrowser();
80 | rvComic = findViewById(R.id.rvComic);
81 | pbComic = findViewById(R.id.pbComic);
82 |
83 | layoutManager = (LinearLayoutManager)rvComic.getLayoutManager();
84 | final ZoomRecyclerView rvComic = findViewById(R.id.rvComic);
85 | rvComic.setEnableScale(true);
86 | rvComic.setHasFixedSize(true);
87 | initOnScrollListener();
88 |
89 | presenter = ComicPresenter.factory(this, this, extras, idFromBrowser, rvComic);
90 |
91 | //set appbar
92 | Toolbar toolbar = findViewById(R.id.toolbar_comic);
93 | setSupportActionBar(toolbar);
94 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
95 | getSupportActionBar().setDisplayShowHomeEnabled(true);
96 | }
97 |
98 | private void initOnScrollListener() {
99 | //Remember Last page
100 | final LinearLayoutManager layoutManager = (LinearLayoutManager) rvComic.getLayoutManager();
101 | rvComic.addOnScrollListener(new RecyclerView.OnScrollListener() {
102 | @Override
103 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {//dy > 0 = move down, dy < 0 = move up
104 | super.onScrolled(recyclerView, dx, dy);
105 |
106 | if (layoutManager.findFirstCompletelyVisibleItemPosition() == -1)
107 | return;
108 |
109 | lastVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition() + 1;
110 | int itemCount = layoutManager.getItemCount() - 1;
111 |
112 | }
113 | });
114 | }
115 |
116 | @Override
117 | public ComicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
118 | View v = LayoutInflater.from(parent.getContext())
119 | .inflate(R.layout.item_comic, parent, false);
120 | return new ComicViewHolder(v);
121 | }
122 |
123 | @Override
124 | public void onBindViewHolder(ComicViewHolder holder, int position, String url) {
125 | final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
126 |
127 |
128 | circularProgressDrawable = new CircularProgressDrawable(this);// TODO: 2019/10/27 many drawable object is created, may hurt performance & loading speed
129 | circularProgressDrawable.setStrokeWidth(10f);
130 | circularProgressDrawable.setCenterRadius(30f);
131 | circularProgressDrawable.setColorSchemeColors(Color.WHITE); // stroke color, found in CircularProgressDrawable comment
132 | circularProgressDrawable.start();
133 |
134 | //determine blur image or not
135 | if (pref.getBoolean(MainActivity.KEY_PREF_DEMO_MODE, false))
136 | Glide.with(this)
137 | .load(url)
138 | .placeholder(circularProgressDrawable)
139 | .apply(RequestOptions.bitmapTransform(new BlurTransformation(16, 5)))
140 | .into(holder.ivComicPage);
141 | else
142 | Glide.with(this)
143 | .load(url)
144 | .placeholder(circularProgressDrawable)
145 | .into(holder.ivComicPage);
146 |
147 | holder.tvComicPage.setText(String.valueOf(position + 1));
148 |
149 | int pos = layoutManager.findLastVisibleItemPosition();
150 | pbComic.setProgress(100 * pos / layoutManager.getItemCount());
151 | }
152 |
153 | @Override
154 | public int getLastVisibleItemPosition() {
155 | return lastVisibleItemPosition;
156 | }
157 |
158 | @Override
159 | public View getRootView() {
160 | return rvComic.getRootView();
161 | }
162 |
163 | @Override
164 | public RecyclerView getRVComic() {
165 | return rvComic;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/ComicCollectionFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.content.SharedPreferences;
4 | import android.graphics.drawable.ColorDrawable;
5 | import android.os.Bundle;
6 | import android.preference.PreferenceManager;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.TextView;
11 |
12 | import androidx.annotation.NonNull;
13 | import androidx.annotation.Nullable;
14 | import androidx.core.content.ContextCompat;
15 | import androidx.core.widget.ContentLoadingProgressBar;
16 | import androidx.fragment.app.Fragment;
17 | import androidx.navigation.Navigation;
18 | import androidx.recyclerview.widget.GridLayoutManager;
19 | import androidx.recyclerview.widget.RecyclerView;
20 |
21 | import com.bumptech.glide.Glide;
22 | import com.bumptech.glide.request.RequestOptions;
23 | import com.github.ttdyce.nhviewer.R;
24 | import com.github.ttdyce.nhviewer.presenter.ComicCollectionPresenter;
25 |
26 | import java.util.Locale;
27 |
28 | import jp.wasabeef.glide.transformations.BlurTransformation;
29 |
30 |
31 | public class ComicCollectionFragment extends Fragment implements ComicCollectionPresenter.ComicCollectionView {
32 | private RecyclerView rvComicList;
33 | private ComicCollectionPresenter presenter;
34 | private ContentLoadingProgressBar pbComicList;
35 | private TextView tvComicListDesc;
36 |
37 |
38 | @Override
39 | public void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 |
42 | }
43 |
44 | @Override
45 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
46 | Bundle savedInstanceState) {
47 | // Inflate the layout for this fragment
48 | return inflater.inflate(R.layout.fragment_comic_list, container, false);
49 |
50 | }
51 |
52 | @Override
53 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
54 | super.onViewCreated(view, savedInstanceState);
55 |
56 | presenter = new ComicCollectionPresenter(this, Navigation.findNavController(view));
57 | GridLayoutManager layoutManager = new GridLayoutManager(requireActivity(), 3);
58 | rvComicList = view.findViewById(R.id.rvComicList);
59 | pbComicList = view.findViewById(R.id.pbComicList);
60 | tvComicListDesc = view.findViewById(R.id.tvComicListDesc);
61 |
62 | tvComicListDesc.setText("Loading collection list...");
63 |
64 | rvComicList.setHasFixedSize(true);
65 | rvComicList.setAdapter(presenter.getAdapter());
66 | rvComicList.setLayoutManager(layoutManager);
67 | }
68 |
69 | @Override
70 | public ComicCollectionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
71 | View v = LayoutInflater.from(parent.getContext())
72 | .inflate(R.layout.item_comic_collection_list, parent, false);
73 | return new ComicCollectionViewHolder(v);
74 | }
75 |
76 | @Override
77 | public void onBindViewHolder(ComicCollectionViewHolder holder, final int position, String name, String thumbUrl, int numOfPages) {
78 | final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(requireContext());
79 |
80 | holder.tvNumOfComics.setText(String.format(Locale.ENGLISH, "%d collected", numOfPages));
81 | holder.tvTitle.setText(name);
82 |
83 | //determine blur image or not
84 | if (pref.getBoolean(MainActivity.KEY_PREF_DEMO_MODE, false))
85 | Glide.with(this)
86 | .load(thumbUrl)
87 | .placeholder(new ColorDrawable(ContextCompat.getColor(requireContext(), R.color.secondaryColor)))
88 | .apply(RequestOptions.bitmapTransform(new BlurTransformation(16, 5)))
89 | .into(holder.ivThumb);
90 | else
91 | Glide.with(this)
92 | .load(thumbUrl)
93 | .placeholder(new ColorDrawable(ContextCompat.getColor(requireContext(), R.color.secondaryColor)))
94 | .into(holder.ivThumb);
95 |
96 | holder.cvComicItem.setOnClickListener(new View.OnClickListener() {
97 | @Override
98 | public void onClick(View v) {
99 | presenter.onItemClick(position);
100 | }
101 | });
102 | }
103 |
104 | @Override
105 | public void updateList() {
106 | RecyclerView.Adapter adapter = rvComicList.getAdapter();
107 | if (adapter.getItemCount() != 0)
108 | adapter.notifyDataSetChanged();
109 |
110 | toggleLoadingDesc(false);
111 |
112 | }
113 |
114 | private void toggleLoadingDesc(boolean loading) {
115 | if (loading) {
116 | pbComicList.show();
117 | tvComicListDesc.setVisibility(View.VISIBLE);
118 | } else {
119 | pbComicList.hide();
120 | tvComicListDesc.setVisibility(View.INVISIBLE);
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/ComicCollectionViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 |
4 | import android.view.View;
5 | import android.widget.ImageView;
6 | import android.widget.TextView;
7 |
8 | import androidx.cardview.widget.CardView;
9 | import androidx.recyclerview.widget.RecyclerView;
10 |
11 | import com.github.ttdyce.nhviewer.R;
12 |
13 | public class ComicCollectionViewHolder extends RecyclerView.ViewHolder {
14 | public CardView cvComicItem;
15 | public TextView tvTitle;
16 | public TextView tvNumOfComics;
17 | public ImageView ivThumb;
18 |
19 | public ComicCollectionViewHolder(View v) {
20 | super(v);
21 | tvTitle = v.findViewById(R.id.tvComicListItem);
22 | tvNumOfComics= v.findViewById(R.id.tvNumOfComics);
23 | ivThumb = v.findViewById(R.id.ivComicListItem);
24 | cvComicItem = v.findViewById(R.id.cvComicListItem);
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/ComicListViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 |
4 | import android.view.View;
5 | import android.widget.ImageButton;
6 | import android.widget.ImageView;
7 | import android.widget.TextView;
8 |
9 | import androidx.cardview.widget.CardView;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import com.github.ttdyce.nhviewer.R;
13 |
14 | public class ComicListViewHolder extends RecyclerView.ViewHolder {
15 | public CardView cvComicItem;
16 | public TextView tvTitle;
17 | public TextView tvNumOfPages;
18 | public ImageView ivThumb;
19 | public ImageButton ibCollect;
20 | public ImageButton ibFavorite;
21 |
22 | public ComicListViewHolder(View v) {
23 | super(v);
24 | tvTitle = v.findViewById(R.id.tvComicListItem);
25 | tvNumOfPages= v.findViewById(R.id.tvNumOfPages);
26 | ivThumb = v.findViewById(R.id.ivComicListItem);
27 | cvComicItem = v.findViewById(R.id.cvComicListItem);
28 | ibCollect = v.findViewById(R.id.ibCollect);
29 | ibFavorite = v.findViewById(R.id.ibFavorite);
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/ComicViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.view.View;
4 | import android.widget.ImageView;
5 | import android.widget.TextView;
6 |
7 | import androidx.recyclerview.widget.RecyclerView;
8 |
9 | import com.github.ttdyce.nhviewer.R;
10 |
11 | public class ComicViewHolder extends RecyclerView.ViewHolder {
12 | public ImageView ivComicPage;
13 | public TextView tvComicPage;
14 |
15 | public ComicViewHolder(View v) {
16 | super(v);
17 | ivComicPage = v.findViewById(R.id.ivComicPage);
18 | tvComicPage = v.findViewById(R.id.tvComicPage);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.os.Bundle;
8 | import android.preference.PreferenceManager;
9 | import android.util.Log;
10 |
11 | import androidx.appcompat.app.AlertDialog;
12 | import androidx.appcompat.app.AppCompatActivity;
13 | import androidx.appcompat.widget.Toolbar;
14 | import androidx.navigation.NavController;
15 | import androidx.navigation.Navigation;
16 | import androidx.navigation.ui.NavigationUI;
17 | import androidx.room.Room;
18 |
19 | import com.github.ttdyce.nhviewer.BuildConfig;
20 | import com.github.ttdyce.nhviewer.R;
21 | import com.github.ttdyce.nhviewer.model.MyDistributeListener;
22 | import com.github.ttdyce.nhviewer.model.room.AppDatabase;
23 | import com.github.ttdyce.nhviewer.model.room.ComicCollectionDao;
24 | import com.github.ttdyce.nhviewer.model.room.ComicCollectionEntity;
25 | import com.google.android.material.bottomnavigation.BottomNavigationView;
26 | import com.microsoft.appcenter.AppCenter;
27 | import com.microsoft.appcenter.analytics.Analytics;
28 | import com.microsoft.appcenter.crashes.Crashes;
29 | import com.microsoft.appcenter.distribute.Distribute;
30 |
31 | import java.util.Date;
32 |
33 |
34 | public class MainActivity extends AppCompatActivity {
35 | public static final String KEY_PREF_DEFAULT_LANGUAGE = "key_default_language";
36 | public static final String KEY_PREF_DEMO_MODE = "key_demo_mode";
37 | public static final String KEY_PREF_ENABLE_SPLASH = "key_enable_splash";
38 | public static final String KEY_PREF_CHECK_UPDATE = "key_check_update";
39 | public static final String KEY_PREF_LAST_VERSION_OPENED = "key_last_version_opened";
40 | public static final CharSequence KEY_PREF_VERSION = "key_version";
41 | public static final String KEY_PREF_PROXY = "key_proxy";
42 | public static final String KEY_PREF_PROXY_HOST = "key_proxy_host";
43 | public static final String KEY_PREF_PROXY_PORT = "key_proxy_port";
44 | private static final String TAG = "MainActivity";
45 | private static AppDatabase appDatabase;
46 | public static String proxyHost;
47 | public static int proxyPort;
48 |
49 | @Override
50 | protected void onCreate(Bundle savedInstanceState) {
51 | setTheme(R.style.AppTheme);//replacing the SplashTheme
52 | //Open SplashActivity
53 | final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
54 | boolean enabledSplash = pref.getBoolean(KEY_PREF_ENABLE_SPLASH, true);
55 | if (enabledSplash)
56 | startActivity(new Intent(this, SplashActivity.class));// TODO: 12/15/2019 Open SplashActivity from MainActivity
57 |
58 | super.onCreate(savedInstanceState);
59 | setContentView(R.layout.activity_main);
60 |
61 | checkMigration();
62 | tryAskForLanguage();
63 | init();
64 | }
65 |
66 | private void checkMigration() {
67 | final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
68 | String currentVersion = BuildConfig.VERSION_NAME;
69 | String lastVersion = pref.getString(KEY_PREF_LAST_VERSION_OPENED, "0.0.0");
70 | if (!currentVersion.equals(lastVersion)) {
71 | //it is first time open after update / simply first time open
72 |
73 | if (currentVersion.equals("2.6.0") || lastVersion.equals("0.0.0")) {
74 | //fix for 2.5.0 -> 2.6.0
75 | pref.edit().remove(KEY_PREF_DEFAULT_LANGUAGE).commit();
76 | }
77 |
78 | }
79 |
80 | pref.edit().putString(KEY_PREF_LAST_VERSION_OPENED, currentVersion).apply();
81 | }
82 |
83 | private void init() {
84 | // setup vs-app-center
85 | Distribute.setListener(new MyDistributeListener());
86 | AppCenter.start(getApplication(), "3b65600f-dd4f-415c-8949-e32f594cba0d",
87 | Analytics.class, Crashes.class, Distribute.class);
88 |
89 | final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
90 | boolean enabledCheckUpdate = pref.getBoolean(KEY_PREF_CHECK_UPDATE, true);
91 | AppCenter.setEnabled(enabledCheckUpdate); // for all services
92 |
93 | appDatabase = Room.databaseBuilder(getApplicationContext(),
94 | AppDatabase.class, AppDatabase.DB_NAME)
95 | .addMigrations(AppDatabase.MIGRATION_1_2).build();
96 |
97 | // deleteDatabase(AppDatabase.DB_NAME);
98 | //init Collections
99 | // todo replace with executor-things (java concurrent)
100 | new Thread(
101 | new Runnable() {
102 | @Override
103 | public void run() {
104 | ComicCollectionDao dao = appDatabase.comicCollectionDao();
105 | if (dao.notExist(AppDatabase.COL_COLLECTION_HISTORY, -1)) {
106 | dao.insert(ComicCollectionEntity.create(AppDatabase.COL_COLLECTION_HISTORY, -1, new Date()));
107 | dao.insert(ComicCollectionEntity.create(AppDatabase.COL_COLLECTION_FAVORITE, -1, new Date()));
108 | dao.insert(ComicCollectionEntity.create(AppDatabase.COL_COLLECTION_NEXT, -1, new Date()));
109 | }
110 | }
111 | }).start();
112 | //app bar
113 | Toolbar myToolbar = findViewById(R.id.toolbar_main);
114 | setSupportActionBar(myToolbar);
115 |
116 | //proxy
117 | proxyHost = pref.getString(KEY_PREF_PROXY_HOST, "");
118 | try {
119 | proxyPort = Integer.parseInt(pref.getString(KEY_PREF_PROXY_PORT, "8080"));
120 | } catch (NumberFormatException e) {
121 | Log.e(TAG, "init ignorable error: setting proxyPort to default (8080)");
122 | proxyPort = 8080;
123 | }
124 |
125 | Log.d(TAG, "init: proxyHost: " + proxyHost);
126 | Log.d(TAG, "init: proxyPort: " + proxyPort);
127 |
128 | }
129 |
130 | //Link bottom navigation view with jetpack navigation
131 | private void initNavigation() {
132 | NavController navController = Navigation.findNavController(this, R.id.fragmentNavHost);
133 | navController.setGraph(R.navigation.nav_app);
134 | BottomNavigationView bottomNavigation = findViewById(R.id.bottomNavigation);
135 | NavigationUI.setupWithNavController(bottomNavigation, navController);
136 | // Navigation.findNavController(this, R.id.fragmentNavHost)
137 | }
138 |
139 | @SuppressLint("ApplySharedPref")
140 | private void tryAskForLanguage() {
141 | String comicLanguage = SettingsFragment.Language.notSet.toString();
142 | final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
143 |
144 | try {
145 | comicLanguage = pref.getString(KEY_PREF_DEFAULT_LANGUAGE, SettingsFragment.Language.notSet.toString());
146 | } catch (ClassCastException e) {
147 | pref.edit().remove(KEY_PREF_DEFAULT_LANGUAGE).commit();
148 | }
149 |
150 | if (!comicLanguage.equals(SettingsFragment.Language.notSet.toString())) {
151 | initNavigation();
152 | return;
153 | }
154 |
155 | //pop up dialog for setting default language
156 | final String[] languageArray = getResources().getStringArray(R.array.languages);
157 | AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.DialogTheme);
158 |
159 | builder.setTitle(getString(R.string.set_default_language));
160 | builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
161 | @Override
162 | public void onClick(DialogInterface dialog, int which) {
163 | SharedPreferences.Editor editor = pref.edit();
164 |
165 | editor.putString(KEY_PREF_DEFAULT_LANGUAGE, SettingsFragment.Language.all.toString());
166 | editor.apply();
167 |
168 | }
169 | });
170 | builder.setItems(languageArray, new DialogInterface.OnClickListener() {
171 | @Override
172 | public void onClick(DialogInterface dialog, int which) {
173 | Log.d(TAG, "onClick: init language clicked: " + which);
174 | SharedPreferences.Editor editor = pref.edit();
175 |
176 | editor.putString(KEY_PREF_DEFAULT_LANGUAGE, String.valueOf(which));
177 | editor.apply();
178 |
179 | final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
180 | }
181 | });
182 | builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
183 | @Override
184 | public void onDismiss(DialogInterface dialog) {
185 | String language = pref.getString(KEY_PREF_DEFAULT_LANGUAGE, SettingsFragment.Language.notSet.toString());
186 | if (SettingsFragment.Language.notSet.toString().equals(language)) {
187 | SharedPreferences.Editor editor = pref.edit();
188 | editor.putString(KEY_PREF_DEFAULT_LANGUAGE, SettingsFragment.Language.all.toString());
189 | editor.apply();
190 | }
191 |
192 | Log.d(TAG, "onClick: after init language, before dismiss dialog: " + pref.getString(KEY_PREF_DEFAULT_LANGUAGE, "not set"));
193 | initNavigation();
194 | }
195 | });
196 | builder.show();
197 | }
198 |
199 | //Singleton database
200 | public static AppDatabase getAppDatabase() {
201 | return appDatabase;
202 | }
203 |
204 | // TODO: 4/5/2023 seems not the best way to maintain proxy-related settings here, proxyHost sometimes becomes null
205 | public static boolean isProxied() {
206 | if (proxyHost == null || "".equals(proxyHost))
207 | return false;
208 |
209 | return true;
210 | }
211 |
212 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/ProxySettingsFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.preference.PreferenceFragmentCompat;
6 |
7 | import com.github.ttdyce.nhviewer.R;
8 |
9 | public class ProxySettingsFragment extends PreferenceFragmentCompat {
10 |
11 | @Override
12 | public void onCreate(Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 |
15 | // Load the preferences from an XML resource
16 | addPreferencesFromResource(R.xml.proxy_preferences);
17 | }
18 |
19 | @Override
20 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
21 |
22 |
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/RefreshCookieActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.util.Log;
7 | import android.webkit.CookieManager;
8 | import android.webkit.ValueCallback;
9 | import android.webkit.WebView;
10 | import android.widget.Toast;
11 |
12 | import androidx.appcompat.app.AppCompatActivity;
13 |
14 | import com.github.ttdyce.nhviewer.R;
15 | import com.github.ttdyce.nhviewer.model.CookieStringRequest;
16 |
17 | public class RefreshCookieActivity extends AppCompatActivity {
18 | String url = "https://nhentai.net";
19 |
20 | @SuppressLint("SetJavaScriptEnabled")
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_refresh_cookie);
25 |
26 | /* Use a WebView to bypass cloudflare challenge (Retrieve cookie) */
27 | WebView wvRefreshCookie = findViewById(R.id.wvRefreshCookie);
28 |
29 | // clear cookie on create. on App open and Preference page item click enter this part
30 | CookieManager.getInstance().removeAllCookies(new ValueCallback() {
31 | @Override
32 | public void onReceiveValue(Boolean value) {
33 | // leave empty
34 | }
35 | });
36 |
37 | wvRefreshCookie.getSettings().setJavaScriptEnabled(true);
38 | // wvInvisibleSplash.getSettings().setUserAgentString("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/110.0");
39 | wvRefreshCookie.loadUrl(url);
40 |
41 | String cookies = CookieManager.getInstance().getCookie(url);
42 | Log.d("SplashActivitiy", "url: " + url);
43 | Log.d("SplashActivitiy", "Got cookie: " + cookies);
44 | Log.d("SplashActivitiy", "User agent: " + wvRefreshCookie.getSettings().getUserAgentString());
45 | checkCookie(wvRefreshCookie.getSettings().getUserAgentString());
46 | }
47 |
48 | private void checkCookie(String userAgent) {
49 | Handler handler = new Handler();
50 | handler.postDelayed(new Runnable() {
51 | public void run() {
52 | String cookies = CookieManager.getInstance().getCookie(url);
53 | if (cookies == null || !cookies.contains("cf_clearance=")) {
54 | Log.e("SplashActivitiy", "Not found required cookie: cf_clearance, try again soon...");
55 | checkCookie(userAgent);
56 | // checkCookie(userAgent);
57 | finish();
58 | } else {
59 | Log.d("SplashActivitiy", "url: " + url);
60 | Log.d("SplashActivitiy", "Got cookie: " + cookies);
61 | // Log.d("SplashActivitiy", "User agent: " + wvInvisibleSplash.getSettings().getUserAgentString());
62 | CookieStringRequest.challengeCookies = cookies;
63 | CookieStringRequest.userAgent = userAgent;
64 | Toast.makeText(getApplicationContext(), "Saved cookie, page loading should be work now (" + cookies.substring(0, 20) + "...", Toast.LENGTH_LONG).show();
65 |
66 | finish();
67 | }
68 | }
69 | }, 500);
70 |
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/SearchingFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.app.SearchManager;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 | import android.view.LayoutInflater;
9 | import android.view.Menu;
10 | import android.view.MenuInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.TextView;
14 |
15 | import androidx.annotation.NonNull;
16 | import androidx.annotation.Nullable;
17 | import androidx.appcompat.widget.SearchView;
18 | import androidx.core.widget.ContentLoadingProgressBar;
19 | import androidx.fragment.app.Fragment;
20 | import androidx.navigation.Navigation;
21 |
22 | import com.github.ttdyce.nhviewer.R;
23 | import com.github.ttdyce.nhviewer.model.api.NHAPI;
24 | import com.github.ttdyce.nhviewer.model.api.ResponseCallback;
25 | import com.github.ttdyce.nhviewer.model.comic.Comic;
26 | import com.github.ttdyce.nhviewer.presenter.ComicPresenter;
27 | import com.google.gson.Gson;
28 | import com.google.gson.JsonObject;
29 | import com.google.gson.JsonParser;
30 |
31 |
32 | public class SearchingFragment extends Fragment {
33 | private static final String TAG = "SearchingFragment";
34 |
35 | private ContentLoadingProgressBar pbComicList;
36 | private TextView tvComicListDesc;
37 |
38 |
39 | @Override
40 | public void onCreate(Bundle savedInstanceState) {
41 | super.onCreate(savedInstanceState);
42 | setHasOptionsMenu(true);
43 |
44 | }
45 |
46 | @Override
47 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
48 | Bundle savedInstanceState) {
49 | // Inflate the layout for this fragment
50 | return inflater.inflate(R.layout.fragment_comic_list, container, false);
51 |
52 | }
53 |
54 | @Override
55 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
56 | super.onViewCreated(view, savedInstanceState);
57 |
58 | pbComicList = view.findViewById(R.id.pbComicList);
59 | tvComicListDesc = view.findViewById(R.id.tvComicListDesc);
60 |
61 | pbComicList.setVisibility(View.INVISIBLE);
62 | tvComicListDesc.setText(getString(R.string.enter_search_query));
63 | }
64 |
65 |
66 | @Override
67 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
68 | inflater.inflate(R.menu.app_bar_items_searching, menu);
69 |
70 | // Get the SearchView and set the searchable configuration
71 | SearchManager searchManager = (SearchManager) requireActivity().getSystemService(Context.SEARCH_SERVICE);
72 | SearchView searchView = (SearchView) menu.findItem(R.id.action_searchview).getActionView();
73 | // Assumes current activity is the searchable activity
74 | searchView.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().getComponentName()));
75 | searchView.setIconified(false);
76 | searchView.requestFocusFromTouch();
77 |
78 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
79 | @Override
80 | public boolean onQueryTextSubmit(String query) {
81 | try {
82 | int id = Integer.parseInt(query); // treat number query as an id
83 | NHAPI nhapi = new NHAPI(requireContext(), MainActivity.proxyHost, MainActivity.proxyPort);
84 |
85 | // FIXME: 10/26/2020 activity is opened twice
86 | nhapi.getComic(id, new ResponseCallback() {
87 | @Override
88 | public void onReponse(String response) {
89 | JsonObject obj = JsonParser.parseString(response).getAsJsonObject();
90 | Gson gson = new Gson();
91 | Comic c = gson.fromJson(obj, Comic.class);
92 | //enter comic
93 | Context activity = getActivity();
94 | Intent intent = new Intent(activity, ComicActivity.class);
95 | Bundle args = new Bundle();
96 |
97 | intent.putExtra(ComicPresenter.ARG_ID, c.getId());
98 | intent.putExtra(ComicPresenter.ARG_MID, c.getMid());
99 | intent.putExtra(ComicPresenter.ARG_TITLE, c.getTitle().toString());
100 | intent.putExtra(ComicPresenter.ARG_NUM_OF_PAGES, c.getNumOfPages());
101 | intent.putExtra(ComicPresenter.ARG_PAGE_TYPES, c.getPageTypes());
102 |
103 | activity.startActivity(intent, args);
104 | }
105 | });
106 |
107 | } catch (NumberFormatException _ignored) {
108 | // do searching, not an id
109 | Log.d(TAG, String.format("onClick: searching %s", query));
110 | Bundle bundle = new Bundle();
111 | bundle.putString(ComicListFragment.ARG_COLLECTION_NAME, "result");
112 | bundle.putString(ComicListFragment.ARG_QUERY, query);
113 | Navigation.findNavController(requireView()).navigate(R.id.comicListFragment, bundle);
114 | }
115 |
116 | return false;
117 | }
118 |
119 | @Override
120 | public boolean onQueryTextChange(String newText) {
121 | return false;
122 | }
123 | });
124 |
125 | super.onCreateOptionsMenu(menu, inflater);
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 | import android.widget.Toast;
6 |
7 | import androidx.appcompat.app.AlertDialog;
8 | import androidx.navigation.NavController;
9 | import androidx.navigation.Navigation;
10 | import androidx.preference.Preference;
11 | import androidx.preference.PreferenceFragmentCompat;
12 | import androidx.preference.PreferenceScreen;
13 | import androidx.preference.SwitchPreference;
14 |
15 | import com.github.ttdyce.nhviewer.BuildConfig;
16 | import com.github.ttdyce.nhviewer.R;
17 | import com.microsoft.appcenter.distribute.Distribute;
18 |
19 | public class SettingsFragment extends PreferenceFragmentCompat {
20 |
21 | @Override
22 | public void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 |
25 | // Load the preferences from an XML resource
26 | addPreferencesFromResource(R.xml.preferences);
27 |
28 | showVersionName();
29 | setVersionOnClick();
30 |
31 | PreferenceScreen proxyPreference = findPreference(MainActivity.KEY_PREF_PROXY);
32 | proxyPreference.setOnPreferenceClickListener(preference -> {
33 | NavController navController = Navigation.findNavController(getActivity(), R.id.fragmentNavHost);
34 | navController.navigate(R.id.proxySettingsFragment);
35 | return true;
36 | });
37 | setCheckUpdateOnClick();
38 | }
39 |
40 | private void setCheckUpdateOnClick() {
41 |
42 | SwitchPreference checkUpdatePreference = findPreference(MainActivity.KEY_PREF_CHECK_UPDATE);
43 | checkUpdatePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
44 | @Override
45 | public boolean onPreferenceClick(Preference preference) {
46 | Toast.makeText(requireActivity().getApplicationContext(), R.string.remind_restart_after_setting, Toast.LENGTH_SHORT).show();
47 | return true;
48 | }
49 | });
50 | }
51 |
52 | @Override
53 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
54 |
55 |
56 | }
57 |
58 | private void setVersionOnClick() {
59 | PreferenceScreen versionPreference = findPreference(MainActivity.KEY_PREF_VERSION);
60 | versionPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
61 | @Override
62 | public boolean onPreferenceClick(Preference preference) {
63 | Toast.makeText(requireActivity().getApplicationContext(), "Checking latest version...", Toast.LENGTH_SHORT).show();
64 | Distribute.checkForUpdate();
65 |
66 | return true;
67 | }
68 | });
69 | }
70 |
71 | private void showVersionName() {
72 | int versionCode = BuildConfig.VERSION_CODE;
73 | String versionName = BuildConfig.VERSION_NAME;
74 | Log.i("SettingsFragment", "onCreate: version name=" + versionName);
75 | Log.i("SettingsFragment", "onCreate: version code=" + versionCode);
76 |
77 | PreferenceScreen editTextPreference = findPreference(MainActivity.KEY_PREF_VERSION);
78 | editTextPreference.setSummary(versionName);
79 | }
80 |
81 |
82 |
83 |
84 | public enum Language{
85 | all(0), chinese(1), english(2), japanese(3), notSet(-1);
86 |
87 | int id;
88 |
89 | Language(int i) {
90 | id = i;
91 | }
92 |
93 | public int getInt() {
94 | return id;
95 | }
96 |
97 | public String toString(){
98 | return String.valueOf(id);
99 | }
100 |
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/ttdyce/nhviewer/view/SplashActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer.view;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.util.Log;
7 | import android.view.animation.Animation;
8 | import android.view.animation.AnimationUtils;
9 | import android.webkit.CookieManager;
10 | import android.webkit.WebView;
11 | import android.widget.TextView;
12 | import android.widget.Toast;
13 |
14 | import androidx.appcompat.app.AppCompatActivity;
15 |
16 | import com.github.ttdyce.nhviewer.R;
17 | import com.github.ttdyce.nhviewer.model.CookieStringRequest;
18 |
19 | public class SplashActivity extends AppCompatActivity {
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_splash);
25 |
26 | /* Use a WebView to bypass cloudflare challenge (Retrieve cookie) */
27 | // any api url is fine :|
28 | String url = "https://nhentai.net";
29 | WebView wvInvisibleSplash = findViewById(R.id.wvInvisibleSplash);
30 |
31 | // uncomment this part to simulate no-cookie state, for debugging
32 | CookieManager.getInstance().removeAllCookies(null);
33 | CookieManager.getInstance().flush();
34 |
35 | wvInvisibleSplash.getSettings().setJavaScriptEnabled(true);
36 | wvInvisibleSplash.getSettings().setUserAgentString("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0");
37 |
38 | wvInvisibleSplash.loadUrl(url);
39 |
40 | /* Performs animation on a TextView. */
41 | TextView splashLoading = findViewById(R.id.tvSplashLoading);
42 | Animation animation = AnimationUtils.loadAnimation(this, R.anim.splash_loading);
43 | splashLoading.startAnimation(animation);
44 |
45 | // TODO: 12/15/2019 hard coded SplashActivity wait for 1.5 second
46 | // TODO: 8/1/2022 hard coded 5 seconds for cloudflare challenge
47 | Handler handler = new Handler();
48 | Intent refreshCookieIntent = new Intent(this, RefreshCookieActivity.class);
49 | handler.postDelayed(new Runnable() {
50 | public void run() {
51 | // handle Cloudflare challenge
52 | String cookies = CookieManager.getInstance().getCookie(url);
53 | Log.d("SplashActivitiy", "url: " + url);
54 | Log.d("SplashActivitiy", "Got cookie: " + cookies);
55 | Log.d("SplashActivitiy", "User agent: " + wvInvisibleSplash.getSettings().getUserAgentString());
56 | if (cookies == null || !cookies.contains("cf_clearance=")) {
57 | Log.e("SplashActivitiy", "Not found required cookie: cf_clearance, try loading anyway as recently CF Cookie is not needed");
58 |
59 | Toast.makeText(getApplicationContext(), "Failed to bypass human checking, maybe direct connect is allowed", Toast.LENGTH_LONG).show();
60 | startActivity(refreshCookieIntent);
61 | CookieStringRequest.challengeCookies = "cf_clearance=N/A";
62 | CookieStringRequest.userAgent = wvInvisibleSplash.getSettings().getUserAgentString();
63 | } else {
64 | CookieStringRequest.challengeCookies = cookies;
65 | CookieStringRequest.userAgent = wvInvisibleSplash.getSettings().getUserAgentString();
66 | }
67 |
68 | // return to MainActivity
69 | finish();
70 | }
71 | }, 2 * 1000); // 2 seconds
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/splash_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
31 |
36 |
39 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_to_photos_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_box_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_done_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_favorite_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_favorite_border_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_keyboard_arrow_right_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_nhviewer_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_nhlogo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sort_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rectangle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_backup.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_comic.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
20 |
21 |
25 |
26 |
34 |
35 |
36 |
37 |
40 |
41 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_refresh_cookie.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
22 |
23 |
29 |
30 |
36 |
37 |
45 |
46 |
51 |
52 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_comic_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
19 |
26 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_comic.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_comic_collection_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
24 |
25 |
26 |
32 |
33 |
34 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_comic_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
20 |
21 |
24 |
25 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
53 |
54 |
55 |
58 |
59 |
68 |
69 |
75 |
76 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/app_bar_items_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/app_bar_items_searching.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/app_bar_selection_mode.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_navigation_items.xml:
--------------------------------------------------------------------------------
1 |
2 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_nhviewer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_nhviewer_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_nhviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-hdpi/ic_launcher_nhviewer.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_nhviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-hdpi/ic_launcher_nhviewer_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_nhviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-hdpi/ic_launcher_nhviewer_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_nhviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-mdpi/ic_launcher_nhviewer.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_nhviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-mdpi/ic_launcher_nhviewer_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_nhviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-mdpi/ic_launcher_nhviewer_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_nhviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xhdpi/ic_launcher_nhviewer.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_nhviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xhdpi/ic_launcher_nhviewer_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_nhviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xhdpi/ic_launcher_nhviewer_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_nhviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxhdpi/ic_launcher_nhviewer.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_nhviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxhdpi/ic_launcher_nhviewer_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_nhviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxhdpi/ic_launcher_nhviewer_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_nhviewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxxhdpi/ic_launcher_nhviewer.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_nhviewer_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxxhdpi/ic_launcher_nhviewer_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_nhviewer_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxxhdpi/ic_launcher_nhviewer_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_app.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
16 |
20 |
21 |
26 |
30 |
34 |
35 |
40 |
44 |
48 |
49 |
54 |
58 |
59 |
64 |
67 |
70 |
71 |
76 |
79 |
80 |
85 |
86 |
88 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 首页加载中
5 |
6 |
7 |
8 | - 所有
9 | - 中文
10 | - 英文
11 | - 日文
12 |
13 |
14 |
15 | 有新版本了: %1$s
16 |
17 |
18 | 首页
19 | 收藏
20 | 搜索
21 | 最爱
22 | 设置
23 |
24 | 语言
25 | 启用开屏动画
26 | 检查更新 (VS App Center)
27 | 显示模式
28 | 通用
29 | 备份
30 | 扫描二维码
31 | 关于
32 | 作者
33 | 版本号
34 | 取消
35 | 载入 %s 中
36 | 设置漫画语言
37 | %s]]>
38 | 删除漫画时出错: %s
39 | %s 中删除: %s]]>
40 | %s]]>
41 | 搜索
42 | 设定会在重启后生效
43 | "需要从 GitHub 重新下载,以更新到3.0.0版本 (V3)。有关 V3 的更新内容亦放在了 GitHub 上 (如自动更新,外观,和更多的新功能!) "
44 | "更新到 3.0.0 "
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 載入首頁中
5 |
6 |
7 |
8 | - 所有
9 | - 中文
10 | - 英文
11 | - 日文
12 |
13 |
14 |
15 | 新版本檢查: %1$s
16 |
17 |
18 | 首頁
19 | 收藏
20 | 搜尋
21 | 最愛
22 | 設定
23 |
24 | 語言
25 | 開啟打開動畫
26 | 檢查更新 (VS App Center)
27 | 演示模式
28 | 通用
29 | 備份
30 | 掃瞄二維碼
31 | 關於
32 | 維護者
33 | 安裝版本
34 | 取消
35 | 載入 %s 中
36 | 設定漫畫語言
37 | %s]]>
38 | 刪除漫畫時出錯: %s
39 | %s 中刪除: %s]]>
40 | %s]]>
41 | 搜尋
42 | 設定會在重啟後生效
43 | 需要從 GitHub 重新下載,以更新到3.0.0版本 (V3)。有關 V3 的更新內容亦放在了 GitHub 上 (如自動更新,外觀,和更多的新功能!)
44 | "更新到 3.0.0 "
45 | 按受歡迎程度排列
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #a00037
4 | #ff5c8d
5 | #8A1747
6 | #b0bec5
7 | #e2f1f8
8 | #808e95
9 |
10 | @color/secondaryTextColor
11 | #fff
12 | @color/secondaryColor
13 | #fff
14 | #202a34
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_nhviewer_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #202A34
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | NHViewer
4 | github.com/ttdyce
5 | Abandon all hope, ye who enter here
6 | ここから入らんとする者は一切の希望を放棄せよ
7 | Comic inner page
8 | Comic thumbnail
9 | Comic selector
10 | Comic collect button
11 | Comic favorite button
12 |
13 |
14 | Loading from index
15 |
16 |
17 |
18 | - All
19 | - Chinese
20 | - English
21 | - Japanese
22 |
23 |
24 | - All
25 | - Chinese
26 | - English
27 | - Japanese
28 |
29 |
30 | - 0
31 | - 1
32 | - 2
33 | - 3
34 |
35 |
36 |
37 |
38 | - None
39 | - All-Time
40 | - Month
41 | - Week
42 | - Day
43 |
44 |
45 |
46 | New version available %1$s
47 |
48 | Index
49 | Collection
50 | Search
51 | Favorite
52 | Setting
53 | Language
54 | Enable splash screen
55 | Check update (by VS App Center)
56 | Demo mode
57 | General
58 | Backup
59 | Scan QR code (Connect to server)
60 | About
61 | Maintained by
62 | Version
63 | Loading from %s …
64 | %s]]>
65 | %s]]>
66 | %s, named %s]]>
67 | Error when deleting comic named %s
68 | Set your default language
69 | Cancel
70 | Enter search query
71 | Settings would take effect after restart
72 | Re-downloading from GitHub is required for version 3.0.0 (V3). Please go to GitHub to see more details about V3 (e.g. changes on auto-update, the look and feel, and more features!)
73 | Version 3.0.0 update
74 | Popular type
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
24 |
25 |
29 |
30 |
31 |
37 |
38 |
42 |
43 |
50 |
51 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/firebase_default_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | updateRequired
6 | false
7 |
8 |
9 | currentVersion
10 | 2.0.0
11 |
12 |
13 | updateUrl
14 | https://github.com/ttdyce/NHentaiViewer/releases/download/2.0.0/nhviewer-2.0.0-signed.apk
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
12 | >
16 |
19 |
20 |
21 |
26 |
31 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/proxy_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/test/java/com/github/ttdyce/nhviewer/APIUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer;
2 |
3 | import com.github.ttdyce.nhviewer.model.api.NHAPI;
4 |
5 | import org.junit.Test;
6 |
7 | import static org.junit.Assert.assertEquals;
8 |
9 | public class APIUnitTest {
10 | private final String mid = "1438192";
11 |
12 | @Test
13 | public void get_thumbnail_url() {
14 | String url = NHAPI.URLs.getThumbnail(mid, "j");
15 |
16 | assertEquals(url, "https://t.nhentai.net/galleries/" + mid + "/thumb.jpg");
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/test/java/com/github/ttdyce/nhviewer/ComicCollectionUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer;
2 |
3 | import com.github.ttdyce.nhviewer.model.comic.Comic;
4 | import com.github.ttdyce.nhviewer.model.room.ComicCollectionEntity;
5 |
6 | import org.junit.Test;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Date;
10 | import java.util.List;
11 |
12 | public class ComicCollectionUnitTest {
13 | private static final String TAG = "ComicCollectionUnitTest";
14 |
15 | @Test
16 | public void comicCollection_creation() {
17 | String name = "Demo collection";
18 | List comics = new ArrayList<>();
19 | comics.add(new Comic());
20 | comics.add(new Comic());
21 | comics.add(new Comic());
22 | comics.add(new Comic());
23 |
24 | // ComicCollectionEntity cc = new ComicCollectionEntity(name, comics);
25 |
26 | // assertNotNull(cc);
27 |
28 | }
29 |
30 | @Test
31 | public void comicCollectionEntity_toJson() {
32 | String name = "Demo collection";
33 | ComicCollectionEntity cc = new ComicCollectionEntity(name, 333, new Date());
34 |
35 | cc.toJson();
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/test/java/com/github/ttdyce/nhviewer/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.github.ttdyce.nhviewer;
2 |
3 | /**
4 | * Example local unit test, which will execute on the development machine (host).
5 | *
6 | * @see Testing documentation
7 | */
8 | public class ExampleUnitTest {
9 |
10 |
11 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 | maven {
8 | url "https://plugins.gradle.org/m2/"
9 | }
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:4.0.0'
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | //Firebase
16 | classpath 'com.google.gms:google-services:4.3.2'
17 | classpath "org.ajoberstar.grgit:grgit-gradle:4.1.1"
18 | }
19 | }
20 |
21 |
22 | allprojects {
23 | repositories {
24 | mavenCentral()
25 | google()
26 | jcenter()
27 |
28 | }
29 | }
30 |
31 | task clean(type: Delete) {
32 | delete rootProject.buildDir
33 | }
34 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | # fix gson >jdk8 exception 20220104
10 | org.gradle.jvmargs=-Xmx1536m --add-opens=java.base/java.io=ALL-UNNAMED
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | # AndroidX package structure to make it clearer which packages are bundled with the
16 | # Android operating system, and which are packaged with your app's APK
17 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
18 | android.useAndroidX=true
19 | # Automatically convert third-party libraries to use AndroidX
20 | android.enableJetifier=true
21 |
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jun 09 04:04:02 HKT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/screenshots/collection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/collection.png
--------------------------------------------------------------------------------
/screenshots/deprecated/V2/collection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/V2/collection.png
--------------------------------------------------------------------------------
/screenshots/deprecated/V2/comic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/V2/comic.png
--------------------------------------------------------------------------------
/screenshots/deprecated/V2/favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/V2/favorite.png
--------------------------------------------------------------------------------
/screenshots/deprecated/V2/index.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/V2/index.png
--------------------------------------------------------------------------------
/screenshots/deprecated/V2/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/V2/search.png
--------------------------------------------------------------------------------
/screenshots/deprecated/V2/setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/V2/setting.png
--------------------------------------------------------------------------------
/screenshots/deprecated/collection_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/collection_list.png
--------------------------------------------------------------------------------
/screenshots/deprecated/favorite_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/favorite_list.png
--------------------------------------------------------------------------------
/screenshots/deprecated/navigation_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/deprecated/navigation_view.png
--------------------------------------------------------------------------------
/screenshots/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/search.png
--------------------------------------------------------------------------------
/screenshots/setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttdyce/NHentai-NHViewer/12d8416263166b4f15369b00a4255eb8039efe63/screenshots/setting.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='NHViewer'
3 |
--------------------------------------------------------------------------------