├── .fvm
└── fvm_config.json
├── .gitattributes
├── .github
├── dependabot.yml
└── workflows
│ └── release.yml
├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── README_cn.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ ├── AndroidManifest.xml
│ │ ├── ic_launcher-playstore.png
│ │ └── res
│ │ │ ├── drawable-v24
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable
│ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── honjow
│ │ │ │ └── eros_n
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable-v24
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable
│ │ │ ├── ic_launcher_foreground.xml
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ └── ic_launcher.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher_round.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── changelog
├── v1.0.0+1.md
├── v1.0.10+10.md
├── v1.0.11+11.md
├── v1.0.12+12.md
├── v1.0.13+13.md
├── v1.0.14+14.md
├── v1.0.15+15.md
├── v1.0.16+16.md
├── v1.0.17+17.md
├── v1.0.18+18.md
├── v1.0.19+19.md
├── v1.0.2+2.md
├── v1.0.20+20.md
├── v1.0.3+3.md
├── v1.0.4+4.md
├── v1.0.5+5.md
├── v1.0.6+6.md
├── v1.0.7+7.md
├── v1.0.8+8.md
└── v1.0.9+9.md
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon.png
│ │ ├── icon_20pt.png
│ │ ├── icon_20pt@2x 1.png
│ │ ├── icon_20pt@2x.png
│ │ ├── icon_20pt@3x.png
│ │ ├── icon_29pt 1.png
│ │ ├── icon_29pt.png
│ │ ├── icon_29pt@2x 1.png
│ │ ├── icon_29pt@2x.png
│ │ ├── icon_29pt@3x.png
│ │ ├── icon_40pt.png
│ │ ├── icon_40pt@2x 1.png
│ │ ├── icon_40pt@2x.png
│ │ ├── icon_40pt@3x.png
│ │ ├── icon_60pt@2x.png
│ │ ├── icon_60pt@3x.png
│ │ ├── icon_76pt.png
│ │ ├── icon_76pt@2x.png
│ │ └── icon_83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── common
│ ├── const
│ │ └── const.dart
│ ├── enum.dart
│ ├── extension.dart
│ ├── global.dart
│ ├── parser
│ │ ├── paese_user_page.dart
│ │ ├── parse_gallery_detail.dart
│ │ ├── parse_gallery_image.dart
│ │ ├── parse_gallery_list.dart
│ │ ├── parse_info.dart
│ │ ├── parse_login_page.dart
│ │ └── parser.dart
│ └── provider
│ │ ├── palette_generator.dart
│ │ ├── receive_sharing_provider.dart
│ │ ├── settings_provider.dart
│ │ ├── tag_translate_provider.dart
│ │ └── theme_provider.dart
├── component
│ ├── converter
│ │ └── locale_converter.dart
│ ├── dialog
│ │ └── cf_dialog.dart
│ ├── exception
│ │ └── error.dart
│ ├── models
│ │ ├── comment.dart
│ │ ├── comment.freezed.dart
│ │ ├── comment.g.dart
│ │ ├── comment_poster.dart
│ │ ├── comment_poster.freezed.dart
│ │ ├── comment_poster.g.dart
│ │ ├── gallery.dart
│ │ ├── gallery.freezed.dart
│ │ ├── gallery.g.dart
│ │ ├── gallery_images.dart
│ │ ├── gallery_images.freezed.dart
│ │ ├── gallery_images.g.dart
│ │ ├── gallery_search.dart
│ │ ├── gallery_search.freezed.dart
│ │ ├── gallery_search.g.dart
│ │ ├── gallery_set.dart
│ │ ├── gallery_set.freezed.dart
│ │ ├── gallery_set.g.dart
│ │ ├── gallery_title.dart
│ │ ├── gallery_title.freezed.dart
│ │ ├── gallery_title.g.dart
│ │ ├── image.dart
│ │ ├── image.freezed.dart
│ │ ├── image.g.dart
│ │ ├── index.dart
│ │ ├── settings.dart
│ │ ├── settings.freezed.dart
│ │ ├── settings.g.dart
│ │ ├── tag.dart
│ │ ├── tag.freezed.dart
│ │ ├── tag.g.dart
│ │ ├── tag_translate_info.dart
│ │ ├── tag_translate_info.freezed.dart
│ │ ├── tag_translate_info.g.dart
│ │ ├── user.dart
│ │ ├── user.freezed.dart
│ │ └── user.g.dart
│ ├── theme
│ │ └── theme.dart
│ └── widget
│ │ ├── blur_image.dart
│ │ ├── broken_shield.dart
│ │ ├── buttons.dart
│ │ ├── desktop.dart
│ │ ├── eros_cached_network_image.dart
│ │ ├── gallery_image
│ │ ├── _image_loader.dart
│ │ ├── gallery_image_platform_interface.dart
│ │ ├── gallery_image_provider.dart
│ │ ├── gallery_image_web.dart
│ │ └── multi_image_stream_completer.dart
│ │ ├── preload_photo_view_gallery.dart
│ │ ├── scroll.dart
│ │ ├── scrolling_fab.dart
│ │ ├── sliver.dart
│ │ ├── sys_title.dart
│ │ ├── system_ui_overlay.dart
│ │ └── web_view.dart
├── generated
│ ├── intl
│ │ ├── messages_all.dart
│ │ ├── messages_en.dart
│ │ └── messages_zh_CN.dart
│ └── l10n.dart
├── l10n
│ ├── intl_en.arb
│ └── intl_zh_CN.arb
├── main.dart
├── network
│ ├── api.dart
│ ├── app_dio
│ │ ├── app_dio.dart
│ │ ├── dio_file_service.dart
│ │ ├── dio_http_cli.dart
│ │ ├── dio_through.dart
│ │ ├── dio_user_agent.dart
│ │ ├── exception.dart
│ │ ├── http_config.dart
│ │ ├── http_response.dart
│ │ ├── http_transformer.dart
│ │ └── pdio.dart
│ └── request.dart
├── pages
│ ├── enum.dart
│ ├── gallery
│ │ ├── comments_page.dart
│ │ ├── gallery_page_state.dart
│ │ ├── gallery_page_state.freezed.dart
│ │ ├── gallery_provider.dart
│ │ ├── gallery_view.dart
│ │ └── thumb_page.dart
│ ├── list_view
│ │ ├── item
│ │ │ ├── item_base.dart
│ │ │ ├── item_grid_card.dart
│ │ │ ├── item_list_card.dart
│ │ │ └── item_waterfall_flow_card.dart
│ │ └── list_view.dart
│ ├── nav
│ │ ├── favorite
│ │ │ ├── favorite_provider.dart
│ │ │ └── favorite_view.dart
│ │ ├── front
│ │ │ ├── front_provider.dart
│ │ │ ├── front_view.dart
│ │ │ ├── list_view_state.dart
│ │ │ ├── list_view_state.freezed.dart
│ │ │ └── list_view_state.g.dart
│ │ ├── history
│ │ │ ├── history_provider.dart
│ │ │ └── history_view.dart
│ │ ├── index
│ │ │ ├── index_provider.dart
│ │ │ ├── index_state.dart
│ │ │ └── index_view.dart
│ │ ├── more
│ │ │ ├── more_state.dart
│ │ │ └── more_view.dart
│ │ └── search
│ │ │ ├── search_provider.dart
│ │ │ └── search_view.dart
│ ├── read
│ │ ├── read_provider.dart
│ │ ├── read_state.dart
│ │ ├── read_state.freezed.dart
│ │ ├── read_state.g.dart
│ │ ├── read_view.dart
│ │ └── read_widget.dart
│ ├── setting
│ │ ├── about_page.dart
│ │ ├── advanced_setting_page.dart
│ │ ├── appearance_setting_page.dart
│ │ ├── general_setting_page.dart
│ │ ├── license_page.dart
│ │ ├── read_setting_page.dart
│ │ ├── setting_base.dart
│ │ └── settings_page.dart
│ ├── splash
│ │ └── splash_view.dart
│ ├── user
│ │ ├── login_page.dart
│ │ ├── user_provider.dart
│ │ └── web_login_page.dart
│ └── webview
│ │ └── webview.dart
├── routes
│ ├── routes.dart
│ └── routes.gr.dart
├── store
│ ├── db
│ │ ├── entity
│ │ │ ├── gallery_history.dart
│ │ │ ├── gallery_history.g.dart
│ │ │ ├── nh_tag.dart
│ │ │ ├── nh_tag.g.dart
│ │ │ ├── tag_translate.dart
│ │ │ └── tag_translate.g.dart
│ │ ├── isar.dart
│ │ └── isar_helper.dart
│ └── kv
│ │ └── hive.dart
└── utils
│ ├── clipboard_helper.dart
│ ├── eros_utils.dart
│ ├── get_utils
│ ├── extensions
│ │ ├── context_extensions.dart
│ │ ├── double_extensions.dart
│ │ ├── duration_extensions.dart
│ │ ├── export.dart
│ │ ├── iterable_extensions.dart
│ │ ├── num_extensions.dart
│ │ ├── string_extensions.dart
│ │ └── widget_extensions.dart
│ ├── get_utils.dart
│ ├── get_utils
│ │ └── get_utils.dart
│ ├── platform
│ │ ├── platform.dart
│ │ ├── platform_io.dart
│ │ └── platform_web.dart
│ └── queue
│ │ └── get_queue.dart
│ ├── logger.dart
│ ├── logger
│ └── pretty_printer.dart
│ └── toast.dart
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── app_icon_1024.png
│ │ ├── app_icon_128.png
│ │ ├── app_icon_16.png
│ │ ├── app_icon_256.png
│ │ ├── app_icon_32.png
│ │ ├── app_icon_512.png
│ │ └── app_icon_64.png
│ ├── Base.lproj
│ └── MainMenu.xib
│ ├── Configs
│ ├── AppInfo.xcconfig
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── Warnings.xcconfig
│ ├── DebugProfile.entitlements
│ ├── Info.plist
│ ├── MainFlutterWindow.swift
│ └── Release.entitlements
├── pubspec.lock
├── pubspec.yaml
├── screenshots
├── comment_1.jpg
├── gallery_1.jpg
├── gallery_2.jpg
├── history_1.jpg
└── home_1.jpg
├── scripts
└── thin-payload.sh
├── test
├── model_test.dart
├── network_test.dart
├── nhapi_test.dart
└── widget_test.dart
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.ico
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
/.fvm/fvm_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "flutterSdkVersion": "stable",
3 | "flavors": {}
4 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Always perform LF normalization
5 | *.dart text
6 | *.gradle text
7 | *.html text
8 | *.java text
9 | *.json text
10 | *.md text
11 | *.py text
12 | *.sh text
13 | *.txt text
14 | *.xml text
15 | *.yaml text
16 |
17 | # Make sure that these Windows files always have CRLF line endings at checkout
18 | *.bat text eol=crlf
19 | *.ps1 text eol=crlf
20 | *.rc text eol=crlf
21 | *.sln text eol=crlf
22 | *.props text eol=crlf
23 | *.vcxproj text eol=crlf
24 | *.vcxproj.filters text eol=crlf
25 | # Including templates
26 | *.sln.tmpl text eol=crlf
27 | *.props.tmpl text eol=crlf
28 | *.vcxproj.tmpl text eol=crlf
29 |
30 | # Never perform LF normalization
31 | *.ico binary
32 | *.jar binary
33 | *.png binary
34 | *.zip binary
35 | *.ttf binary
36 | *.otf binary
37 |
38 |
39 | GoogleService-Info.plist filter=git-crypt diff=git-crypt binary
40 | google-service.json filter=git-crypt diff=git-crypt binary
41 | /lib/firebase_options.dart filter=git-crypt diff=git-crypt binary
42 | /lib/config/config.dart filter=git-crypt diff=git-crypt binary
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 |
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 |
9 | - package-ecosystem: "gradle"
10 | directory: "/android"
11 | schedule:
12 | interval: "daily"
13 |
14 | - package-ecosystem: "pub"
15 | directory: "/"
16 | schedule:
17 | interval: "daily"
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
46 | GPUCache/
47 | .fvm/flutter_sdk
48 |
49 | .local-chromium/
50 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
8 | channel: unknown
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
17 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
18 | - platform: windows
19 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
20 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Eros-N
2 | English | [简体中文](https://github.com/honjow/eros_n/blob/master/README_cn.md)
3 |
4 | [](https://gitHub.com/honjow/eros_n/releases)
5 | [](https://github.com/honjow/eros_n/releases/latest)
6 | [](https://github.com/honjow/eros_n/releases/latest)
7 | []()
8 | [](https://t.me/joinchat/AEj27KMQe0JiMmUx)
9 |
10 | ## Introduction
11 | An unofficial Nhentai app.
12 |
13 | ## Features
14 | - [x] Front page
15 | - [x] User login
16 | - [x] Favorites
17 | - [x] View gallery
18 | - [x] Gallery tags
19 | - [ ] More view settings
20 | - [ ] Download gallery
21 | - [x] Similar galleries
22 | - [x] Gallery comments
23 | - [x] Post comments
24 | - [x] History
25 | - [x] Search
26 | - [ ] More settings
27 |
28 | ## Windows platform requirements
29 | - [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
30 | - [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170)
31 | - Windows 10 1809+
32 |
33 |
34 | ## Screenshots
35 | | | | |
36 | |:--------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------:|
37 | | Front | Gallery | Gallery |
38 | | Comment | History ||
39 |
--------------------------------------------------------------------------------
/README_cn.md:
--------------------------------------------------------------------------------
1 | # Eros-N
2 | [English](https://github.com/honjow/eros_n/blob/master/README.md) | 简体中文
3 |
4 | [](https://gitHub.com/honjow/eros_n/releases)
5 | [](https://github.com/honjow/eros_n/releases/latest)
6 | [](https://github.com/honjow/eros_n/releases/latest)
7 | []()
8 | [](https://t.me/joinchat/AEj27KMQe0JiMmUx)
9 |
10 |
11 | ## 应用简介
12 | 一个第三方nh客户端.
13 |
14 | ## 功能
15 | - [x] 首页
16 | - [x] 用户登录
17 | - [x] 收藏列表
18 | - [x] 查看画廊
19 | - [x] 画廊标签 (翻译)
20 | - [ ] 更多查看设置
21 | - [ ] 下载画廊
22 | - [x] 相似画廊
23 | - [x] 画廊评论
24 | - [x] 发布评论
25 | - [x] 画廊历史
26 | - [x] 搜索
27 | - [ ] 更多设置
28 |
29 | ## Windows 平台需求
30 | - [WebView2 运行时](https://developer.microsoft.com/zh-CN/microsoft-edge/webview2/)
31 | - [Microsoft Visual C++ 可再发行程序包](https://learn.microsoft.com/zh-CN/cpp/windows/latest-supported-vc-redist?view=msvc-170)
32 | - Windows 10 1809+
33 |
34 | ## 截图
35 | | | | |
36 | |:---------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------:|
37 | | 首页 | 画廊 | 画廊 |
38 | | 评论 | 历史 ||
39 |
40 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | keystore.properties
13 | **/*.keystore
14 | **/*.jks
15 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/debug/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/drawable-v24/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
8 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/android/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/honjow/eros_n/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.honjow.eros_n
2 |
3 | import android.os.Build
4 | import android.os.Bundle
5 | import androidx.core.view.WindowCompat
6 | import io.flutter.embedding.android.FlutterFragmentActivity
7 | import dev.darttools.flutter_android_volume_keydown.FlutterAndroidVolumeKeydownFragmentActivity
8 |
9 | class MainActivity: FlutterAndroidVolumeKeydownFragmentActivity() {
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | WindowCompat.setDecorFitsSystemWindows(getWindow(), false)
12 |
13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
14 | // Disable the Android splash screen fade out animation to avoid
15 | // a flicker before the similar frame is drawn in Flutter.
16 | splashScreen.setOnExitAnimationListener { splashScreenView -> splashScreenView.remove() }
17 | }
18 |
19 | super.onCreate(savedInstanceState)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v24/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.2.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/changelog/v1.0.0+1.md:
--------------------------------------------------------------------------------
1 | # Eros-N
2 | 一个第三方nh客户端
3 | ## 初版发布
4 | - [x] 首页
5 | - [x] 用户登录
6 | - [x] 收藏列表
7 | - [x] 查看画廊
8 | - [ ] 画廊TAG
9 | - [ ] 更多查看模式设置
10 | - [ ] 下载画廊
11 | - [x] 相似画廊
12 | - [ ] 画廊评论
13 | - [ ] 画廊历史
14 | - [ ] 搜索
15 | - [ ] 更多设置
16 |
--------------------------------------------------------------------------------
/changelog/v1.0.10+10.md:
--------------------------------------------------------------------------------
1 | # Fix
2 | - 修正iOS上部分非预期滚动效果
3 | - 优化web登陆响应速度,避免长时间等待
4 | - 优化cf过盾速度
5 | # Feature
6 | - 添加使用封面色调着色功能
7 | - 添加评论发布功能
--------------------------------------------------------------------------------
/changelog/v1.0.11+11.md:
--------------------------------------------------------------------------------
1 | # Fix
2 | - 修正webtoon等垂直阅读模式缩放问题
3 | # Feature
4 | - 外部链接打开画廊功能
5 | - 音量键翻页
6 | - 历史记录搜索
--------------------------------------------------------------------------------
/changelog/v1.0.12+12.md:
--------------------------------------------------------------------------------
1 | # Feature
2 | - 自动阅读
--------------------------------------------------------------------------------
/changelog/v1.0.13+13.md:
--------------------------------------------------------------------------------
1 | # Feature
2 | - 为首页添加语言筛选和排序
3 | ---
4 |
5 | - Add language filtering and sorting to the front page
--------------------------------------------------------------------------------
/changelog/v1.0.14+14.md:
--------------------------------------------------------------------------------
1 | # Feature
2 | - 修复网页登陆问题
3 | ---
4 |
5 | - Fix web login bug
--------------------------------------------------------------------------------
/changelog/v1.0.15+15.md:
--------------------------------------------------------------------------------
1 | # Feature
2 | - 搜索添加语言过滤按钮
3 | - 增加检测剪贴板画廊链接功能(默认关闭)
--------------------------------------------------------------------------------
/changelog/v1.0.16+16.md:
--------------------------------------------------------------------------------
1 | # Feature
2 | - 搜索添加popular_month排序
3 |
4 | ----
5 |
6 | - add popular_month sort
--------------------------------------------------------------------------------
/changelog/v1.0.17+17.md:
--------------------------------------------------------------------------------
1 | - 过滤器切换加载指示
2 | - 优化通过外部url打开画廊的场景
--------------------------------------------------------------------------------
/changelog/v1.0.18+18.md:
--------------------------------------------------------------------------------
1 | ## Fix
2 | - 修复通过外部url冷启动app
3 |
4 | ## Feature
5 | - 画廊页大屏布局,缩略图显示在右侧
--------------------------------------------------------------------------------
/changelog/v1.0.19+19.md:
--------------------------------------------------------------------------------
1 | - 更新Flutter3.7
--------------------------------------------------------------------------------
/changelog/v1.0.2+2.md:
--------------------------------------------------------------------------------
1 | - 修正iOS不能正常载入的问题
2 | - 修正不能从历史记录正常进入画廊的问题
3 | - 中文翻译更新
4 | - 历史记录按天分组
5 | - 其它小调整
6 |
7 | 其余基本功能在慢慢开发中
8 |
--------------------------------------------------------------------------------
/changelog/v1.0.20+20.md:
--------------------------------------------------------------------------------
1 | - 优化过盾检测
--------------------------------------------------------------------------------
/changelog/v1.0.3+3.md:
--------------------------------------------------------------------------------
1 | - 添加画廊分享,种子下载分享
2 | - 添加评论页 (还不能发表)
--------------------------------------------------------------------------------
/changelog/v1.0.4+4.md:
--------------------------------------------------------------------------------
1 | - 画廊页添加tag,设置添加tag翻译开关(默认开关的时候自动下载最新数据库,长按强制更新)
2 |
3 | 接下来的工作是重构部分列表请求逻辑。放假比较懒,慢慢写
--------------------------------------------------------------------------------
/changelog/v1.0.5+5.md:
--------------------------------------------------------------------------------
1 | - 添加搜索页,基本的搜索功能
2 | - 画廊语言标识(仅标注中文,英文)
3 |
4 | 本来打算用nh的api来进行搜索的,为此重构了大部分model,但是发现nh的api有比较奇怪的bug,所以还是用网页的搜索来做
--------------------------------------------------------------------------------
/changelog/v1.0.6+6.md:
--------------------------------------------------------------------------------
1 | - 在画廊列表显示标签(默认关闭)
--------------------------------------------------------------------------------
/changelog/v1.0.7+7.md:
--------------------------------------------------------------------------------
1 | - 优化启用列表显示标签后,列表的滑动流畅性
2 | - 列表标签默认使用Wrap布局,水平布局作为可选切换
--------------------------------------------------------------------------------
/changelog/v1.0.8+8.md:
--------------------------------------------------------------------------------
1 | - 完善搜索,可通过点击标签跳转搜索页
2 | - 调整搜索文本框样式
3 | - 完善阅读页,增加全屏选项,阅读方向设置,滑动控制条等
4 | - 添加深色模式手动设置选项
5 | - 列表布局样式增加列表,网格布局
--------------------------------------------------------------------------------
/changelog/v1.0.9+9.md:
--------------------------------------------------------------------------------
1 | - 搜索标签提示
2 | - 更多主题色
3 | - 应用语言设置
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | $iOSVersion = '11.0'
3 | platform :ios, $iOSVersion
4 |
5 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
6 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
7 |
8 | project 'Runner', {
9 | 'Debug' => :debug,
10 | 'Profile' => :release,
11 | 'Release' => :release,
12 | }
13 |
14 | def flutter_root
15 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
16 | unless File.exist?(generated_xcode_build_settings_path)
17 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
18 | end
19 |
20 | File.foreach(generated_xcode_build_settings_path) do |line|
21 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
22 | return matches[1].strip if matches
23 | end
24 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
25 | end
26 |
27 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
28 |
29 | flutter_ios_podfile_setup
30 |
31 | target 'Runner' do
32 | use_frameworks!
33 | use_modular_headers!
34 |
35 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
36 | end
37 |
38 | post_install do |installer|
39 | installer.pods_project.targets.each do |target|
40 | flutter_additional_ios_build_settings(target)
41 | target.build_configurations.each do |config|
42 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion # required by simple_permission
43 | # config.build_settings['ENABLE_BITCODE'] = 'NO'
44 | # config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
45 | # config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_20pt@2x 1.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "icon_20pt@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "icon_29pt.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "icon_29pt@2x.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "icon_29pt@3x.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "icon_40pt@2x 1.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "icon_40pt@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "icon_60pt@2x.png",
47 | "idiom" : "iphone",
48 | "scale" : "2x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "icon_60pt@3x.png",
53 | "idiom" : "iphone",
54 | "scale" : "3x",
55 | "size" : "60x60"
56 | },
57 | {
58 | "filename" : "icon_20pt.png",
59 | "idiom" : "ipad",
60 | "scale" : "1x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "icon_20pt@2x.png",
65 | "idiom" : "ipad",
66 | "scale" : "2x",
67 | "size" : "20x20"
68 | },
69 | {
70 | "filename" : "icon_29pt 1.png",
71 | "idiom" : "ipad",
72 | "scale" : "1x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "icon_29pt@2x 1.png",
77 | "idiom" : "ipad",
78 | "scale" : "2x",
79 | "size" : "29x29"
80 | },
81 | {
82 | "filename" : "icon_40pt.png",
83 | "idiom" : "ipad",
84 | "scale" : "1x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "icon_40pt@2x.png",
89 | "idiom" : "ipad",
90 | "scale" : "2x",
91 | "size" : "40x40"
92 | },
93 | {
94 | "filename" : "icon_76pt.png",
95 | "idiom" : "ipad",
96 | "scale" : "1x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "icon_76pt@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "76x76"
104 | },
105 | {
106 | "filename" : "icon_83.5@2x.png",
107 | "idiom" : "ipad",
108 | "scale" : "2x",
109 | "size" : "83.5x83.5"
110 | },
111 | {
112 | "filename" : "Icon.png",
113 | "idiom" : "ios-marketing",
114 | "scale" : "1x",
115 | "size" : "1024x1024"
116 | }
117 | ],
118 | "info" : {
119 | "author" : "xcode",
120 | "version" : 1
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x 1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt 1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x 1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x 1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Eros N
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | eros_n
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UIApplicationSupportsIndirectInputEvents
30 |
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | UIMainStoryboardFile
34 | Main
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 | UIViewControllerBasedStatusBarAppearance
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/common/const/const.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:eros_n/store/db/entity/nh_tag.dart';
4 |
5 | class NHConst {
6 | static const String userAgent = 'ErosN';
7 |
8 | static const String accept =
9 | 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9';
10 |
11 | static const String acceptLanguage =
12 | 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6';
13 |
14 | static const String baseUrl = 'https://nhentai.net';
15 | static const String baseHost = 'nhentai.net';
16 |
17 | static const String loginUrl = 'https://nhentai.net/login/';
18 | static const String registerUrl = 'https://nhentai.net/register/';
19 | static const String infoUrl = 'https://nhentai.net/info/';
20 |
21 | // 瀑布流视图参数
22 | // static const double waterfallFlowCrossAxisSpacing = 4.0;
23 | // static const double waterfallFlowMainAxisSpacing = 4.0;
24 | // static const double waterfallFlowMaxCrossAxisExtent = 150.0;
25 | // static const double waterfallFlowMaxCrossAxisExtentTablet = 220.0;
26 |
27 | // 瀑布流视图参数 large
28 | static const double waterfallFlowLargeCrossAxisSpacing = 12.0;
29 | static const double waterfallFlowLargeMainAxisSpacing = 12.0;
30 | static const double waterfallFlowLargeMaxCrossAxisExtent = 190.0;
31 |
32 | // Grid视图参数
33 | static const double gridCrossAxisSpacing = 6.0;
34 | static const double gridMainAxisSpacing = 6.0;
35 | static const double gridMaxCrossAxisExtent = 150.0;
36 | static const double gridChildAspectRatio = 1 / 1.8;
37 |
38 | // 扩展名map
39 | static const Map extMap = {
40 | 'j': 'jpg',
41 | 'p': 'png',
42 | 'g': 'gif',
43 | };
44 |
45 | static const Map localeMap = {
46 | 'en_': 'English',
47 | 'zh_CN': '简体中文'
48 | };
49 |
50 | static List internalNhTags = [
51 | NhTag(
52 | id: 6346,
53 | name: 'japanese',
54 | type: 'language',
55 | ),
56 | NhTag(
57 | id: 12227,
58 | name: 'english',
59 | type: 'language',
60 | ),
61 | NhTag(
62 | id: 29963,
63 | name: 'chinese',
64 | type: 'language',
65 | ),
66 | NhTag(
67 | id: 17249,
68 | name: 'translated',
69 | type: 'language',
70 | ),
71 | ];
72 | }
73 |
--------------------------------------------------------------------------------
/lib/common/enum.dart:
--------------------------------------------------------------------------------
1 | enum TagCategory {
2 | tags('tags'),
3 | artists('artists'),
4 | groups('groups'),
5 | parodies('parodies'),
6 | characters('characters'),
7 | languages('languages');
8 |
9 | const TagCategory(this.value);
10 | final String value;
11 | }
12 |
13 | enum TagLayoutOnItem {
14 | singleLine,
15 |
16 | // row,
17 |
18 | wrap,
19 | }
20 |
21 | enum ReadModel {
22 | leftToRight,
23 | rightToLeft,
24 | curlLeftToRight,
25 | curlRightToLeft,
26 | vertical,
27 | curlVertical,
28 | webtoon,
29 | defaultModel,
30 | }
31 |
32 | enum ListModel {
33 | list,
34 | grid,
35 | waterfall,
36 | waterfallCompact,
37 | }
38 |
39 | enum SearchSort {
40 | popularMonth('popular-month'),
41 | popularWeek('popular-week'),
42 | popularToday('popular-today'),
43 | popular('popular'),
44 | recent('');
45 |
46 | const SearchSort(this.value);
47 |
48 | final String value;
49 | }
50 |
51 | enum LanguagesFilter {
52 | japanese('japanese'),
53 | chinese('chinese'),
54 | english('english'),
55 | translated('translated'),
56 | all('');
57 |
58 | const LanguagesFilter(this.value);
59 | final String value;
60 | }
61 |
--------------------------------------------------------------------------------
/lib/common/extension.dart:
--------------------------------------------------------------------------------
1 | extension DateHelpers on DateTime {
2 | bool isToday() {
3 | final now = DateTime.now();
4 | return now.day == day && now.month == month && now.year == year;
5 | }
6 |
7 | bool isYesterday() {
8 | final yesterday = DateTime.now().subtract(const Duration(days: 1));
9 | return yesterday.day == day &&
10 | yesterday.month == month &&
11 | yesterday.year == year;
12 | }
13 | }
14 |
15 | extension ExtString on String {
16 | String get prettyTitle =>
17 | replaceAll(RegExp(r'(\[.*?\]|\(.*?\))|{.*?}'), '').trim();
18 |
19 | String get processApi => replaceAllMapped(
20 | RegExp(r'"id":\s*"(\d+)",'), (match) => '"id":${match.group(1)},')
21 | .replaceAllMapped(RegExp(r'"media_id":\s+(\d+),'),
22 | (match) => '"id": "${match.group(1)}",')
23 | .replaceAllMapped(
24 | RegExp(r'"w":\s*"(\d+)",'), (match) => '"w":${match.group(1)},')
25 | .replaceAllMapped(
26 | RegExp(r'"h":\s*"(\d+)",'), (match) => '"h":${match.group(1)},')
27 | .replaceAllMapped(RegExp(r'"count":\s*"(\d+)",'),
28 | (match) => '"count":${match.group(1)},');
29 | }
30 |
31 | extension IterableExtensions on Iterable {
32 | Iterable> chunked(int chunkSize) sync* {
33 | if (length <= 0) {
34 | yield [];
35 | return;
36 | }
37 | int skip = 0;
38 | while (skip < length) {
39 | final chunk = this.skip(skip).take(chunkSize);
40 | yield chunk.toList(growable: false);
41 | skip += chunkSize;
42 | if (chunk.length < chunkSize) return;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/common/parser/paese_user_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/component/models/index.dart';
2 | import 'package:eros_n/utils/logger.dart';
3 | import 'package:html/dom.dart';
4 | import 'package:html/parser.dart' show parse;
5 |
6 | User? parseUserPage(String html) {
7 | final Document document = parse(html);
8 |
9 | const avatarSelector = '.bigavatar > img';
10 | final avatarElm = document.querySelector(avatarSelector);
11 | final avatarUrl = avatarElm?.attributes['src'];
12 | return User(
13 | avatarUrl: avatarUrl,
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/lib/common/parser/parse_gallery_image.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/component/models/index.dart';
2 | import 'package:eros_n/utils/logger.dart';
3 | import 'package:html/dom.dart';
4 | import 'package:html/parser.dart' show parse;
5 |
6 | GalleryImage parseGalleryImage(String html) {
7 | final Document document = parse(html);
8 |
9 | const selectorImage = '#image-container > a > img';
10 |
11 | final imageElm = document.querySelector(selectorImage);
12 | final imageUrl = imageElm?.attributes['src'];
13 | final imageHeight = imageElm?.attributes['height'];
14 | final imageWidth = imageElm?.attributes['width'];
15 |
16 | logger.d('imageUrl: $imageUrl');
17 |
18 | return GalleryImage(
19 | imageUrl: imageUrl,
20 | imgHeight: int.tryParse(imageHeight ?? ''),
21 | imgWidth: int.tryParse(imageWidth ?? ''),
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/lib/common/parser/parse_info.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/component/models/index.dart';
2 | import 'package:eros_n/utils/logger.dart';
3 | import 'package:html/dom.dart';
4 | import 'package:html/parser.dart' show parse;
5 |
6 | User? parseInfo(String html) {
7 | final Document document = parse(html);
8 |
9 | const usernameSelector = '.username';
10 | final usernameElm = document.querySelector(usernameSelector);
11 | final username = usernameElm?.text;
12 |
13 | final hrefElem = usernameElm?.parent;
14 | logger.d('hrefElem: ${hrefElem?.outerHtml}');
15 | final href = hrefElem?.attributes['href'];
16 | final userId = RegExp(r'\d+').firstMatch(href ?? '')?.group(0);
17 |
18 | return User(
19 | userName: username,
20 | userId: userId,
21 | userUrl: href,
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/lib/common/parser/parse_login_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/component/exception/error.dart';
2 | import 'package:eros_n/component/models/index.dart';
3 | import 'package:eros_n/utils/logger.dart';
4 | import 'package:html/dom.dart';
5 | import 'package:html/parser.dart' show parse;
6 |
7 | String? parseLoginPage(String html) {
8 | final doc = parse(html);
9 |
10 | // final regExpToken = RegExp(r'csrf_token:\s+"(\w+)"');
11 | // final regExpTokenMatch = regExpToken.firstMatch(html);
12 | // final token = regExpTokenMatch?.group(1);
13 |
14 | const tokenSelector = '#content > form > input[type=hidden]';
15 | final tokenElement = doc.querySelector(tokenSelector);
16 | final tokenValue = tokenElement?.attributes['value'];
17 |
18 | const captchaSelector = '#content > form > div > div.captcha';
19 |
20 | final captchaElement = doc.querySelector(captchaSelector);
21 |
22 | logger.d('captchaElement ${captchaElement?.outerHtml}');
23 |
24 | final error = doc.querySelector('#errors')?.text ?? '';
25 |
26 | if (error.contains('CAPTCHA') || captchaElement != null) {
27 | logger.e('CAPTCHA');
28 | throw LoginCaptchaError();
29 | }
30 |
31 | if (error.contains('Invalid')) {
32 | logger.e('Invalid');
33 | throw LoginInvalidError();
34 | }
35 |
36 | return tokenValue;
37 | }
38 |
--------------------------------------------------------------------------------
/lib/common/parser/parser.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | library parser;
4 |
5 | export 'paese_user_page.dart';
6 | export 'parse_gallery_detail.dart';
7 | export 'parse_gallery_image.dart';
8 | export 'parse_gallery_list.dart';
9 | export 'parse_info.dart';
10 | export 'parse_login_page.dart';
11 |
--------------------------------------------------------------------------------
/lib/common/provider/palette_generator.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:ui';
3 |
4 | import 'package:eros_n/component/widget/eros_cached_network_image.dart';
5 | import 'package:flutter/cupertino.dart';
6 | import 'package:hooks_riverpod/hooks_riverpod.dart';
7 | import 'package:palette_generator/palette_generator.dart';
8 |
9 | final paletteGeneratorProvider = FutureProvider.autoDispose
10 | .family((ref, imageUrl) async {
11 | final imageProvider = ResizeImage(getErosImageProvider(imageUrl), width: 32);
12 |
13 | final completer = Completer();
14 | imageProvider.resolve(const ImageConfiguration()).addListener(
15 | ImageStreamListener(
16 | (imageInfo, _) {
17 | final image = imageInfo.image;
18 | final paletteGenerator = PaletteGenerator.fromImage(
19 | image,
20 | // region: Rect.fromCenter(
21 | // center: Offset(image.width / 2, image.height / 2),
22 | // width: image.width * 1,
23 | // height: image.height * 1,
24 | // ),
25 | );
26 | completer.complete(paletteGenerator);
27 | },
28 | ),
29 | );
30 |
31 | return completer.future;
32 | });
33 |
--------------------------------------------------------------------------------
/lib/common/provider/receive_sharing_provider.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:eros_n/common/global.dart';
4 | import 'package:eros_n/routes/routes.dart';
5 | import 'package:eros_n/utils/get_utils/get_utils.dart';
6 | import 'package:eros_n/utils/logger.dart';
7 | import 'package:hooks_riverpod/hooks_riverpod.dart';
8 | import 'package:receive_sharing_intent/receive_sharing_intent.dart';
9 |
10 | class ReceiveSharingNotifier extends StateNotifier {
11 | ReceiveSharingNotifier(this.ref) : super(null);
12 |
13 | final Ref ref;
14 |
15 | String? sharedText;
16 |
17 | Future _goPage(
18 | String url,
19 | WidgetRef widgetRef, {
20 | bool replace = true,
21 | }) async {
22 | if (url.isEmpty ||
23 | !(await RouteUtil.goGalleryByUrl(widgetRef, url, replace: replace))) {
24 | 0
25 | .milliseconds
26 | .delay()
27 | .then((value) => erosRouter.replaceNamed(NHRoutes.home));
28 | }
29 | }
30 |
31 | void listenReceiveSharing(WidgetRef widgetRef) {
32 | // For sharing or opening urls/text coming from outside the app while the app is in the memory
33 | ReceiveSharingIntent.getTextStream().listen((String value) {
34 | sharedText = value;
35 | logger.e('getTextStream Shared: $sharedText');
36 | _goPage(sharedText ?? '', widgetRef, replace: false);
37 | }, onError: (err) {
38 | logger.e('getTextStream error: $err');
39 | });
40 |
41 | // For sharing or opening urls/text coming from outside the app while the app is closed
42 | ReceiveSharingIntent.getInitialText().then((String? value) {
43 | logger.e('getInitialText Shared: $value');
44 |
45 | if (value == sharedText) {
46 | return;
47 | }
48 |
49 | sharedText = value ?? '';
50 | logger.v('Shared: $sharedText');
51 | _goPage(sharedText ?? '', widgetRef, replace: false);
52 | });
53 | }
54 | }
55 |
56 | final receiveSharingProvider =
57 | StateNotifierProvider.autoDispose((ref) {
58 | return ReceiveSharingNotifier(ref);
59 | });
60 |
--------------------------------------------------------------------------------
/lib/common/provider/theme_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/component/theme/theme.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:hooks_riverpod/hooks_riverpod.dart';
4 |
5 | class ThemeNotifier extends StateNotifier {
6 | ThemeNotifier() : super(ThemeConfig.lightTheme);
7 | }
8 |
9 | final themeProvider = StateNotifierProvider((ref) {
10 | return ThemeNotifier();
11 | });
12 |
--------------------------------------------------------------------------------
/lib/component/converter/locale_converter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 |
5 | class CustomLocaleConverter implements JsonConverter {
6 | const CustomLocaleConverter();
7 |
8 | @override
9 | Locale fromJson(String json) {
10 | final parts = json.split('_');
11 | return Locale(parts[0], parts[1]);
12 | }
13 |
14 | @override
15 | String toJson(Locale object) {
16 | return '${object.languageCode}_${object.countryCode}';
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/component/exception/error.dart:
--------------------------------------------------------------------------------
1 | enum NhErrorType {
2 | def,
3 |
4 | loginCaptcha,
5 |
6 | loginInvalid,
7 | }
8 |
9 | class NhError implements Exception {
10 | NhError({
11 | this.type = NhErrorType.def,
12 | this.error,
13 | });
14 |
15 | NhErrorType type;
16 |
17 | dynamic error;
18 |
19 | String get message => error?.toString() ?? '';
20 |
21 | @override
22 | String toString() {
23 | var msg = 'NhError [$type]: $message';
24 | if (error is Error) {
25 | msg += '\n${error.stackTrace}';
26 | }
27 | return msg;
28 | }
29 | }
30 |
31 | class LoginCaptchaError extends NhError {
32 | LoginCaptchaError() : super(type: NhErrorType.loginCaptcha);
33 | }
34 |
35 | class LoginInvalidError extends NhError {
36 | LoginInvalidError() : super(type: NhErrorType.loginInvalid);
37 | }
38 |
--------------------------------------------------------------------------------
/lib/component/models/comment.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 | import 'comment_poster.dart';
4 |
5 | part 'comment.freezed.dart';
6 | part 'comment.g.dart';
7 |
8 | @freezed
9 | class Comment with _$Comment {
10 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
11 | const factory Comment({
12 | @JsonKey(name: 'id') int? commentId,
13 | @JsonKey(name: 'gallery_id') int? gid,
14 | @JsonKey(name: 'post_date') int? postDate,
15 | @JsonKey(name: 'body') String? commentText,
16 | @JsonKey(name: 'poster') CommentPoster? poster,
17 | }) = _Comment;
18 |
19 | factory Comment.fromJson(Map json) =>
20 | _$CommentFromJson(json);
21 | }
22 |
--------------------------------------------------------------------------------
/lib/component/models/comment.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'comment.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_Comment _$$_CommentFromJson(Map json) => _$_Comment(
10 | commentId: json['id'] as int?,
11 | gid: json['gallery_id'] as int?,
12 | postDate: json['post_date'] as int?,
13 | commentText: json['body'] as String?,
14 | poster: json['poster'] == null
15 | ? null
16 | : CommentPoster.fromJson(json['poster'] as Map),
17 | );
18 |
19 | Map _$$_CommentToJson(_$_Comment instance) =>
20 | {
21 | 'id': instance.commentId,
22 | 'gallery_id': instance.gid,
23 | 'post_date': instance.postDate,
24 | 'body': instance.commentText,
25 | 'poster': instance.poster?.toJson(),
26 | };
27 |
--------------------------------------------------------------------------------
/lib/component/models/comment_poster.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'comment_poster.freezed.dart';
5 | part 'comment_poster.g.dart';
6 |
7 | @freezed
8 | class CommentPoster with _$CommentPoster {
9 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
10 | const factory CommentPoster({
11 | @JsonKey(name: 'id') int? posterId,
12 | @JsonKey(name: 'username') String? username,
13 | @JsonKey(name: 'avatar_url') String? avatarUrl,
14 | @JsonKey(name: 'is_superuser') bool? isSuperuser,
15 | @JsonKey(name: 'is_staff') bool? isStaff,
16 | }) = _CommentPoster;
17 |
18 | factory CommentPoster.fromJson(Map json) =>
19 | _$CommentPosterFromJson(json);
20 | }
21 |
--------------------------------------------------------------------------------
/lib/component/models/comment_poster.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'comment_poster.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_CommentPoster _$$_CommentPosterFromJson(Map json) =>
10 | _$_CommentPoster(
11 | posterId: json['id'] as int?,
12 | username: json['username'] as String?,
13 | avatarUrl: json['avatar_url'] as String?,
14 | isSuperuser: json['is_superuser'] as bool?,
15 | isStaff: json['is_staff'] as bool?,
16 | );
17 |
18 | Map _$$_CommentPosterToJson(_$_CommentPoster instance) =>
19 | {
20 | 'id': instance.posterId,
21 | 'username': instance.username,
22 | 'avatar_url': instance.avatarUrl,
23 | 'is_superuser': instance.isSuperuser,
24 | 'is_staff': instance.isStaff,
25 | };
26 |
--------------------------------------------------------------------------------
/lib/component/models/gallery.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/common/const/const.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | import 'comment.dart';
5 | import 'gallery_images.dart';
6 | import 'gallery_title.dart';
7 | import 'tag.dart';
8 |
9 | part 'gallery.freezed.dart';
10 |
11 | part 'gallery.g.dart';
12 |
13 | @freezed
14 | class Gallery with _$Gallery {
15 | const factory Gallery({
16 | @JsonKey(name: 'id') @Default(0) int gid,
17 | @JsonKey(name: 'media_id') String? mediaId,
18 | @Default(GalleryTitle()) GalleryTitle title,
19 | @Default(GalleryImages()) GalleryImages images,
20 | String? scanlator,
21 | @JsonKey(name: 'upload_date') int? uploadDate,
22 | @JsonKey(name: 'num_pages') int? numPages,
23 | @JsonKey(name: 'num_favorites') int? numFavorites,
24 | @Default([]) List tags,
25 | @JsonKey(ignore: true) @Default([]) List simpleTags,
26 | @JsonKey(ignore: true) String? languageCode,
27 | @JsonKey(ignore: true) int? viewTime,
28 | @JsonKey(ignore: true) @Default([]) List comments,
29 | @JsonKey(ignore: true) @Default([]) List moreLikeGallerys,
30 | @JsonKey(ignore: true) @Default(0) int currentPageIndex,
31 | @JsonKey(ignore: true) bool? isFavorited,
32 | @JsonKey(ignore: true) String? uploadedDateTime,
33 | @JsonKey(ignore: true) String? csrfToken,
34 | }) = _Gallery;
35 |
36 | const Gallery._();
37 |
38 | factory Gallery.fromJson(Map json) =>
39 | _$GalleryFromJson(json);
40 |
41 | @JsonKey(ignore: true)
42 | DateTime? get uploadedDate =>
43 | DateTime.tryParse(uploadedDateTime ?? '')?.toLocal();
44 |
45 | @JsonKey(ignore: true)
46 | String? get thumbUrl => mediaId != null
47 | ? 'https://t.nhentai.net/galleries/$mediaId/thumb.${NHConst.extMap[images.thumbnail.type]}'
48 | : null;
49 | @JsonKey(ignore: true)
50 | String? get coverUrl => mediaId != null
51 | ? 'https://t.nhentai.net/galleries/$mediaId/cover.${NHConst.extMap[images.cover.type]}'
52 | : null;
53 | @JsonKey(ignore: true)
54 | String get url => 'https://nhentai.net/g/$gid/';
55 | }
56 |
--------------------------------------------------------------------------------
/lib/component/models/gallery.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'gallery.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_Gallery _$$_GalleryFromJson(Map json) => _$_Gallery(
10 | gid: json['id'] as int? ?? 0,
11 | mediaId: json['media_id'] as String?,
12 | title: json['title'] == null
13 | ? const GalleryTitle()
14 | : GalleryTitle.fromJson(json['title'] as Map),
15 | images: json['images'] == null
16 | ? const GalleryImages()
17 | : GalleryImages.fromJson(json['images'] as Map),
18 | scanlator: json['scanlator'] as String?,
19 | uploadDate: json['upload_date'] as int?,
20 | numPages: json['num_pages'] as int?,
21 | numFavorites: json['num_favorites'] as int?,
22 | tags: (json['tags'] as List?)
23 | ?.map((e) => Tag.fromJson(e as Map))
24 | .toList() ??
25 | const [],
26 | );
27 |
28 | Map _$$_GalleryToJson(_$_Gallery instance) =>
29 | {
30 | 'id': instance.gid,
31 | 'media_id': instance.mediaId,
32 | 'title': instance.title,
33 | 'images': instance.images,
34 | 'scanlator': instance.scanlator,
35 | 'upload_date': instance.uploadDate,
36 | 'num_pages': instance.numPages,
37 | 'num_favorites': instance.numFavorites,
38 | 'tags': instance.tags,
39 | };
40 |
--------------------------------------------------------------------------------
/lib/component/models/gallery_images.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | import 'image.dart';
5 |
6 | part 'gallery_images.freezed.dart';
7 |
8 | part 'gallery_images.g.dart';
9 |
10 | @freezed
11 | class GalleryImages with _$GalleryImages {
12 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
13 | const factory GalleryImages({
14 | @JsonKey(name: 'pages') @Default([]) List pages,
15 | @JsonKey(name: 'cover') @Default(GalleryImage()) GalleryImage cover,
16 | @JsonKey(name: 'thumbnail') @Default(GalleryImage()) GalleryImage thumbnail,
17 | }) = _GalleryImages;
18 |
19 | factory GalleryImages.fromJson(Map json) =>
20 | _$GalleryImagesFromJson(json);
21 | }
22 |
--------------------------------------------------------------------------------
/lib/component/models/gallery_images.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'gallery_images.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_GalleryImages _$$_GalleryImagesFromJson(Map json) =>
10 | _$_GalleryImages(
11 | pages: (json['pages'] as List?)
12 | ?.map((e) => GalleryImage.fromJson(e as Map))
13 | .toList() ??
14 | const [],
15 | cover: json['cover'] == null
16 | ? const GalleryImage()
17 | : GalleryImage.fromJson(json['cover'] as Map),
18 | thumbnail: json['thumbnail'] == null
19 | ? const GalleryImage()
20 | : GalleryImage.fromJson(json['thumbnail'] as Map),
21 | );
22 |
23 | Map _$$_GalleryImagesToJson(_$_GalleryImages instance) =>
24 | {
25 | 'pages': instance.pages.map((e) => e.toJson()).toList(),
26 | 'cover': instance.cover.toJson(),
27 | 'thumbnail': instance.thumbnail.toJson(),
28 | };
29 |
--------------------------------------------------------------------------------
/lib/component/models/gallery_search.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | import 'gallery.dart';
5 |
6 | part 'gallery_search.freezed.dart';
7 |
8 | part 'gallery_search.g.dart';
9 |
10 | @freezed
11 | class GallerySearch with _$GallerySearch {
12 | // @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
13 | const factory GallerySearch({
14 | @JsonKey(name: 'result') @Default([]) List result,
15 | @JsonKey(name: 'num_pages') int? numPages,
16 | @JsonKey(name: 'per_page') int? perPage,
17 | }) = _GallerySearch;
18 |
19 | factory GallerySearch.fromJson(Map json) =>
20 | _$GallerySearchFromJson(json);
21 | }
22 |
--------------------------------------------------------------------------------
/lib/component/models/gallery_search.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'gallery_search.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_GallerySearch _$$_GallerySearchFromJson(Map json) =>
10 | _$_GallerySearch(
11 | result: (json['result'] as List?)
12 | ?.map((e) => Gallery.fromJson(e as Map))
13 | .toList() ??
14 | const [],
15 | numPages: json['num_pages'] as int?,
16 | perPage: json['per_page'] as int?,
17 | );
18 |
19 | Map _$$_GallerySearchToJson(_$_GallerySearch instance) =>
20 | {
21 | 'result': instance.result,
22 | 'num_pages': instance.numPages,
23 | 'per_page': instance.perPage,
24 | };
25 |
--------------------------------------------------------------------------------
/lib/component/models/gallery_set.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | import 'gallery.dart';
4 |
5 | part 'gallery_set.freezed.dart';
6 |
7 | part 'gallery_set.g.dart';
8 |
9 | @freezed
10 | class GallerySet with _$GallerySet {
11 | const factory GallerySet({
12 | List? gallerys,
13 | List? populars,
14 | List? favorites,
15 | int? maxPage,
16 | int? currentPage,
17 | bool? fromCache,
18 | }) = _GallerySet;
19 |
20 | factory GallerySet.fromJson(Map json) =>
21 | _$GallerySetFromJson(json);
22 | }
23 |
--------------------------------------------------------------------------------
/lib/component/models/gallery_set.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'gallery_set.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_GallerySet _$$_GallerySetFromJson(Map json) =>
10 | _$_GallerySet(
11 | gallerys: (json['gallerys'] as List?)
12 | ?.map((e) => Gallery.fromJson(e as Map))
13 | .toList(),
14 | populars: (json['populars'] as List?)
15 | ?.map((e) => Gallery.fromJson(e as Map))
16 | .toList(),
17 | favorites: (json['favorites'] as List?)
18 | ?.map((e) => Gallery.fromJson(e as Map))
19 | .toList(),
20 | maxPage: json['maxPage'] as int?,
21 | currentPage: json['currentPage'] as int?,
22 | fromCache: json['fromCache'] as bool?,
23 | );
24 |
25 | Map _$$_GallerySetToJson(_$_GallerySet instance) =>
26 | {
27 | 'gallerys': instance.gallerys,
28 | 'populars': instance.populars,
29 | 'favorites': instance.favorites,
30 | 'maxPage': instance.maxPage,
31 | 'currentPage': instance.currentPage,
32 | 'fromCache': instance.fromCache,
33 | };
34 |
--------------------------------------------------------------------------------
/lib/component/models/gallery_title.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'gallery_title.freezed.dart';
5 |
6 | part 'gallery_title.g.dart';
7 |
8 | @freezed
9 | class GalleryTitle with _$GalleryTitle {
10 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
11 | const factory GalleryTitle({
12 | @JsonKey(name: 'english') String? englishTitle,
13 | @JsonKey(name: 'japanese') String? japaneseTitle,
14 | @JsonKey(name: 'pretty') String? prettyTitle,
15 | }) = _GalleryTitle;
16 |
17 | factory GalleryTitle.fromJson(Map json) =>
18 | _$GalleryTitleFromJson(json);
19 | }
20 |
--------------------------------------------------------------------------------
/lib/component/models/gallery_title.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'gallery_title.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_GalleryTitle _$$_GalleryTitleFromJson(Map json) =>
10 | _$_GalleryTitle(
11 | englishTitle: json['english'] as String?,
12 | japaneseTitle: json['japanese'] as String?,
13 | prettyTitle: json['pretty'] as String?,
14 | );
15 |
16 | Map _$$_GalleryTitleToJson(_$_GalleryTitle instance) =>
17 | {
18 | 'english': instance.englishTitle,
19 | 'japanese': instance.japaneseTitle,
20 | 'pretty': instance.prettyTitle,
21 | };
22 |
--------------------------------------------------------------------------------
/lib/component/models/image.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'image.freezed.dart';
5 |
6 | part 'image.g.dart';
7 |
8 | @freezed
9 | class GalleryImage with _$GalleryImage {
10 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
11 | const factory GalleryImage({
12 | @Default('j') @JsonKey(name: 't') String type,
13 | @JsonKey(name: 'h') int? imgHeight,
14 | @JsonKey(name: 'w') int? imgWidth,
15 | @JsonKey(ignore: true) String? imageUrl,
16 | @JsonKey(ignore: true) String? href,
17 | }) = _GalleryImage;
18 |
19 | factory GalleryImage.fromJson(Map json) =>
20 | _$GalleryImageFromJson(json);
21 | }
22 |
--------------------------------------------------------------------------------
/lib/component/models/image.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'image.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_GalleryImage _$$_GalleryImageFromJson(Map json) =>
10 | _$_GalleryImage(
11 | type: json['t'] as String? ?? 'j',
12 | imgHeight: json['h'] as int?,
13 | imgWidth: json['w'] as int?,
14 | );
15 |
16 | Map _$$_GalleryImageToJson(_$_GalleryImage instance) =>
17 | {
18 | 't': instance.type,
19 | 'h': instance.imgHeight,
20 | 'w': instance.imgWidth,
21 | };
22 |
--------------------------------------------------------------------------------
/lib/component/models/index.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | library index;
4 |
5 | export 'comment.dart';
6 | export 'comment_poster.dart';
7 | export 'gallery.dart';
8 | export 'gallery_images.dart';
9 | export 'gallery_search.dart';
10 | export 'gallery_set.dart';
11 | export 'gallery_title.dart';
12 | export 'image.dart';
13 | export 'settings.dart';
14 | export 'tag.dart';
15 | export 'tag_translate_info.dart';
16 | export 'user.dart';
17 |
--------------------------------------------------------------------------------
/lib/component/models/settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/common/enum.dart';
2 | import 'package:eros_n/component/theme/theme.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:freezed_annotation/freezed_annotation.dart';
5 |
6 | part 'settings.freezed.dart';
7 | part 'settings.g.dart';
8 |
9 | @freezed
10 | class Settings with _$Settings {
11 | const factory Settings({
12 | @Default(false) bool isCoverBlur,
13 | @Default(false) bool isTagTranslate,
14 | @Default(true) bool dynamicColor,
15 | @Default(SearchSort.recent) SearchSort searchSort,
16 | @Default(false) bool showTags,
17 | @Default(TagLayoutOnItem.wrap) TagLayoutOnItem tagLayoutOnItem,
18 | @Default(ThemeMode.system) ThemeMode themeMode,
19 | @Default(false) bool fullScreenReader,
20 | @Default(ReadModel.leftToRight) ReadModel readModel,
21 | @Default(ListModel.waterfall) ListModel listModel,
22 | @Default('') String localeCode,
23 | @Default(ThemeConfig.dynamicThemeColorLabel) String themeColorLabel,
24 | @Default(false) bool supportDynamicColors,
25 | @Default(true) bool hideBottomNavigationOnScroll,
26 | @Default(true) bool useGalleryTint,
27 | @Default(false) bool volumeKeyTurnPage,
28 | @Default(2.0) double autoReadInterval,
29 | @Default(3) int preloadPagesCount,
30 | @Default(SearchSort.recent) SearchSort searchSortOnFrontPage,
31 | @Default(LanguagesFilter.all) LanguagesFilter frontLanguagesFilter,
32 | @Default(LanguagesFilter.all) LanguagesFilter searchLanguagesFilter,
33 | @Default(false) bool clipboardDetection,
34 | }) = _Settings;
35 |
36 | const Settings._();
37 |
38 | factory Settings.fromJson(Map json) =>
39 | _$SettingsFromJson(json);
40 |
41 | @JsonKey(ignore: true)
42 | bool get readModelPageView =>
43 | readModel == ReadModel.leftToRight ||
44 | readModel == ReadModel.rightToLeft ||
45 | readModel == ReadModel.vertical;
46 | }
47 |
--------------------------------------------------------------------------------
/lib/component/models/tag.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'tag.freezed.dart';
5 |
6 | part 'tag.g.dart';
7 |
8 | @freezed
9 | class Tag with _$Tag {
10 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
11 | const factory Tag({
12 | int? id,
13 | String? type,
14 | String? name,
15 | String? url,
16 | int? count,
17 | @JsonKey(ignore: true) String? translatedName,
18 | }) = _Tag;
19 |
20 | factory Tag.fromJson(Map json) => _$TagFromJson(json);
21 | }
22 |
--------------------------------------------------------------------------------
/lib/component/models/tag.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'tag.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_Tag _$$_TagFromJson(Map json) => _$_Tag(
10 | id: json['id'] as int?,
11 | type: json['type'] as String?,
12 | name: json['name'] as String?,
13 | url: json['url'] as String?,
14 | count: json['count'] as int?,
15 | );
16 |
17 | Map _$$_TagToJson(_$_Tag instance) => {
18 | 'id': instance.id,
19 | 'type': instance.type,
20 | 'name': instance.name,
21 | 'url': instance.url,
22 | 'count': instance.count,
23 | };
24 |
--------------------------------------------------------------------------------
/lib/component/models/tag_translate_info.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'tag_translate_info.freezed.dart';
5 | part 'tag_translate_info.g.dart';
6 |
7 | @freezed
8 | class TagTranslateInfo with _$TagTranslateInfo {
9 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
10 | const factory TagTranslateInfo({
11 | String? version,
12 | String? remoteVersion,
13 | String? lastReleaseUrl,
14 | String? nhTagVersion,
15 | }) = _TagTranslateInfo;
16 |
17 | factory TagTranslateInfo.fromJson(Map json) =>
18 | _$TagTranslateInfoFromJson(json);
19 | }
20 |
--------------------------------------------------------------------------------
/lib/component/models/tag_translate_info.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'tag_translate_info.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_TagTranslateInfo _$$_TagTranslateInfoFromJson(Map json) =>
10 | _$_TagTranslateInfo(
11 | version: json['version'] as String?,
12 | remoteVersion: json['remote_version'] as String?,
13 | lastReleaseUrl: json['last_release_url'] as String?,
14 | nhTagVersion: json['nh_tag_version'] as String?,
15 | );
16 |
17 | Map _$$_TagTranslateInfoToJson(_$_TagTranslateInfo instance) =>
18 | {
19 | 'version': instance.version,
20 | 'remote_version': instance.remoteVersion,
21 | 'last_release_url': instance.lastReleaseUrl,
22 | 'nh_tag_version': instance.nhTagVersion,
23 | };
24 |
--------------------------------------------------------------------------------
/lib/component/models/user.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'user.freezed.dart';
5 | part 'user.g.dart';
6 |
7 | @freezed
8 | class User with _$User {
9 | const factory User({
10 | String? userName,
11 | String? avatarUrl,
12 | String? userId,
13 | String? userUrl,
14 | String? sessionid,
15 | }) = _User;
16 |
17 | factory User.fromJson(Map json) => _$UserFromJson(json);
18 |
19 | const User._();
20 |
21 | bool get isLogin => sessionid != null && sessionid!.isNotEmpty;
22 | }
23 |
--------------------------------------------------------------------------------
/lib/component/models/user.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'user.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_User _$$_UserFromJson(Map json) => _$_User(
10 | userName: json['userName'] as String?,
11 | avatarUrl: json['avatarUrl'] as String?,
12 | userId: json['userId'] as String?,
13 | userUrl: json['userUrl'] as String?,
14 | sessionid: json['sessionid'] as String?,
15 | );
16 |
17 | Map _$$_UserToJson(_$_User instance) => {
18 | 'userName': instance.userName,
19 | 'avatarUrl': instance.avatarUrl,
20 | 'userId': instance.userId,
21 | 'userUrl': instance.userUrl,
22 | 'sessionid': instance.sessionid,
23 | };
24 |
--------------------------------------------------------------------------------
/lib/component/theme/theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ThemeConfig {
4 | static late ColorScheme lightColorScheme;
5 | static late ColorScheme darkColorScheme;
6 |
7 | static late ThemeData lightTheme;
8 | static late ThemeData darkTheme;
9 |
10 | static const defaultThemeColorLabel = 'blue';
11 | static const dynamicThemeColorLabel = 'dynamic';
12 | static const Map colorMap = {
13 | 'red': Colors.red,
14 | 'pink': Colors.pink,
15 | 'purple': Colors.purple,
16 | 'deep_purple': Colors.deepPurple,
17 | 'indigo': Colors.indigo,
18 | 'blue': Colors.blue,
19 | 'light_blue': Colors.lightBlue,
20 | 'cyan': Colors.cyan,
21 | 'teal': Colors.teal,
22 | 'green': Colors.green,
23 | 'light_green': Colors.lightGreen,
24 | 'lime': Colors.lime,
25 | 'yellow': Colors.yellow,
26 | 'amber': Colors.amber,
27 | 'orange': Colors.orange,
28 | 'deep_orange': Colors.deepOrange,
29 | 'brown': Colors.brown,
30 | 'grey': Colors.grey,
31 | 'blue_grey': Colors.blueGrey,
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/lib/component/widget/blur_image.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/widgets.dart';
5 |
6 | class BlurImage extends StatelessWidget {
7 | const BlurImage({
8 | super.key,
9 | required this.child,
10 | this.blur = true,
11 | this.sigma = 5.0,
12 | this.fit = StackFit.expand,
13 | this.color = Colors.transparent,
14 | });
15 |
16 | final Widget child;
17 | final bool blur;
18 | final double sigma;
19 | final StackFit fit;
20 | final Color color;
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return Stack(
25 | alignment: Alignment.center,
26 | fit: fit,
27 | children: [
28 | Container(child: child),
29 | if (blur)
30 | BackdropFilter(
31 | filter: ImageFilter.blur(
32 | sigmaX: sigma,
33 | sigmaY: sigma,
34 | ),
35 | child: ColoredBox(
36 | color: color,
37 | ),
38 | ),
39 | ],
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/component/widget/gallery_image/gallery_image_platform_interface.dart:
--------------------------------------------------------------------------------
1 | library cached_network_image_platform_interface;
2 |
3 | import 'dart:async';
4 | import 'dart:ui' as ui;
5 |
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter_cache_manager/flutter_cache_manager.dart';
8 |
9 | /// Render options for images on the web platform.
10 | enum ImageRenderMethodForWeb {
11 | /// HtmlImage uses a default web image including default browser caching.
12 | /// This is the recommended and default choice.
13 | HtmlImage, // ignore: constant_identifier_names
14 |
15 | /// HttpGet uses an http client to fetch an image. It enables the use of
16 | /// headers, but loses some default web functionality.
17 | HttpGet, // ignore: constant_identifier_names
18 | }
19 |
20 | /// ImageLoader class to load images differently on various platforms.
21 | class ImageLoader {
22 | /// loads the images async and gives the resulted codecs on a Stream. The
23 | /// Stream gives the option to show multiple images after each other.
24 | Stream loadBufferAsync(
25 | String url,
26 | String? cacheKey,
27 | StreamController chunkEvents,
28 | DecoderBufferCallback decode,
29 | BaseCacheManager cacheManager,
30 | int? maxHeight,
31 | int? maxWidth,
32 | Map? headers,
33 | Function()? errorListener,
34 | ImageRenderMethodForWeb imageRenderMethodForWeb,
35 | Function() evictImage,
36 | ) {
37 | throw UnimplementedError();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/component/widget/scroll.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CustomScrollPhysics extends BouncingScrollPhysics {
4 | const CustomScrollPhysics({ScrollPhysics? parent}) : super(parent: parent);
5 |
6 | @override
7 | CustomScrollPhysics applyTo(ScrollPhysics? ancestor) {
8 | return CustomScrollPhysics(parent: buildParent(ancestor));
9 | }
10 |
11 | @override
12 | SpringDescription get spring => SpringDescription.withDampingRatio(
13 | mass: 0.5,
14 | stiffness: 300.0, // Increase this value as you wish.
15 | ratio: 1.1,
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/lib/component/widget/scrolling_fab.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/utils/logger.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/rendering.dart';
4 |
5 | class ScrollingFab extends StatefulWidget {
6 | const ScrollingFab({
7 | Key? key,
8 | this.duration,
9 | this.curve,
10 | this.scrollController,
11 | required this.label,
12 | required this.icon,
13 | this.extendedIconLabelSpacing,
14 | this.onPressed,
15 | }) : super(key: key);
16 |
17 | final Duration? duration;
18 | final Curve? curve;
19 | final ScrollController? scrollController;
20 | final double? extendedIconLabelSpacing;
21 | final Widget label;
22 | final Widget icon;
23 | final VoidCallback? onPressed;
24 |
25 | @override
26 | State createState() => _ScrollingFabState();
27 | }
28 |
29 | class _ScrollingFabState extends State {
30 | late bool _isExtended;
31 |
32 | void _scrollListener() {
33 | if (widget.scrollController!.position.userScrollDirection ==
34 | ScrollDirection.reverse) {
35 | if (!mounted || !_isExtended) {
36 | return;
37 | }
38 | logger.v('ScrollingFab: ScrollDirection.reverse');
39 | setState(() {
40 | _isExtended = false;
41 | });
42 | }
43 | if (widget.scrollController!.position.userScrollDirection ==
44 | ScrollDirection.forward) {
45 | if (!mounted || _isExtended) {
46 | return;
47 | }
48 | logger.v('ScrollingFab: _scrollListener: forward');
49 | setState(() {
50 | _isExtended = true;
51 | });
52 | }
53 | }
54 |
55 | @override
56 | void initState() {
57 | super.initState();
58 | _isExtended = true;
59 | widget.scrollController?.addListener(_scrollListener);
60 | }
61 |
62 | @override
63 | void dispose() {
64 | widget.scrollController?.removeListener(_scrollListener);
65 | super.dispose();
66 | }
67 |
68 | @override
69 | Widget build(BuildContext context) {
70 | final labelSizeAnimation = AnimatedSize(
71 | duration: widget.duration ?? const Duration(milliseconds: 300),
72 | curve: widget.curve ?? Curves.easeInOut,
73 | child: SizedBox(
74 | width: _isExtended ? null : 0,
75 | child: _isExtended ? widget.label : const SizedBox(),
76 | ),
77 | );
78 |
79 | return AnimatedSize(
80 | duration: widget.duration ?? const Duration(milliseconds: 300),
81 | curve: widget.curve ?? Curves.easeInOut,
82 | child: FloatingActionButton.extended(
83 | onPressed: widget.onPressed,
84 | label: labelSizeAnimation,
85 | icon: widget.icon,
86 | isExtended: true,
87 | extendedIconLabelSpacing:
88 | _isExtended ? widget.extendedIconLabelSpacing : 0,
89 | extendedPadding: _isExtended
90 | ? const EdgeInsetsDirectional.only(start: 16, end: 20)
91 | : const EdgeInsetsDirectional.only(start: 16, end: 16),
92 | ),
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/component/widget/sliver.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../utils/logger.dart';
4 |
5 | class SGDelegatePack {
6 | SGDelegatePack(this.gridDelegate, this.size);
7 | SliverGridDelegateWithFixedCrossAxisCount gridDelegate;
8 | Size size;
9 | }
10 |
11 | SGDelegatePack sliverGridDelegateWithMaxToCount(
12 | double width, SliverGridDelegateWithMaxCrossAxisExtent sliverGridDelegate,
13 | [double extendHeight = 0]) {
14 | int crossAxisCount = ((width + sliverGridDelegate.crossAxisSpacing) /
15 | (sliverGridDelegate.maxCrossAxisExtent +
16 | sliverGridDelegate.crossAxisSpacing))
17 | .ceil();
18 |
19 | final double usableCrossAxisExtent =
20 | width - sliverGridDelegate.crossAxisSpacing * (crossAxisCount - 1);
21 | if (crossAxisCount < 1) {
22 | crossAxisCount = 1;
23 | }
24 | final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
25 | double childMainAxisExtent =
26 | childCrossAxisExtent / sliverGridDelegate.childAspectRatio;
27 | childMainAxisExtent += extendHeight;
28 | final childAspectRatio = childCrossAxisExtent / childMainAxisExtent;
29 | return SGDelegatePack(
30 | SliverGridDelegateWithFixedCrossAxisCount(
31 | crossAxisCount: crossAxisCount,
32 | mainAxisSpacing: sliverGridDelegate.mainAxisSpacing,
33 | crossAxisSpacing: sliverGridDelegate.crossAxisSpacing,
34 | childAspectRatio: childAspectRatio,
35 | ),
36 | Size(childCrossAxisExtent, childMainAxisExtent));
37 | }
38 |
--------------------------------------------------------------------------------
/lib/component/widget/sys_title.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/foundation.dart';
5 | import 'package:flutter/services.dart';
6 |
7 | // todo: Windows 标题
8 | // 没有找到好的方案 暂时搁置
9 |
10 | class DesktopTitle {
11 | DesktopTitle(this.title, {
12 | this.titleVisible = true,
13 | this.buttonVisible = true,
14 | });
15 | final String title;
16 | final bool titleVisible;
17 | final bool buttonVisible;
18 | }
19 |
20 | class SysTitle extends StatefulWidget {
21 |
22 | SysTitle({
23 | required this.title,
24 | required this.child,
25 | this.desktopTitleVisible = true,
26 | this.desktopButtonVisible = true,
27 | this.color,
28 | super.key
29 | });
30 |
31 | final bool desktopTitleVisible;
32 | final bool desktopButtonVisible;
33 |
34 | static DesktopTitle? current;
35 | static final StreamController _currentController = StreamController.broadcast();
36 | static Stream get currentStream => _currentController.stream;
37 |
38 |
39 | /// A one-line description of this app for use in the window manager.
40 | /// Must not be null.
41 | final String title;
42 |
43 | /// A color that the window manager should use to identify this app. Must be
44 | /// an opaque color (i.e. color.alpha must be 255 (0xFF)), and must not be
45 | /// null.
46 | final Color? color;
47 |
48 | /// The widget below this widget in the tree.
49 | ///
50 | /// {@macro flutter.widgets.ProxyWidget.child}
51 | final Widget child;
52 |
53 | @override
54 | State createState() => _SysTitleState();
55 | }
56 |
57 | class _SysTitleState extends State {
58 | @override
59 | Widget build(BuildContext context) {
60 | final desktopTitle = DesktopTitle(widget.title, titleVisible: widget.desktopTitleVisible, buttonVisible: widget.desktopButtonVisible);
61 | SysTitle.current = desktopTitle;
62 | SysTitle._currentController.sink.add(desktopTitle);
63 | SystemChrome.setApplicationSwitcherDescription(
64 | ApplicationSwitcherDescription(
65 | label: widget.title,
66 | primaryColor: widget.color?.value,
67 | ),
68 | );
69 | return widget.child;
70 | }
71 |
72 | @override
73 | void debugFillProperties(DiagnosticPropertiesBuilder properties) {
74 | super.debugFillProperties(properties);
75 | properties.add(StringProperty('title', widget.title, defaultValue: ''));
76 | properties.add(ColorProperty('color', widget.color, defaultValue: null));
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/component/widget/system_ui_overlay.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 |
5 | class SystemUIOverlay extends StatelessWidget {
6 | const SystemUIOverlay({Key? key, this.child}) : super(key: key);
7 | final Widget? child;
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | final brightnessLight = Theme.of(context).brightness == Brightness.light;
12 |
13 | return AnnotatedRegion(
14 | value: SystemUiOverlayStyle(
15 | systemNavigationBarColor: Colors.transparent,
16 | systemNavigationBarDividerColor: Colors.transparent,
17 | statusBarColor: Colors.transparent,
18 | systemNavigationBarContrastEnforced: false,
19 | systemNavigationBarIconBrightness:
20 | brightnessLight ? Brightness.dark : Brightness.light,
21 | ),
22 | child: child ?? Container(),
23 | );
24 | }
25 |
26 | static TransitionBuilder? init({TransitionBuilder? builder}) {
27 | return (BuildContext context, Widget? child) => SystemUIOverlay(
28 | child: builder?.call(context, child),
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/generated/intl/messages_all.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that looks up messages for specific locales by
3 | // delegating to the appropriate library.
4 |
5 | // Ignore issues from commonly used lints in this file.
6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new
7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment
9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
10 | // ignore_for_file:comment_references
11 |
12 | import 'dart:async';
13 |
14 | import 'package:flutter/foundation.dart';
15 | import 'package:intl/intl.dart';
16 | import 'package:intl/message_lookup_by_library.dart';
17 | import 'package:intl/src/intl_helpers.dart';
18 |
19 | import 'messages_en.dart' as messages_en;
20 | import 'messages_zh_CN.dart' as messages_zh_cn;
21 |
22 | typedef Future LibraryLoader();
23 | Map _deferredLibraries = {
24 | 'en': () => new SynchronousFuture(null),
25 | 'zh_CN': () => new SynchronousFuture(null),
26 | };
27 |
28 | MessageLookupByLibrary? _findExact(String localeName) {
29 | switch (localeName) {
30 | case 'en':
31 | return messages_en.messages;
32 | case 'zh_CN':
33 | return messages_zh_cn.messages;
34 | default:
35 | return null;
36 | }
37 | }
38 |
39 | /// User programs should call this before using [localeName] for messages.
40 | Future initializeMessages(String localeName) {
41 | var availableLocale = Intl.verifiedLocale(
42 | localeName, (locale) => _deferredLibraries[locale] != null,
43 | onFailure: (_) => null);
44 | if (availableLocale == null) {
45 | return new SynchronousFuture(false);
46 | }
47 | var lib = _deferredLibraries[availableLocale];
48 | lib == null ? new SynchronousFuture(false) : lib();
49 | initializeInternalMessageLookup(() => new CompositeMessageLookup());
50 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
51 | return new SynchronousFuture(true);
52 | }
53 |
54 | bool _messagesExistFor(String locale) {
55 | try {
56 | return _findExact(locale) != null;
57 | } catch (e) {
58 | return false;
59 | }
60 | }
61 |
62 | MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
63 | var actualLocale =
64 | Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
65 | if (actualLocale == null) return null;
66 | return _findExact(actualLocale);
67 | }
68 |
--------------------------------------------------------------------------------
/lib/l10n/intl_zh_CN.arb:
--------------------------------------------------------------------------------
1 | {
2 | "language_name": "简体中文",
3 | "app_title": "Eros-N",
4 | "popular": "热门",
5 | "gallery": "画廊",
6 | "newest": "最新",
7 | "home": "主页",
8 | "favorites": "收藏",
9 | "history": "历史",
10 | "more": "更多",
11 | "search": "搜索",
12 | "settings": "设置",
13 | "appearance": "外观",
14 | "about": "关于",
15 | "download": "下载",
16 | "login": "登录",
17 | "logout": "登出",
18 | "register": "注册",
19 | "username": "用户名",
20 | "password": "密码",
21 | "login_by_web": "通过网页登陆",
22 | "today": "今天",
23 | "yesterday": "昨天",
24 | "thumbs": "缩略图",
25 | "more_like_this": "更多相似",
26 | "read": "阅读",
27 | "resume": "继续",
28 | "share": "分享",
29 | "please_login_first": "请先登录",
30 | "login_need_captcha": "登录需要验证码",
31 | "login_invalid": "无效登录信息",
32 | "please_login_web": "请通过网页登录",
33 | "ok": "确定",
34 | "cancel": "取消",
35 | "never": "从不",
36 | "always": "始终",
37 | "auto": "自动",
38 | "comments": "评论",
39 | "tag_type_parodies": "原作",
40 | "tag_type_characters": "角色",
41 | "tag_type_tags": "标签",
42 | "tag_type_artists": "作者",
43 | "tag_type_groups": "团队",
44 | "tag_type_languages": "语言",
45 | "tag_type_categories": "类别",
46 | "recent": "最新发布",
47 | "popular_today": "本日热门",
48 | "popular_week": "本周热门",
49 | "popular_month": "本月热门",
50 | "popular_all": "所有热门",
51 | "clear_history": "清除历史",
52 | "clear_history_tip": "确定要清除所有历史记录吗?",
53 | "left_to_right": "从左到右",
54 | "right_to_left": "从右到左",
55 | "webtoon": "条漫",
56 | "vertical": "垂直",
57 | "curl_vertical": "垂直(连续)",
58 | "full_screen": "全屏",
59 | "display": "显示",
60 | "theme_mode": "主题模式",
61 | "light": "浅色",
62 | "dark": "深色",
63 | "system": "系统",
64 | "dynamic_color": "动态颜色",
65 | "dynamic_color_tip": "根据壁纸自动调整主题颜色",
66 | "theme": "主题",
67 | "list_style": "列表样式",
68 | "tag_translation": "标签翻译",
69 | "tag_translation_tip": "需要下载翻译数据,当前版本: {version}",
70 | "tags_data": "标签数据",
71 | "show_tags": "显示标签",
72 | "show_tags_tip": "在列表中显示标签, 需要下载额外标签数据",
73 | "tag_layout_on_item": "列表项标签布局",
74 | "wrap": "换行",
75 | "single_line": "单行",
76 | "cover_blur": "封面模糊",
77 | "list_model": "列表模式",
78 | "list": "列表",
79 | "grid": "网格",
80 | "waterfall": "瀑布流",
81 | "waterfall_compact": "瀑布流 (紧凑)",
82 | "advanced": "高级",
83 | "tag": "标签",
84 | "read_model": "阅读模式",
85 | "general": "通用",
86 | "language": "语言",
87 | "hide_bottom_navigation_on_scroll": "滚动时隐藏底部导航栏",
88 | "use_gallery_tint": "使用画廊色调",
89 | "use_gallery_tint_tip": "根据封面的颜色给画廊页面着色",
90 | "comment_length_error": "评论内容需要超过10个字符",
91 | "open_supported_links": "打开支持的链接",
92 | "open_supported_links_tip": "从 Android 12 开始, 应用只有在获得许可的情况下,才能作为网络链接的处理应用。否则会使用默认浏览器处理。您可以在此手动许可",
93 | "volume_key_turn_page": "音量键翻页",
94 | "auto_read_interval": "自动阅读间隔",
95 | "preload_pages_count": "预载页数",
96 | "japanese": "日语",
97 | "chinese": "汉语",
98 | "english": "英语",
99 | "translated": "翻译",
100 | "none": "无",
101 | "all": "全部",
102 | "clipboard_detection": "剪贴板链接检测"
103 | }
--------------------------------------------------------------------------------
/lib/network/api.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
2 | import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
3 | import 'package:eros_n/common/global.dart';
4 |
5 | class Api {
6 | static CacheOptions cacheOption = CacheOptions(
7 | store: BackupCacheStore(
8 | primary: MemCacheStore(),
9 | secondary: HiveCacheStore(Global.appSupportPath),
10 | ),
11 | policy: CachePolicy.forceCache,
12 | hitCacheOnErrorExcept: [401, 403, 304, 503],
13 | maxStale: const Duration(days: 1),
14 | priority: CachePriority.normal,
15 | cipher: null,
16 | keyBuilder: CacheOptions.defaultCacheKeyBuilder,
17 | allowPostMethod: false,
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/lib/network/app_dio/dio_file_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:eros_n/common/global.dart';
3 | import 'package:flutter_cache_manager/flutter_cache_manager.dart';
4 | import 'package:dio/dio.dart';
5 | import 'package:flutter_cache_manager/src/web/mime_converter.dart';
6 |
7 | import '../../network/app_dio/dio_http_cli.dart';
8 | import 'package:clock/clock.dart';
9 |
10 | import '../../network/app_dio/http_response.dart';
11 | import '../../network/app_dio/http_transformer.dart';
12 |
13 |
14 | class DioFileService extends FileService {
15 | DioFileService();
16 |
17 | @override
18 | Future get(String url,
19 | {Map? headers}) async {
20 | DioHttpClient dioHttpClient = DioHttpClient(dioConfig: globalDioConfig);
21 |
22 | final req = await dioHttpClient.get(url,
23 | options: Options(
24 | headers: headers,
25 | responseType: ResponseType.stream,
26 | ), httpTransformer: HttpTransformerBuilder(
27 | (response) {
28 | return DioHttpResponse.success(response);
29 | },
30 | ));
31 |
32 | return DioGetResponse(req.data as Response);
33 | }
34 | }
35 |
36 | class DioGetResponse implements FileServiceResponse {
37 | DioGetResponse(this._response);
38 |
39 | final DateTime _receivedTime = clock.now();
40 |
41 | final Response _response;
42 |
43 | @override
44 | int get statusCode => _response.statusCode!;
45 |
46 | String? _header(String name) {
47 | return _response.headers.map[name]?.first;
48 | }
49 |
50 | @override
51 | Stream> get content {
52 | return (_response.data as ResponseBody).stream.cast>();
53 | }
54 |
55 | @override
56 | int? get contentLength {
57 | final contentLength = _response.headers[Headers.contentLengthHeader]?.first;
58 | return contentLength != null ? int.tryParse(contentLength) : null;
59 | }
60 |
61 | @override
62 | DateTime get validTill {
63 | // Without a cache-control header we keep the file for a week
64 | var ageDuration = const Duration(days: 7);
65 | final controlHeader = _header(HttpHeaders.cacheControlHeader);
66 | if (controlHeader != null) {
67 | final controlSettings = controlHeader.split(',');
68 | for (final setting in controlSettings) {
69 | final sanitizedSetting = setting.trim().toLowerCase();
70 | if (sanitizedSetting == 'no-cache') {
71 | ageDuration = const Duration();
72 | }
73 | if (sanitizedSetting.startsWith('max-age=')) {
74 | var validSeconds = int.tryParse(sanitizedSetting.split('=')[1]) ?? 0;
75 | if (validSeconds > 0) {
76 | ageDuration = Duration(seconds: validSeconds);
77 | }
78 | }
79 | }
80 | }
81 |
82 | return _receivedTime.add(ageDuration);
83 | }
84 |
85 | @override
86 | String? get eTag => _header(HttpHeaders.etagHeader);
87 |
88 | @override
89 | String get fileExtension {
90 | var fileExtension = '';
91 | final contentTypeHeader = _header(HttpHeaders.contentTypeHeader);
92 | if (contentTypeHeader != null) {
93 | final contentType = ContentType.parse(contentTypeHeader);
94 | fileExtension = contentType.fileExtension;
95 | }
96 | return fileExtension;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/lib/network/app_dio/dio_through.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:dio/dio.dart';
4 |
5 | class DioThroughInterceptor extends Interceptor {
6 | DioThroughInterceptor({required this.dio, required this.throughHandler});
7 |
8 | Dio dio;
9 | Future Function(DioError err) throughHandler;
10 |
11 | @override
12 | Future onError(DioError err, ErrorInterceptorHandler handler) async {
13 | if (!err.requestOptions.disableThrough) {
14 | if (err.response?.statusCode == 403 || err.response?.statusCode == 503) {
15 | print('DioThroughInterceptor onError');
16 | print(err);
17 | final ok = await throughHandler(err);
18 | if (ok) {
19 | final headers = err.requestOptions.headers;
20 | headers.remove(HttpHeaders.cookieHeader);
21 | final request = err.requestOptions.copyWith(headers: headers);
22 | await dio
23 | .fetch(request)
24 | .then((value) => handler.resolve(value));
25 | return;
26 | }
27 | }
28 | }
29 | return super.onError(err, handler);
30 | }
31 | }
32 |
33 | const _disableThroughKey = '__dio_through_disable';
34 |
35 | extension RequestOptionsThrough on RequestOptions {
36 | bool get disableThrough => (extra[_disableThroughKey] as bool?) ?? false;
37 |
38 | set disableThrough(bool value) => extra[_disableThroughKey] = value;
39 | }
40 |
41 | extension OptionsThrough on Options {
42 | bool get disableRetry => (extra?[_disableThroughKey] as bool?) ?? false;
43 |
44 | set disableRetry(bool value) => extra?[_disableThroughKey] = value;
45 | }
46 |
--------------------------------------------------------------------------------
/lib/network/app_dio/dio_user_agent.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 |
4 | import 'package:dio/dio.dart';
5 |
6 | class DioUserAgentInterceptor extends Interceptor {
7 | DioUserAgentInterceptor(this.getUserAgent);
8 | FutureOr Function(RequestOptions) getUserAgent;
9 |
10 | @override
11 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
12 | final ua = await getUserAgent(options);
13 | if(ua != null) {
14 | options.headers[HttpHeaders.userAgentHeader] = ua;
15 | }
16 | handler.next(options);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/lib/network/app_dio/exception.dart:
--------------------------------------------------------------------------------
1 | class HttpException implements Exception {
2 | HttpException([this._message, this._code, this._data]);
3 |
4 | final String? _message;
5 |
6 | String get message => _message ?? runtimeType.toString();
7 |
8 | final int? _code;
9 |
10 | int get code => _code ?? -1;
11 |
12 | final dynamic _data;
13 | dynamic get data => _data;
14 |
15 | @override
16 | String toString() {
17 | return 'HttpException{_message: $_message, _code: $_code, _data: $_data}';
18 | }
19 | }
20 |
21 | /// 客户端请求错误
22 | class BadRequestException extends HttpException {
23 | BadRequestException({String? message, int? code, dynamic data})
24 | : super(message, code, data);
25 | }
26 |
27 | /// 服务端响应错误
28 | class BadServiceException extends HttpException {
29 | BadServiceException({String? message, int? code}) : super(message, code);
30 | }
31 |
32 | class UnknownException extends HttpException {
33 | UnknownException([String? message]) : super(message);
34 | }
35 |
36 | class CancelException extends HttpException {
37 | CancelException([String? message]) : super(message);
38 | }
39 |
40 | class Image509Exception extends HttpException {
41 | Image509Exception([String? message]) : super(message);
42 | }
43 |
44 | class NetworkException extends HttpException {
45 | NetworkException({String? message, int? code}) : super(message, code);
46 | }
47 |
48 | /// 401
49 | class UnauthorisedException extends HttpException {
50 | UnauthorisedException({String? message, int? code = 401})
51 | : super(message, code);
52 | }
53 |
54 | class BadResponseException extends HttpException {
55 | BadResponseException([this.data]) : super();
56 |
57 | dynamic data;
58 | }
59 |
60 | /// 列表样式
61 | class ListDisplayModeException extends HttpException {
62 | ListDisplayModeException({String? message, int? code, this.params})
63 | : super(message, code);
64 | Map? params;
65 | }
66 |
67 | class FavOrderException extends HttpException {
68 | FavOrderException({String? message, int? code, this.order})
69 | : super(message, code);
70 | String? order;
71 | }
72 |
--------------------------------------------------------------------------------
/lib/network/app_dio/http_transformer.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:dio/dio.dart';
4 |
5 | import 'http_response.dart';
6 |
7 | typedef HttpTransformerBuilderCallback = FutureOr Function(
8 | Response response);
9 |
10 | /// Response 解析
11 | abstract class HttpTransformer {
12 | FutureOr> parse(Response response);
13 | }
14 |
15 | class DefaultHttpTransformer extends HttpTransformer {
16 | // 假设接口返回类型
17 | // {
18 | // "code": 100,
19 | // "data": {},
20 | // "message": "success"
21 | // }
22 | /// 内部构造方法,可避免外部暴露构造函数,进行实例化
23 | DefaultHttpTransformer._internal();
24 |
25 | /// 工厂构造方法,这里使用命名构造函数方式进行声明
26 | factory DefaultHttpTransformer.getInstance() => _instance;
27 |
28 | /// 单例对象
29 | static final DefaultHttpTransformer _instance =
30 | DefaultHttpTransformer._internal();
31 |
32 | @override
33 | FutureOr> parse(Response response) {
34 | // if (response.data["code"] == 100) {
35 | // return HttpResponse.success(response.data["data"]);
36 | // } else {
37 | // return HttpResponse.failure(errorMsg:response.data["message"],errorCode: response.data["code"]);
38 | // }
39 | // return DioHttpResponse.success(response.data['data']);
40 | return DioHttpResponse.success(response.data);
41 | }
42 | }
43 |
44 | class HttpTransformerBuilder extends HttpTransformer {
45 | HttpTransformerBuilder(this.parseCallback);
46 |
47 | final HttpTransformerBuilderCallback parseCallback;
48 |
49 | @override
50 | FutureOr parse(Response response) {
51 | return parseCallback(response);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/network/app_dio/pdio.dart:
--------------------------------------------------------------------------------
1 | export 'app_dio.dart';
2 | export 'dio_http_cli.dart';
3 | export 'exception.dart';
4 | export 'http_config.dart';
5 | export 'http_response.dart';
6 | export 'http_transformer.dart';
7 |
--------------------------------------------------------------------------------
/lib/pages/enum.dart:
--------------------------------------------------------------------------------
1 | enum LoadStatus {
2 | none,
3 | loading,
4 | loadingMore,
5 | empty,
6 | success,
7 | error,
8 | getToken,
9 | }
10 |
11 | enum PageStatus {
12 | none,
13 | loading,
14 | empty,
15 | error,
16 | }
17 |
--------------------------------------------------------------------------------
/lib/pages/gallery/gallery_page_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/pages/enum.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'gallery_page_state.freezed.dart';
5 |
6 | @freezed
7 | class GalleryViewState with _$GalleryViewState {
8 | const factory GalleryViewState({
9 | PageStatus? pageStatus,
10 | @Default(true) bool appBartTansparent,
11 | }) = _GalleryViewState;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/pages/nav/front/list_view_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | import '../../enum.dart';
4 |
5 | part 'list_view_state.freezed.dart';
6 | part 'list_view_state.g.dart';
7 |
8 | @freezed
9 | class ListViewState with _$ListViewState {
10 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
11 | const factory ListViewState({
12 | @Default(LoadStatus.none) LoadStatus status,
13 | @Default(1) int curPage,
14 | @Default(1) int maxPage,
15 | @Default(true) bool floatingAppBar,
16 | String? errorMessage,
17 | @Default(false) bool appBarSearch,
18 | }) = _ListViewState;
19 |
20 | factory ListViewState.fromJson(Map json) =>
21 | _$ListViewStateFromJson(json);
22 |
23 | const ListViewState._();
24 |
25 | bool get isNone => status == LoadStatus.none;
26 | bool get isLoadMore => status == LoadStatus.loadingMore;
27 | bool get isLoading => status == LoadStatus.loading;
28 | bool get isLoadEmpty => status == LoadStatus.empty;
29 | bool get isLoadError => status == LoadStatus.error;
30 | bool get isLoadSuccess => status == LoadStatus.success;
31 | }
32 |
--------------------------------------------------------------------------------
/lib/pages/nav/front/list_view_state.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'list_view_state.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_ListViewState _$$_ListViewStateFromJson(Map json) =>
10 | _$_ListViewState(
11 | status: $enumDecodeNullable(_$LoadStatusEnumMap, json['status']) ??
12 | LoadStatus.none,
13 | curPage: json['cur_page'] as int? ?? 1,
14 | maxPage: json['max_page'] as int? ?? 1,
15 | floatingAppBar: json['floating_app_bar'] as bool? ?? true,
16 | errorMessage: json['error_message'] as String?,
17 | appBarSearch: json['app_bar_search'] as bool? ?? false,
18 | );
19 |
20 | Map _$$_ListViewStateToJson(_$_ListViewState instance) =>
21 | {
22 | 'status': _$LoadStatusEnumMap[instance.status]!,
23 | 'cur_page': instance.curPage,
24 | 'max_page': instance.maxPage,
25 | 'floating_app_bar': instance.floatingAppBar,
26 | 'error_message': instance.errorMessage,
27 | 'app_bar_search': instance.appBarSearch,
28 | };
29 |
30 | const _$LoadStatusEnumMap = {
31 | LoadStatus.none: 'none',
32 | LoadStatus.loading: 'loading',
33 | LoadStatus.loadingMore: 'loadingMore',
34 | LoadStatus.empty: 'empty',
35 | LoadStatus.success: 'success',
36 | LoadStatus.error: 'error',
37 | LoadStatus.getToken: 'getToken',
38 | };
39 |
--------------------------------------------------------------------------------
/lib/pages/nav/index/index_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/common/provider/settings_provider.dart';
2 | import 'package:eros_n/utils/get_utils/extensions/export.dart';
3 | import 'package:eros_n/utils/logger.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:hooks_riverpod/hooks_riverpod.dart';
6 |
7 | import 'index_state.dart';
8 |
9 | class IndexNotifier extends StateNotifier {
10 | IndexNotifier(this.ref) : super(IndexState());
11 |
12 | final Map scrollControllerMap = {};
13 | bool tapAwait = false;
14 |
15 | final Ref ref;
16 |
17 | Future setIndex(int index,
18 | {required BuildContext context, bool jumpToPage = false}) async {
19 | if (index == state.selectedIndex) {
20 | // logger.d('state.scrollControllerMap len ${scrollControllerMap.length}');
21 | await doubleTapBar(
22 | duration: const Duration(milliseconds: 800),
23 | awaitComplete: false,
24 | onTap: () {
25 | scrollControllerMap[state.selectedIndex]
26 | ?.animateTo(0, duration: 300.milliseconds, curve: Curves.ease);
27 | },
28 | );
29 | }
30 |
31 | if (jumpToPage) {
32 | state.pageController.jumpToPage(index);
33 | }
34 | state = state.copyWith(selectedIndex: index);
35 | }
36 |
37 | void hideNavigationBar() {
38 | final hideBottomNavigationOnScroll = ref.read(settingsProvider
39 | .select((settings) => settings.hideBottomNavigationOnScroll));
40 | if (!state.hideNavigationBar && hideBottomNavigationOnScroll) {
41 | state = state.copyWith(hideNavigationBar: true);
42 | }
43 | }
44 |
45 | void showNavigationBar() {
46 | if (state.hideNavigationBar) {
47 | state = state.copyWith(hideNavigationBar: false);
48 | }
49 | }
50 |
51 | void addScrollController(ScrollController scrollController) {
52 | scrollControllerMap[state.selectedIndex] = scrollController;
53 | logger.v('state.scrollControllerMap len ${scrollControllerMap.length}');
54 | }
55 |
56 | /// 双击bar的处理
57 | Future doubleTapBar({
58 | required VoidCallback onTap,
59 | VoidCallback? onDoubleTap,
60 | required Duration duration,
61 | required bool awaitComplete,
62 | }) async {
63 | if (!tapAwait) {
64 | tapAwait = true;
65 |
66 | if (awaitComplete) {
67 | await Future.delayed(duration);
68 | if (tapAwait) {
69 | // loggerNoStack.v('等待结束 执行单击事件');
70 | tapAwait = false;
71 | onTap();
72 | }
73 | } else {
74 | onTap();
75 | await Future.delayed(duration);
76 | tapAwait = false;
77 | }
78 | } else {
79 | tapAwait = false;
80 | onDoubleTap?.call();
81 | }
82 | }
83 | }
84 |
85 | final indexProvider = StateNotifierProvider((ref) {
86 | return IndexNotifier(ref);
87 | });
88 |
--------------------------------------------------------------------------------
/lib/pages/nav/index/index_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | @immutable
4 | class IndexState {
5 | IndexState({this.selectedIndex = 0, this.hideNavigationBar = false});
6 |
7 | final int selectedIndex;
8 |
9 | final bool hideNavigationBar;
10 |
11 | final PageController pageController = PageController();
12 |
13 |
14 |
15 | IndexState copyWith({int? selectedIndex, bool? hideNavigationBar}) {
16 | return IndexState(
17 | selectedIndex: selectedIndex ?? this.selectedIndex,
18 | hideNavigationBar: hideNavigationBar ?? this.hideNavigationBar
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/pages/nav/more/more_state.dart:
--------------------------------------------------------------------------------
1 | class MoreState {
2 | MoreState() {
3 | ///Initialize variables
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/lib/pages/read/read_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'read_state.freezed.dart';
4 | part 'read_state.g.dart';
5 |
6 | @freezed
7 | class ReadState with _$ReadState {
8 | @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
9 | const factory ReadState({
10 | @Default(false) bool showAppBar,
11 | @Default(0.0) double topBarOffset,
12 | @Default(0.0) double bottomBarOffset,
13 | @Default(0.0) double bottomBarHeight,
14 | @Default(false) bool showThumbList,
15 | @Default(0.0) double paddingTop,
16 | @Default(0.0) double paddingBottom,
17 | @Default(false) bool autoRead,
18 | @Default({}) Set loadCompleteIndexSet,
19 | }) = _ReadState;
20 |
21 | const ReadState._();
22 |
23 | factory ReadState.fromJson(Map json) =>
24 | _$ReadStateFromJson(json);
25 | }
26 |
--------------------------------------------------------------------------------
/lib/pages/read/read_state.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'read_state.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$_ReadState _$$_ReadStateFromJson(Map json) => _$_ReadState(
10 | showAppBar: json['show_app_bar'] as bool? ?? false,
11 | topBarOffset: (json['top_bar_offset'] as num?)?.toDouble() ?? 0.0,
12 | bottomBarOffset: (json['bottom_bar_offset'] as num?)?.toDouble() ?? 0.0,
13 | bottomBarHeight: (json['bottom_bar_height'] as num?)?.toDouble() ?? 0.0,
14 | showThumbList: json['show_thumb_list'] as bool? ?? false,
15 | paddingTop: (json['padding_top'] as num?)?.toDouble() ?? 0.0,
16 | paddingBottom: (json['padding_bottom'] as num?)?.toDouble() ?? 0.0,
17 | autoRead: json['auto_read'] as bool? ?? false,
18 | loadCompleteIndexSet: (json['load_complete_index_set'] as List?)
19 | ?.map((e) => e as int)
20 | .toSet() ??
21 | const {},
22 | );
23 |
24 | Map _$$_ReadStateToJson(_$_ReadState instance) =>
25 | {
26 | 'show_app_bar': instance.showAppBar,
27 | 'top_bar_offset': instance.topBarOffset,
28 | 'bottom_bar_offset': instance.bottomBarOffset,
29 | 'bottom_bar_height': instance.bottomBarHeight,
30 | 'show_thumb_list': instance.showThumbList,
31 | 'padding_top': instance.paddingTop,
32 | 'padding_bottom': instance.paddingBottom,
33 | 'auto_read': instance.autoRead,
34 | 'load_complete_index_set': instance.loadCompleteIndexSet.toList(),
35 | };
36 |
--------------------------------------------------------------------------------
/lib/pages/setting/about_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:eros_n/common/global.dart';
3 | import 'package:eros_n/routes/routes.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:url_launcher/url_launcher_string.dart';
6 |
7 | @RoutePage()
8 | class AboutPage extends StatelessWidget {
9 | const AboutPage({Key? key}) : super(key: key);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Scaffold(
14 | appBar: AppBar(
15 | title: Text('About'),
16 | ),
17 | body: SingleChildScrollView(
18 | child: Column(
19 | children: [
20 | ListTile(
21 | title: Text('Version'),
22 | subtitle: Text(
23 | '${Global.packageInfo.version} (${Global.packageInfo.buildNumber})'),
24 | ),
25 | ListTile(
26 | title: Text('Author'),
27 | subtitle: Text('Honjow'),
28 | onTap: () => launchUrlString(
29 | 'https://github.com/honjow',
30 | mode: LaunchMode.externalApplication,
31 | ),
32 | ),
33 | ListTile(
34 | title: Text('Email'),
35 | subtitle: Text('honjow311@gmail.com'),
36 | onTap: () => launchUrlString('mailto:honjow311@gmail.com'),
37 | ),
38 | ListTile(
39 | title: Text('Github'),
40 | subtitle: Text('https://github.com/erosTeam/eros_n'),
41 | onTap: () => launchUrlString(
42 | 'https://github.com/erosTeam/eros_n',
43 | mode: LaunchMode.externalApplication,
44 | ),
45 | ),
46 | ListTile(
47 | title: Text('Telegram'),
48 | onTap: () => launchUrlString(
49 | 'https://t.me/joinchat/AEj27KMQe0JiMmUx',
50 | mode: LaunchMode.externalApplication,
51 | ),
52 | ),
53 | ListTile(
54 | title: Text('Telegram Channel'),
55 | onTap: () => launchUrlString(
56 | 'https://t.me/fehviewer',
57 | mode: LaunchMode.externalApplication,
58 | ),
59 | ),
60 | ListTile(
61 | title: Text('License'),
62 | onTap: () {
63 | erosRouter.pushNamed(NHRoutes.license);
64 | },
65 | ),
66 | ],
67 | ),
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/pages/setting/advanced_setting_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:eros_n/common/provider/tag_translate_provider.dart';
3 | import 'package:eros_n/generated/l10n.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:hooks_riverpod/hooks_riverpod.dart';
6 |
7 | import 'setting_base.dart';
8 |
9 | @RoutePage()
10 | class AdvancedSettingPage extends StatelessWidget {
11 | const AdvancedSettingPage({Key? key}) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: Text(L10n.of(context).advanced),
18 | ),
19 | body: ListView(
20 | children: [
21 | SmallTitle(title: L10n.of(context).tag),
22 | Consumer(builder: (context, ref, child) {
23 | final allNhTag = ref.watch(allNhTagProvider);
24 | return ListTile(
25 | title: Text(L10n.of(context).tags_data),
26 | // subtitle: Text('total: ${ref.watch(tagTranslateProvider).total}, long press to update'),
27 | subtitle: allNhTag.when(
28 | data: (data) =>
29 | Text('total: ${data.length}, long press to update'),
30 | loading: () => Text('loading...'),
31 | error: (error, stack) => Text('error: $error'),
32 | ),
33 | onLongPress: () {
34 | ref.read(tagTranslateProvider.notifier).updateNhTags();
35 | },
36 | );
37 | }),
38 | ],
39 | ),
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/pages/setting/license_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | @RoutePage()
5 | class MyLicensePage extends StatelessWidget {
6 | const MyLicensePage({Key? key}) : super(key: key);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return const LicensePage();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/pages/setting/setting_base.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:eros_n/generated/l10n.dart';
4 | import 'package:eros_n/utils/get_utils/extensions/context_extensions.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | class SmallTitle extends StatelessWidget {
8 | const SmallTitle({Key? key, required this.title}) : super(key: key);
9 | final String title;
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Container(
14 | padding: EdgeInsets.only(
15 | left: max(context.mediaQueryPadding.left, 16), top: 16, bottom: 6),
16 | child: Text(
17 | title,
18 | style: Theme.of(context).textTheme.labelLarge?.copyWith(
19 | color: Theme.of(context).colorScheme.primary,
20 | fontWeight: FontWeight.w500,
21 | ),
22 | ),
23 | );
24 | }
25 | }
26 |
27 | class RadioDialogListTile extends StatelessWidget {
28 | const RadioDialogListTile(
29 | {Key? key,
30 | this.title,
31 | this.subtitle,
32 | this.onChanged,
33 | this.dialogTitle,
34 | this.groupValue,
35 | required this.radioTitleMap})
36 | : super(key: key);
37 |
38 | final Widget? title;
39 | final Widget? subtitle;
40 | final ValueChanged? onChanged;
41 | final Widget? dialogTitle;
42 | final T? groupValue;
43 | final Map radioTitleMap;
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | void onChanged(T? value) {
48 | if (value != null) {
49 | this.onChanged?.call(value);
50 | Navigator.of(context).pop();
51 | }
52 | }
53 |
54 | final children = radioTitleMap.entries
55 | .map((e) => RadioListTile(
56 | title: e.value,
57 | value: e.key,
58 | groupValue: groupValue,
59 | onChanged: onChanged,
60 | activeColor: Theme.of(context).colorScheme.primary,
61 | ))
62 | .toList();
63 |
64 | return ListTile(
65 | title: title,
66 | subtitle: subtitle ?? radioTitleMap[groupValue],
67 | onTap: () {
68 | showDialog(
69 | context: context,
70 | builder: (context) {
71 | return AlertDialog(
72 | contentPadding: const EdgeInsets.only(top: 16),
73 | title: dialogTitle ?? title,
74 | content: Column(
75 | mainAxisSize: MainAxisSize.min,
76 | children: children,
77 | ),
78 | actions: [
79 | TextButton(
80 | onPressed: () {
81 | Navigator.of(context).pop();
82 | },
83 | child: Text(L10n.of(context).cancel),
84 | ),
85 | ],
86 | );
87 | },
88 | );
89 | },
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/pages/setting/settings_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:eros_n/common/global.dart';
3 | import 'package:eros_n/generated/l10n.dart';
4 | import 'package:eros_n/routes/routes.dart';
5 | import 'package:flutter/foundation.dart';
6 | import 'package:flutter/material.dart';
7 |
8 | @RoutePage()
9 | class SettingsPage extends StatelessWidget {
10 | const SettingsPage({Key? key}) : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return Scaffold(
15 | appBar: AppBar(
16 | title: Text(L10n.of(context).settings),
17 | ),
18 | body: ListView(
19 | children: [
20 | ListTile(
21 | iconColor: Theme.of(context).colorScheme.primary,
22 | leading: const Icon(Icons.tune_outlined),
23 | title: Text(L10n.of(context).general),
24 | onTap: () {
25 | erosRouter.pushNamed(NHRoutes.generalSetting);
26 | },
27 | ),
28 | ListTile(
29 | iconColor: Theme.of(context).colorScheme.primary,
30 | leading: const Icon(Icons.color_lens_outlined),
31 | title: Text(L10n.of(context).appearance),
32 | onTap: () {
33 | erosRouter.pushNamed(NHRoutes.appearanceSetting);
34 | },
35 | ),
36 | // read setting
37 | ListTile(
38 | iconColor: Theme.of(context).colorScheme.primary,
39 | leading: const Icon(Icons.chrome_reader_mode_outlined),
40 | title: Text(L10n.of(context).read),
41 | onTap: () {
42 | erosRouter.pushNamed(NHRoutes.readSetting);
43 | },
44 | ),
45 | // advance setting
46 | ListTile(
47 | iconColor: Theme.of(context).colorScheme.primary,
48 | leading: const Icon(Icons.data_object_outlined),
49 | title: Text(L10n.of(context).advanced),
50 | onTap: () {
51 | erosRouter.pushNamed(NHRoutes.advancedSetting);
52 | },
53 | ),
54 | ],
55 | ));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/pages/splash/splash_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:eros_n/common/global.dart';
3 | import 'package:eros_n/routes/routes.dart';
4 | import 'package:eros_n/utils/get_utils/get_utils.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:hooks_riverpod/hooks_riverpod.dart';
7 |
8 | @RoutePage()
9 | class SplashPage extends StatefulHookConsumerWidget {
10 | const SplashPage({super.key});
11 |
12 | @override
13 | ConsumerState createState() => _SplashPageState();
14 | }
15 |
16 | class _SplashPageState extends ConsumerState {
17 | @override
18 | void initState() {
19 | super.initState();
20 |
21 | 10
22 | .milliseconds
23 | .delay()
24 | .then((value) => erosRouter.replaceNamed(NHRoutes.home));
25 | }
26 |
27 | @override
28 | void dispose() {
29 | super.dispose();
30 | }
31 |
32 | @override
33 | Widget build(BuildContext context) {
34 | return const Scaffold(
35 | body: Center(
36 | child: Text(
37 | 'Eros-N',
38 | style: TextStyle(fontSize: 40),
39 | ),
40 | ),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/pages/user/user_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection/collection.dart';
2 | import 'package:eros_n/common/const/const.dart';
3 | import 'package:eros_n/common/global.dart';
4 | import 'package:eros_n/component/models/index.dart';
5 | import 'package:eros_n/network/app_dio/pdio.dart';
6 | import 'package:eros_n/network/request.dart';
7 | import 'package:eros_n/utils/logger.dart';
8 | import 'package:flutter_inappwebview/flutter_inappwebview.dart';
9 | import 'package:hooks_riverpod/hooks_riverpod.dart';
10 |
11 | class UserNotifier extends StateNotifier {
12 | UserNotifier(super.state);
13 |
14 | Future login({
15 | required String username,
16 | required String password,
17 | }) async {
18 | late final String csrfToken;
19 |
20 | try {
21 | csrfToken = await getLoginToken() ?? '';
22 | } on HttpException catch (e) {
23 | rethrow;
24 | }
25 |
26 | logger.d('csrfToken $csrfToken');
27 | final loginResult = await loginNhentai(
28 | username: username, password: password, csrfToken: csrfToken);
29 |
30 | return loginResult;
31 | }
32 |
33 | Future loginGetMore() async {
34 | logger.d('loginWithCookie');
35 |
36 | final cookie =
37 | await Global.cookieJar.loadForRequest(Uri.parse(NHConst.baseUrl));
38 | final sessionid =
39 | cookie.firstWhereOrNull((element) => element.name == 'sessionid');
40 |
41 | state = state.copyWith(sessionid: sessionid?.value);
42 | hiveHelper.setUser(state);
43 |
44 | late User userFromIndex;
45 | try {
46 | userFromIndex = await getInfoFromIndex(refresh: true);
47 | } on HttpException catch (e) {
48 | rethrow;
49 | }
50 | // log userFromIndex
51 | logger.d('userFromIndex $userFromIndex');
52 |
53 | state = state.copyWith(
54 | userUrl: userFromIndex.userUrl,
55 | userName: userFromIndex.userName,
56 | userId: userFromIndex.userId,
57 | );
58 | hiveHelper.setUser(state);
59 |
60 | if (userFromIndex.userUrl == null) {
61 | return;
62 | }
63 | late User userFromProfile;
64 | try {
65 | userFromProfile = await getInfoFromUserPage(
66 | url: userFromIndex.userUrl ?? '', refresh: true);
67 | } on HttpException catch (e) {
68 | rethrow;
69 | }
70 |
71 | state = state.copyWith(
72 | avatarUrl: userFromProfile.avatarUrl,
73 | );
74 | hiveHelper.setUser(state);
75 | }
76 |
77 | void logout() {
78 | Global.cookieJar.deleteAll();
79 | CookieManager.instance().deleteAllCookies();
80 | state = const User();
81 | hiveHelper.setUser(state);
82 | }
83 | }
84 |
85 | final userProvider = StateNotifierProvider((ref) {
86 | return UserNotifier(hiveHelper.getUser());
87 | });
88 |
--------------------------------------------------------------------------------
/lib/pages/webview/webview.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_inappwebview/flutter_inappwebview.dart';
4 |
5 | @RoutePage()
6 | class NhWebViewPage extends StatelessWidget {
7 | const NhWebViewPage({Key? key, required this.initialUrl, this.title})
8 | : super(key: key);
9 | final String initialUrl;
10 | final String? title;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return Scaffold(
15 | appBar: AppBar(
16 | title: Text(title ?? ''),
17 | ),
18 | body: InAppWebView(
19 | initialUrlRequest: URLRequest(
20 | url: Uri.parse(initialUrl),
21 | ),
22 | initialOptions: inAppWebViewOptions,
23 | ),
24 | );
25 | }
26 | }
27 |
28 | final InAppWebViewGroupOptions inAppWebViewOptions = InAppWebViewGroupOptions(
29 | crossPlatform: InAppWebViewOptions(
30 | useShouldOverrideUrlLoading: true,
31 | mediaPlaybackRequiresUserGesture: false,
32 | // clearCache: true,
33 | // userAgent: NHConst.userAgent,
34 | ),
35 | android: AndroidInAppWebViewOptions(
36 | useHybridComposition: true,
37 | ),
38 | ios: IOSInAppWebViewOptions(
39 | allowsInlineMediaPlayback: true,
40 | ),
41 | );
42 |
--------------------------------------------------------------------------------
/lib/store/db/entity/gallery_history.dart:
--------------------------------------------------------------------------------
1 | import 'package:isar/isar.dart';
2 |
3 | part 'gallery_history.g.dart';
4 |
5 | @Collection()
6 | class GalleryHistory {
7 | @Index(unique: true, replace: true)
8 | Id gid = 0;
9 | String? mediaId;
10 | String? csrfToken;
11 | String? title;
12 | String? japaneseTitle;
13 | String? url;
14 | String? thumbUrl;
15 | int? coverImgHeight;
16 | int? coverImgWidth;
17 | @Index()
18 | int? lastReadTime;
19 | }
20 |
--------------------------------------------------------------------------------
/lib/store/db/entity/nh_tag.dart:
--------------------------------------------------------------------------------
1 | import 'package:copy_with_extension/copy_with_extension.dart';
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 | import 'package:isar/isar.dart';
4 |
5 | part 'nh_tag.g.dart';
6 |
7 | @CopyWith()
8 | @Collection()
9 | @JsonSerializable()
10 | class NhTag {
11 | NhTag({
12 | required this.id,
13 | this.name,
14 | this.count,
15 | this.type,
16 | this.lastUseTime = 0,
17 | this.translateName,
18 | });
19 |
20 | factory NhTag.fromJson(Map json) => _$NhTagFromJson(json);
21 | Map toJson() => _$NhTagToJson(this);
22 |
23 | @Index(unique: true, replace: true)
24 | final Id? id;
25 | @Index()
26 | @JsonKey(ignore: true)
27 | final String? type;
28 | @Index()
29 | final String? name;
30 | @Index()
31 | final String? translateName;
32 | int? count;
33 | @Index()
34 | final int lastUseTime;
35 |
36 | @override
37 | String toString() {
38 | return 'NhTag{id: $id, type: $type, name: $name, translateName: $translateName, count: $count, lastUseTime: $lastUseTime}';
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/store/db/entity/tag_translate.dart:
--------------------------------------------------------------------------------
1 | import 'package:copy_with_extension/copy_with_extension.dart';
2 | import 'package:isar/isar.dart';
3 |
4 | part 'tag_translate.g.dart';
5 |
6 | @CopyWith()
7 | @Collection()
8 | class TagTranslate {
9 | TagTranslate({
10 | required this.namespace,
11 | required this.name,
12 | this.translateName,
13 | this.intro,
14 | this.links,
15 | this.lastUseTime = 0,
16 | }) : id = Isar.autoIncrement;
17 | Id id;
18 | @Index()
19 | final String namespace;
20 | @Index(composite: [CompositeIndex('namespace')], unique: true, replace: true)
21 | @Index()
22 | final String name;
23 | @Index()
24 | final String? translateName;
25 | final String? intro;
26 | final String? links;
27 | @Index()
28 | final int lastUseTime;
29 |
30 | String? get translateNameNotMD {
31 | final reg = RegExp(r'!\[(\S+)?\]\(.+?\)(\S+)');
32 | final match = reg.allMatches(translateName ?? '');
33 | if (match.isNotEmpty) {
34 | return translateName?.replaceAllMapped(
35 | reg, (match) => match.group(2) ?? '') ??
36 | translateName;
37 | } else {
38 | return translateName;
39 | }
40 | }
41 |
42 | @override
43 | String toString() {
44 | return 'TagTranslate{id: $id, namespace: $namespace, name: $name, translateName: $translateName, intro: $intro, links: $links, lastUseTime: $lastUseTime}';
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/store/db/isar.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/common/global.dart';
2 | import 'package:isar/isar.dart';
3 |
4 | import 'entity/gallery_history.dart';
5 | import 'entity/nh_tag.dart';
6 | import 'entity/tag_translate.dart';
7 |
8 | Future openIsar({String? path}) async {
9 | final dirPath = path ?? Global.appSupportPath;
10 |
11 | final isar = await Isar.open(
12 | [
13 | GalleryHistorySchema,
14 | TagTranslateSchema,
15 | NhTagSchema,
16 | ],
17 | directory: dirPath,
18 | name: 'eros_n',
19 | );
20 |
21 | return isar;
22 | }
23 |
--------------------------------------------------------------------------------
/lib/store/kv/hive.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:eros_n/component/models/index.dart';
4 | import 'package:eros_n/utils/logger.dart';
5 | import 'package:hive_flutter/hive_flutter.dart';
6 |
7 | const String configBox = 'config_box';
8 |
9 | const String userAgentKey = 'user_agent';
10 | const String settingsKey = 'settings';
11 | const String userKey = 'user';
12 | const String tagTranslateInfoKey = 'tag_translate_info';
13 |
14 | class HiveHelper {
15 | HiveHelper();
16 |
17 | static final _configBox = Hive.box(configBox);
18 | static final _userBox = Hive.box(userKey);
19 |
20 | static Future init() async {
21 | await Hive.initFlutter();
22 | await Hive.openBox(
23 | configBox,
24 | compactionStrategy: (int entries, int deletedEntries) {
25 | logger.v('entries $entries');
26 | return entries > 2;
27 | },
28 | );
29 | await Hive.openBox(
30 | userKey,
31 | compactionStrategy: (int entries, int deletedEntries) {
32 | logger.v('entries $entries');
33 | return true;
34 | },
35 | );
36 | }
37 |
38 | String? getString(String key) {
39 | return _configBox.get(key, defaultValue: '');
40 | }
41 |
42 | Future setString(String key, String value) async {
43 | await _configBox.put(key, value);
44 | }
45 |
46 | String? getUserAgent() {
47 | return _configBox.get(userAgentKey, defaultValue: '');
48 | }
49 |
50 | Future setUserAgent(String? value) async {
51 | if (value != null) {
52 | await _configBox.put(userAgentKey, value);
53 | }
54 | }
55 |
56 | Settings? getSettings() {
57 | final settings = _configBox.get(settingsKey, defaultValue: '{}') ?? '{}';
58 | if (settings.isNotEmpty) {
59 | return Settings.fromJson(jsonDecode(settings) as Map);
60 | }
61 | return null;
62 | }
63 |
64 | Future setSettings(Settings settings) async {
65 | await _configBox.put(settingsKey, jsonEncode(settings.toJson()));
66 | }
67 |
68 | User getUser() {
69 | final user = _userBox.get(userKey, defaultValue: '{}') ?? '{}';
70 | if (user.isNotEmpty) {
71 | return User.fromJson(jsonDecode(user) as Map);
72 | }
73 | return const User();
74 | }
75 |
76 | Future setUser(User user) async {
77 | logger.d('setUser $user');
78 | await _userBox.put(userKey, jsonEncode(user.toJson()));
79 | }
80 |
81 | TagTranslateInfo getTagTranslateInfo() {
82 | final tagTranslateInfo =
83 | _configBox.get(tagTranslateInfoKey, defaultValue: '{}') ?? '{}';
84 | if (tagTranslateInfo.isNotEmpty) {
85 | return TagTranslateInfo.fromJson(
86 | jsonDecode(tagTranslateInfo) as Map);
87 | }
88 | return const TagTranslateInfo();
89 | }
90 |
91 | Future setTagTranslateInfo(TagTranslateInfo tagTranslateInfo) async {
92 | await _configBox.put(
93 | tagTranslateInfoKey, jsonEncode(tagTranslateInfo.toJson()));
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/utils/clipboard_helper.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/common/provider/settings_provider.dart';
2 | import 'package:eros_n/routes/routes.dart';
3 | import 'package:eros_n/utils/toast.dart';
4 | import 'package:flutter/cupertino.dart';
5 | import 'package:flutter/services.dart';
6 | import 'package:hooks_riverpod/hooks_riverpod.dart';
7 |
8 | class ClipboardHelper {
9 | Future getClipboardData() async {
10 | ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
11 | return data?.text;
12 | }
13 |
14 | void setClipboardData(String text) {
15 | Clipboard.setData(ClipboardData(text: text));
16 | }
17 |
18 | Future chkClipboardLink(BuildContext context, WidgetRef ref) async {
19 | final clipboardDetection =
20 | ref.read(settingsProvider.select((s) => s.clipboardDetection));
21 | if (!clipboardDetection) {
22 | return;
23 | }
24 |
25 | final String? text = await getClipboardData();
26 | if (text != null) {
27 | _showClipboardLinkToast(text, ref);
28 | }
29 | }
30 |
31 | Future _showClipboardLinkToast(String text, WidgetRef ref) async {
32 | final RegExp regGalleryPageUrl = RegExp(r'https://nhentai.net/g/(\d+)/\d+');
33 | final RegExp regGalleryUrl = RegExp(r'https?://nhentai.net/g/(\d+)/?');
34 |
35 | final url = regGalleryPageUrl.firstMatch(text)?.group(0) ??
36 | regGalleryUrl.firstMatch(text)?.group(0) ??
37 | '';
38 |
39 | if (regGalleryUrl.hasMatch(text) || regGalleryPageUrl.hasMatch(text)) {
40 | showActionToast(
41 | url,
42 | icon: CupertinoIcons.link,
43 | onPressed: () {
44 | RouteUtil.goGalleryByUrl(ref, text);
45 | },
46 | );
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/utils/eros_utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:eros_n/common/const/const.dart';
4 |
5 | T radomList(Iterable srcList) {
6 | final index = Random().nextInt(srcList.length);
7 | return srcList.toList()[index];
8 | }
9 |
10 | String buildImageCacheKey(String url) {
11 | final uri = Uri.parse(url);
12 | final host = uri.host;
13 | if (host.endsWith(NHConst.baseHost)) {
14 | return uri.path;
15 | }
16 | return url;
17 | }
18 |
19 | String? getTagNamespace(String tagType) {
20 | switch (tagType) {
21 | case 'Parodies':
22 | return 'parody';
23 | case 'Characters':
24 | return 'character';
25 | case 'Tags':
26 | return null;
27 | case 'Artists':
28 | return 'artist';
29 | case 'Groups':
30 | return 'group';
31 | case 'Languages':
32 | return 'language';
33 | case 'Categories':
34 | return null;
35 | default:
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/extensions/double_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | extension Precision on double {
4 | double toPrecision(int fractionDigits) {
5 | var mod = pow(10, fractionDigits.toDouble()).toDouble();
6 | return ((this * mod).round().toDouble() / mod);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/extensions/duration_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | /// Duration utilities.
4 | extension GetDurationUtils on Duration {
5 | /// Utility to delay some callback (or code execution).
6 | ///
7 | /// Sample:
8 | /// ```
9 | /// void main() async {
10 | /// final _delay = 3.seconds;
11 | /// print('+ wait $_delay');
12 | /// await _delay.delay();
13 | /// print('- finish wait $_delay');
14 | /// print('+ callback in 700ms');
15 | /// await 0.7.seconds.delay(() {
16 | /// }
17 | ///```
18 | Future delay([FutureOr Function()? callback]) async =>
19 | Future.delayed(this, callback);
20 | }
21 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/extensions/export.dart:
--------------------------------------------------------------------------------
1 | export 'context_extensions.dart';
2 | export 'double_extensions.dart';
3 | export 'duration_extensions.dart';
4 | export 'iterable_extensions.dart';
5 | export 'num_extensions.dart';
6 | export 'string_extensions.dart';
7 | export 'widget_extensions.dart';
8 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/extensions/iterable_extensions.dart:
--------------------------------------------------------------------------------
1 | extension IterableExtensions on Iterable {
2 | Iterable mapMany(
3 | Iterable? Function(T item) selector) sync* {
4 | for (var item in this) {
5 | final res = selector(item);
6 | if (res != null) yield* res;
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/extensions/num_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import '../get_utils/get_utils.dart';
4 |
5 | extension GetNumUtils on num {
6 | bool isLowerThan(num b) => GetUtils.isLowerThan(this, b);
7 |
8 | bool isGreaterThan(num b) => GetUtils.isGreaterThan(this, b);
9 |
10 | bool isEqual(num b) => GetUtils.isEqual(this, b);
11 |
12 | /// Utility to delay some callback (or code execution).
13 | /// TODO: Add a separated implementation of delay() with the ability
14 | /// to stop it.
15 | ///
16 | /// Sample:
17 | /// ```
18 | /// void main() async {
19 | /// print('+ wait for 2 seconds');
20 | /// await 2.delay();
21 | /// print('- 2 seconds completed');
22 | /// print('+ callback in 1.2sec');
23 | /// 1.delay(() => print('- 1.2sec callback called'));
24 | /// print('currently running callback 1.2sec');
25 | /// }
26 | ///```
27 | Future delay([FutureOr Function()? callback]) async => Future.delayed(
28 | Duration(milliseconds: (this * 1000).round()),
29 | callback,
30 | );
31 |
32 | /// Easy way to make Durations from numbers.
33 | ///
34 | /// Sample:
35 | /// ```
36 | /// print(1.seconds + 200.milliseconds);
37 | /// print(1.hours + 30.minutes);
38 | /// print(1.5.hours);
39 | ///```
40 | Duration get milliseconds => Duration(microseconds: (this * 1000).round());
41 |
42 | Duration get seconds => Duration(milliseconds: (this * 1000).round());
43 |
44 | Duration get minutes =>
45 | Duration(seconds: (this * Duration.secondsPerMinute).round());
46 |
47 | Duration get hours =>
48 | Duration(minutes: (this * Duration.minutesPerHour).round());
49 |
50 | Duration get days => Duration(hours: (this * Duration.hoursPerDay).round());
51 |
52 | //final _delayMaps = {};
53 | // TODO: create a proper Future and control the Timer.
54 | // Future delay([double seconds = 0, VoidCallback callback]) async {
55 | // final ms = (seconds * 1000).round();
56 | // return Future.delayed(Duration(milliseconds: ms), callback);
57 | // return _delayMaps[callback];
58 | // }
59 | //killDelay(VoidCallback callback) {
60 | // if (_delayMaps.containsKey(callback)) {
61 | // _delayMaps[callback]?.timeout(Duration.zero, onTimeout: () {
62 | // print('callbacl eliminado!');
63 | // });
64 | // _delayMaps.remove(callback);
65 | // }
66 | //}
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/extensions/string_extensions.dart:
--------------------------------------------------------------------------------
1 | import '../get_utils/get_utils.dart';
2 |
3 | extension GetStringUtils on String {
4 | bool get isNum => GetUtils.isNum(this);
5 |
6 | bool get isNumericOnly => GetUtils.isNumericOnly(this);
7 |
8 | bool get isAlphabetOnly => GetUtils.isAlphabetOnly(this);
9 |
10 | bool get isBool => GetUtils.isBool(this);
11 |
12 | bool get isVectorFileName => GetUtils.isVector(this);
13 |
14 | bool get isImageFileName => GetUtils.isImage(this);
15 |
16 | bool get isAudioFileName => GetUtils.isAudio(this);
17 |
18 | bool get isVideoFileName => GetUtils.isVideo(this);
19 |
20 | bool get isTxtFileName => GetUtils.isTxt(this);
21 |
22 | bool get isDocumentFileName => GetUtils.isWord(this);
23 |
24 | bool get isExcelFileName => GetUtils.isExcel(this);
25 |
26 | bool get isPPTFileName => GetUtils.isPPT(this);
27 |
28 | bool get isAPKFileName => GetUtils.isAPK(this);
29 |
30 | bool get isPDFFileName => GetUtils.isPDF(this);
31 |
32 | bool get isHTMLFileName => GetUtils.isHTML(this);
33 |
34 | bool get isURL => GetUtils.isURL(this);
35 |
36 | bool get isEmail => GetUtils.isEmail(this);
37 |
38 | bool get isPhoneNumber => GetUtils.isPhoneNumber(this);
39 |
40 | bool get isDateTime => GetUtils.isDateTime(this);
41 |
42 | bool get isMD5 => GetUtils.isMD5(this);
43 |
44 | bool get isSHA1 => GetUtils.isSHA1(this);
45 |
46 | bool get isSHA256 => GetUtils.isSHA256(this);
47 |
48 | bool get isBinary => GetUtils.isBinary(this);
49 |
50 | bool get isIPv4 => GetUtils.isIPv4(this);
51 |
52 | bool get isIPv6 => GetUtils.isIPv6(this);
53 |
54 | bool get isHexadecimal => GetUtils.isHexadecimal(this);
55 |
56 | bool get isPalindrom => GetUtils.isPalindrom(this);
57 |
58 | bool get isPassport => GetUtils.isPassport(this);
59 |
60 | bool get isCurrency => GetUtils.isCurrency(this);
61 |
62 | bool get isCpf => GetUtils.isCpf(this);
63 |
64 | bool get isCnpj => GetUtils.isCnpj(this);
65 |
66 | bool isCaseInsensitiveContains(String b) =>
67 | GetUtils.isCaseInsensitiveContains(this, b);
68 |
69 | bool isCaseInsensitiveContainsAny(String b) =>
70 | GetUtils.isCaseInsensitiveContainsAny(this, b);
71 |
72 | String? get capitalize => GetUtils.capitalize(this);
73 |
74 | String? get capitalizeFirst => GetUtils.capitalizeFirst(this);
75 |
76 | String get removeAllWhitespace => GetUtils.removeAllWhitespace(this);
77 |
78 | String? get camelCase => GetUtils.camelCase(this);
79 |
80 | String? get paramCase => GetUtils.paramCase(this);
81 |
82 | String numericOnly({bool firstWordOnly = false}) =>
83 | GetUtils.numericOnly(this, firstWordOnly: firstWordOnly);
84 |
85 | String createPath([Iterable? segments]) {
86 | final path = startsWith('/') ? this : '/$this';
87 | return GetUtils.createPath(path, segments);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/extensions/widget_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | /// add Padding Property to widget
4 | extension WidgetPaddingX on Widget {
5 | Widget paddingAll(double padding) =>
6 | Padding(padding: EdgeInsets.all(padding), child: this);
7 |
8 | Widget paddingSymmetric({double horizontal = 0.0, double vertical = 0.0}) =>
9 | Padding(
10 | padding:
11 | EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical),
12 | child: this);
13 |
14 | Widget paddingOnly({
15 | double left = 0.0,
16 | double top = 0.0,
17 | double right = 0.0,
18 | double bottom = 0.0,
19 | }) =>
20 | Padding(
21 | padding: EdgeInsets.only(
22 | top: top, left: left, right: right, bottom: bottom),
23 | child: this);
24 |
25 | Widget get paddingZero => Padding(padding: EdgeInsets.zero, child: this);
26 | }
27 |
28 | /// Add margin property to widget
29 | extension WidgetMarginX on Widget {
30 | Widget marginAll(double margin) =>
31 | Container(margin: EdgeInsets.all(margin), child: this);
32 |
33 | Widget marginSymmetric({double horizontal = 0.0, double vertical = 0.0}) =>
34 | Container(
35 | margin:
36 | EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical),
37 | child: this);
38 |
39 | Widget marginOnly({
40 | double left = 0.0,
41 | double top = 0.0,
42 | double right = 0.0,
43 | double bottom = 0.0,
44 | }) =>
45 | Container(
46 | margin: EdgeInsets.only(
47 | top: top, left: left, right: right, bottom: bottom),
48 | child: this);
49 |
50 | Widget get marginZero => Container(margin: EdgeInsets.zero, child: this);
51 | }
52 |
53 | /// Allows you to insert widgets inside a CustomScrollView
54 | extension WidgetSliverBoxX on Widget {
55 | Widget get sliverBox => SliverToBoxAdapter(child: this);
56 | }
57 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/get_utils.dart:
--------------------------------------------------------------------------------
1 | export 'extensions/export.dart';
2 | export 'get_utils/get_utils.dart';
3 | export 'platform/platform.dart';
4 | export 'queue/get_queue.dart';
5 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/platform/platform.dart:
--------------------------------------------------------------------------------
1 | import 'platform_web.dart' if (dart.library.io) 'platform_io.dart';
2 |
3 | // ignore: avoid_classes_with_only_static_members
4 | class GetPlatform {
5 | static bool get isWeb => GeneralPlatform.isWeb;
6 |
7 | static bool get isMacOS => GeneralPlatform.isMacOS;
8 |
9 | static bool get isWindows => GeneralPlatform.isWindows;
10 |
11 | static bool get isLinux => GeneralPlatform.isLinux;
12 |
13 | static bool get isAndroid => GeneralPlatform.isAndroid;
14 |
15 | static bool get isIOS => GeneralPlatform.isIOS;
16 |
17 | static bool get isFuchsia => GeneralPlatform.isFuchsia;
18 |
19 | static bool get isMobile => GetPlatform.isIOS || GetPlatform.isAndroid;
20 |
21 | static bool get isDesktop =>
22 | GetPlatform.isMacOS || GetPlatform.isWindows || GetPlatform.isLinux;
23 | }
24 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/platform/platform_io.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | // ignore: avoid_classes_with_only_static_members
4 | class GeneralPlatform {
5 | static bool get isWeb => false;
6 |
7 | static bool get isMacOS => Platform.isMacOS;
8 |
9 | static bool get isWindows => Platform.isWindows;
10 |
11 | static bool get isLinux => Platform.isLinux;
12 |
13 | static bool get isAndroid => Platform.isAndroid;
14 |
15 | static bool get isIOS => Platform.isIOS;
16 |
17 | static bool get isFuchsia => Platform.isFuchsia;
18 |
19 | static bool get isDesktop =>
20 | Platform.isMacOS || Platform.isWindows || Platform.isLinux;
21 | }
22 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/platform/platform_web.dart:
--------------------------------------------------------------------------------
1 | // TODO: resolve platform/desktop by JS browser agent.
2 | // ignore: avoid_web_libraries_in_flutter
3 | import 'dart:html' as html;
4 |
5 | import '../get_utils.dart';
6 |
7 | html.Navigator _navigator = html.window.navigator;
8 |
9 | // ignore: avoid_classes_with_only_static_members
10 | class GeneralPlatform {
11 | static bool get isWeb => true;
12 |
13 | static bool get isMacOS =>
14 | _navigator.appVersion.contains('Mac OS') && !GeneralPlatform.isIOS;
15 |
16 | static bool get isWindows => _navigator.appVersion.contains('Win');
17 |
18 | static bool get isLinux =>
19 | (_navigator.appVersion.contains('Linux') ||
20 | _navigator.appVersion.contains('x11')) &&
21 | !isAndroid;
22 |
23 | // @check https://developer.chrome.com/multidevice/user-agent
24 | static bool get isAndroid => _navigator.appVersion.contains('Android ');
25 |
26 | static bool get isIOS {
27 | // maxTouchPoints is needed to separate iPad iOS13 vs new MacOS
28 | return GetUtils.hasMatch(_navigator.platform, r'/iPad|iPhone|iPod/') ||
29 | (_navigator.platform == 'MacIntel' && _navigator.maxTouchPoints! > 1);
30 | }
31 |
32 | static bool get isFuchsia => false;
33 |
34 | static bool get isDesktop => isMacOS || isWindows || isLinux;
35 | }
36 |
--------------------------------------------------------------------------------
/lib/utils/get_utils/queue/get_queue.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | class GetMicrotask {
4 | int _version = 0;
5 | int _microtask = 0;
6 |
7 | int get microtask => _microtask;
8 | int get version => _version;
9 |
10 | void exec(Function callback) {
11 | if (_microtask == _version) {
12 | _microtask++;
13 | scheduleMicrotask(() {
14 | _version++;
15 | _microtask = _version;
16 | callback();
17 | });
18 | }
19 | }
20 | }
21 |
22 | class GetQueue {
23 | final List<_Item> _queue = [];
24 | bool _active = false;
25 |
26 | Future add(Function job) {
27 | var completer = Completer();
28 | _queue.add(_Item(completer, job));
29 | _check();
30 | return completer.future;
31 | }
32 |
33 | void cancelAllJobs() {
34 | _queue.clear();
35 | }
36 |
37 | void _check() async {
38 | if (!_active && _queue.isNotEmpty) {
39 | _active = true;
40 | var item = _queue.removeAt(0);
41 | try {
42 | item.completer.complete(await item.job());
43 | } on Exception catch (e) {
44 | item.completer.completeError(e);
45 | }
46 | _active = false;
47 | _check();
48 | }
49 | }
50 | }
51 |
52 | class _Item {
53 | final dynamic completer;
54 | final dynamic job;
55 |
56 | _Item(this.completer, this.job);
57 | }
58 |
--------------------------------------------------------------------------------
/lib/utils/toast.dart:
--------------------------------------------------------------------------------
1 | import 'package:eros_n/utils/get_utils/get_utils.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
4 |
5 | void showActionToast(String msg, {IconData? icon, VoidCallback? onPressed}) {
6 | SmartDialog.show(
7 | alignment: Alignment.bottomCenter,
8 | useAnimation: true,
9 | displayTime: 5.seconds,
10 | // consumeEvent: true,
11 | usePenetrate: true,
12 | clickMaskDismiss: false,
13 | maskColor: Colors.transparent,
14 | builder: (context) => Container(
15 | margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 120),
16 | child: Material(
17 | elevation: 6,
18 | borderRadius: BorderRadius.circular(16),
19 | type: MaterialType.card,
20 | child: Container(
21 | padding: const EdgeInsets.symmetric(horizontal: 20),
22 | child: Row(
23 | mainAxisSize: MainAxisSize.min,
24 | children: [
25 | Expanded(
26 | child: Container(
27 | padding: const EdgeInsets.symmetric(vertical: 12),
28 | child: Text(
29 | msg,
30 | textScaleFactor: 1.0,
31 | style: Theme.of(context).textTheme.bodySmall,
32 | softWrap: true,
33 | ),
34 | ),
35 | ),
36 | IconButton(
37 | icon: Icon(
38 | icon,
39 | size: 18,
40 | ),
41 | padding: const EdgeInsets.only(left: 28),
42 | onPressed: () {
43 | SmartDialog.dismiss();
44 | onPressed?.call();
45 | },
46 | ),
47 | ],
48 | ),
49 | ),
50 | ),
51 | ),
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import bitsdojo_window_macos
9 | import dynamic_color
10 | import isar_flutter_libs
11 | import package_info_plus
12 | import path_provider_foundation
13 | import sqflite
14 | import url_launcher_macos
15 | import wakelock_macos
16 | import window_size
17 |
18 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
19 | BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
20 | DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
21 | IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
22 | FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
23 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
24 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
25 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
26 | WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
27 | WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin"))
28 | }
29 |
--------------------------------------------------------------------------------
/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.13'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | end
35 |
36 | post_install do |installer|
37 | installer.pods_project.targets.each do |target|
38 | flutter_additional_macos_build_settings(target)
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/macos/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - bitsdojo_window_macos (0.0.1):
3 | - FlutterMacOS
4 | - dynamic_color (0.0.2):
5 | - FlutterMacOS
6 | - FlutterMacOS (1.0.0)
7 | - FMDB (2.7.5):
8 | - FMDB/standard (= 2.7.5)
9 | - FMDB/standard (2.7.5)
10 | - isar_flutter_libs (1.0.0):
11 | - FlutterMacOS
12 | - package_info_plus_macos (0.0.1):
13 | - FlutterMacOS
14 | - path_provider_macos (0.0.1):
15 | - FlutterMacOS
16 | - sqflite (0.0.2):
17 | - FlutterMacOS
18 | - FMDB (>= 2.7.5)
19 | - url_launcher_macos (0.0.1):
20 | - FlutterMacOS
21 | - wakelock_macos (0.0.1):
22 | - FlutterMacOS
23 | - window_size (0.0.2):
24 | - FlutterMacOS
25 |
26 | DEPENDENCIES:
27 | - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
28 | - dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
29 | - FlutterMacOS (from `Flutter/ephemeral`)
30 | - isar_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos`)
31 | - package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
32 | - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
33 | - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
34 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
35 | - wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
36 | - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
37 |
38 | SPEC REPOS:
39 | trunk:
40 | - FMDB
41 |
42 | EXTERNAL SOURCES:
43 | bitsdojo_window_macos:
44 | :path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
45 | dynamic_color:
46 | :path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos
47 | FlutterMacOS:
48 | :path: Flutter/ephemeral
49 | isar_flutter_libs:
50 | :path: Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos
51 | package_info_plus_macos:
52 | :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
53 | path_provider_macos:
54 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
55 | sqflite:
56 | :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
57 | url_launcher_macos:
58 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
59 | wakelock_macos:
60 | :path: Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos
61 | window_size:
62 | :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
63 |
64 | SPEC CHECKSUMS:
65 | bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
66 | dynamic_color: 394d6a888650f8534e029b27d2f8bc5c64e44008
67 | FlutterMacOS: 85f90bfb3f1703249cf1539e4dfbff31e8584698
68 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
69 | isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a
70 | package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
71 | path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
72 | sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
73 | url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
74 | wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
75 | window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
76 |
77 | PODFILE CHECKSUM: a884f6dd3f7494f3892ee6c81feea3a3abbf9153
78 |
79 | COCOAPODS: 1.11.3
80 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = eros_n
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.honjow.erosn
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.honjow. All rights reserved.
15 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController.init()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/screenshots/comment_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/screenshots/comment_1.jpg
--------------------------------------------------------------------------------
/screenshots/gallery_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/screenshots/gallery_1.jpg
--------------------------------------------------------------------------------
/screenshots/gallery_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/screenshots/gallery_2.jpg
--------------------------------------------------------------------------------
/screenshots/history_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/screenshots/history_1.jpg
--------------------------------------------------------------------------------
/screenshots/home_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/screenshots/home_1.jpg
--------------------------------------------------------------------------------
/scripts/thin-payload.sh:
--------------------------------------------------------------------------------
1 | # 精简Payload文件夹 (上传到AppStore会自动区分平台, 此代码仅用于构建非签名ipa)
2 |
3 | foreachThin(){
4 | for file in $1/*
5 | do
6 | if test -f $file
7 | then
8 | mime=$(file --mime-type -b $file)
9 | if [ "$mime" == 'application/x-mach-binary' ] || [ "${file##*.}"x = "dylib"x ]
10 | then
11 | echo thin $file
12 | xcrun -sdk iphoneos lipo "$file" -thin arm64 -output "$file"
13 | xcrun -sdk iphoneos bitcode_strip "$file" -r -o "$file"
14 | strip -S -x "$file" -o "$file"
15 | fi
16 | fi
17 | if test -d $file
18 | then
19 | foreachThin $file
20 | fi
21 | done
22 | }
23 |
24 | if [ $# eq 0 ]; then
25 | ehco "no argument"
26 | else
27 | foreachThin $1
28 | fi
--------------------------------------------------------------------------------
/test/network_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:http/http.dart' as http;
3 |
4 | void main() {
5 | test('NH', () async {
6 | var headers = {
7 | 'cookie':
8 | 'cf_clearance=6dsrFOlTMeE8iDVcta_FDAUPs466BtRi2VTLoy4Wuz0-1662493545-0-150; csrftoken=tPal8XhvGycU22LwiOhLZ4KRqBndr519zLORNMpOTJpw8pEBbw7TEdkXZipHYDXZ',
9 | 'User-Agent':
10 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.27'
11 | };
12 | var request = http.Request('GET', Uri.parse('https://nhentai.net/'));
13 |
14 | request.headers.addAll(headers);
15 |
16 | http.StreamedResponse response = await request.send();
17 |
18 | if (response.statusCode == 200) {
19 | print(await response.stream.bytesToString());
20 | } else {
21 | print(response.reasonPhrase);
22 | }
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/test/nhapi_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:nhentai/nhentai.dart';
3 |
4 | void main() {
5 | final api = API();
6 | test('api search', () {
7 |
8 | });
9 |
10 | test('get book', () async {
11 | final Book? book = await api.getBook(177013);
12 | if (book == null) {
13 | throw Exception('Something went wrong.');
14 | }
15 |
16 | // Short book summary
17 | print(
18 | 'Book: $book\n'
19 | 'Artists: ${book.tags.artists.join(', ')}\n'
20 | 'Languages: ${book.tags.languages.join(', ')}\n'
21 | 'Cover: ${book.cover.getUrl(api: api)}\n'
22 | 'First page: ${book.pages.first.getUrl(api: api)}\n'
23 | 'First page thumbnail: ${book.pages.first.thumbnail.getUrl(
24 | api: api)}\n',
25 | );
26 | });
27 | }
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:eros_n/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | void RegisterPlugins(flutter::PluginRegistry* registry) {
17 | BitsdojoWindowPluginRegisterWithRegistrar(
18 | registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
19 | DynamicColorPluginCApiRegisterWithRegistrar(
20 | registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
21 | IsarFlutterLibsPluginRegisterWithRegistrar(
22 | registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
23 | UrlLauncherWindowsRegisterWithRegistrar(
24 | registry->GetRegistrarForPlugin("UrlLauncherWindows"));
25 | WebviewWindowsPluginRegisterWithRegistrar(
26 | registry->GetRegistrarForPlugin("WebviewWindowsPlugin"));
27 | WindowSizePluginRegisterWithRegistrar(
28 | registry->GetRegistrarForPlugin("WindowSizePlugin"));
29 | }
30 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void RegisterPlugins(flutter::PluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | bitsdojo_window_windows
7 | dynamic_color
8 | isar_flutter_libs
9 | url_launcher_windows
10 | webview_windows
11 | window_size
12 | )
13 |
14 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
15 | )
16 |
17 | set(PLUGIN_BUNDLED_LIBRARIES)
18 |
19 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
20 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
21 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
24 | endforeach(plugin)
25 |
26 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
27 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
28 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
29 | endforeach(ffi_plugin)
30 |
--------------------------------------------------------------------------------
/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.14)
2 | project(runner LANGUAGES CXX)
3 |
4 | # Define the application target. To change its name, change BINARY_NAME in the
5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
6 | # work.
7 | #
8 | # Any new source files that you add to the application should be added here.
9 | add_executable(${BINARY_NAME} WIN32
10 | "flutter_window.cpp"
11 | "main.cpp"
12 | "utils.cpp"
13 | "win32_window.cpp"
14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
15 | "Runner.rc"
16 | "runner.exe.manifest"
17 | )
18 |
19 | # Apply the standard set of build settings. This can be removed for applications
20 | # that need different build settings.
21 | apply_standard_settings(${BINARY_NAME})
22 |
23 | # Add preprocessor definitions for the build version.
24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
29 |
30 | # Disable Windows macros that collide with C++ standard library functions.
31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
32 |
33 | # Add dependency libraries and include directories. Add any application-specific
34 | # dependencies here.
35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
36 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
37 |
38 | # Run the Flutter tool portions of the build. This must not be removed.
39 | add_dependencies(${BINARY_NAME} flutter_assemble)
40 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include
4 |
5 | #include "flutter/generated_plugin_registrant.h"
6 |
7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project)
8 | : project_(project) {}
9 |
10 | FlutterWindow::~FlutterWindow() {}
11 |
12 | bool FlutterWindow::OnCreate() {
13 | if (!Win32Window::OnCreate()) {
14 | return false;
15 | }
16 |
17 | RECT frame = GetClientArea();
18 |
19 | // The size here must match the window dimensions to avoid unnecessary surface
20 | // creation / destruction in the startup path.
21 | flutter_controller_ = std::make_unique(
22 | frame.right - frame.left, frame.bottom - frame.top, project_);
23 | // Ensure that basic setup of the controller was successful.
24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) {
25 | return false;
26 | }
27 | RegisterPlugins(flutter_controller_->engine());
28 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
29 | return true;
30 | }
31 |
32 | void FlutterWindow::OnDestroy() {
33 | if (flutter_controller_) {
34 | flutter_controller_ = nullptr;
35 | }
36 |
37 | Win32Window::OnDestroy();
38 | }
39 |
40 | LRESULT
41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
42 | WPARAM const wparam,
43 | LPARAM const lparam) noexcept {
44 | // Give Flutter, including plugins, an opportunity to handle window messages.
45 | if (flutter_controller_) {
46 | std::optional result =
47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
48 | lparam);
49 | if (result) {
50 | return *result;
51 | }
52 | }
53 |
54 | switch (message) {
55 | case WM_FONTCHANGE:
56 | flutter_controller_->engine()->ReloadSystemFonts();
57 | break;
58 | }
59 |
60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
61 | }
62 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_FLUTTER_WINDOW_H_
2 | #define RUNNER_FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | // #include "webview_cef/webview_cef_plugin_c_api.h"
5 |
6 | #include "flutter_window.h"
7 | #include "utils.h"
8 |
9 | #include
10 | auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
11 |
12 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
13 | _In_ wchar_t *command_line, _In_ int show_command) {
14 | //start cef deamon processes. MUST CALL FIRST
15 | //initCEFProcesses();
16 |
17 | // Attach to console when present (e.g., 'flutter run') or create a
18 | // new console when running with a debugger.
19 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
20 | CreateAndAttachConsole();
21 | }
22 |
23 | // Initialize COM, so that it is available for use in the library and/or
24 | // plugins.
25 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
26 |
27 | flutter::DartProject project(L"data");
28 |
29 | std::vector command_line_arguments =
30 | GetCommandLineArguments();
31 |
32 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
33 |
34 | FlutterWindow window(project);
35 | Win32Window::Point origin(10, 10);
36 | Win32Window::Size size(1280, 720);
37 | if (!window.CreateAndShow(L"eros_n", origin, size)) {
38 | return EXIT_FAILURE;
39 | }
40 | window.SetQuitOnClose(true);
41 |
42 | ::MSG msg;
43 | while (::GetMessage(&msg, nullptr, 0, 0)) {
44 | ::TranslateMessage(&msg);
45 | ::DispatchMessage(&msg);
46 | }
47 |
48 | ::CoUninitialize();
49 | return EXIT_SUCCESS;
50 | }
51 |
--------------------------------------------------------------------------------
/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erosTeam/eros_n/b23a7d50f1a84de672ba9e157c53cf6ac946515b/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/windows/runner/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | void CreateAndAttachConsole() {
11 | if (::AllocConsole()) {
12 | FILE *unused;
13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
14 | _dup2(_fileno(stdout), 1);
15 | }
16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
17 | _dup2(_fileno(stdout), 2);
18 | }
19 | std::ios::sync_with_stdio();
20 | FlutterDesktopResyncOutputStreams();
21 | }
22 | }
23 |
24 | std::vector GetCommandLineArguments() {
25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
26 | int argc;
27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
28 | if (argv == nullptr) {
29 | return std::vector();
30 | }
31 |
32 | std::vector command_line_arguments;
33 |
34 | // Skip the first argument as it's the binary name.
35 | for (int i = 1; i < argc; i++) {
36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
37 | }
38 |
39 | ::LocalFree(argv);
40 |
41 | return command_line_arguments;
42 | }
43 |
44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
45 | if (utf16_string == nullptr) {
46 | return std::string();
47 | }
48 | int target_length = ::WideCharToMultiByte(
49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
50 | -1, nullptr, 0, nullptr, nullptr);
51 | std::string utf8_string;
52 | if (target_length == 0 || target_length > utf8_string.max_size()) {
53 | return utf8_string;
54 | }
55 | utf8_string.resize(target_length);
56 | int converted_length = ::WideCharToMultiByte(
57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
58 | -1, utf8_string.data(),
59 | target_length, nullptr, nullptr);
60 | if (converted_length == 0) {
61 | return std::string();
62 | }
63 | return utf8_string;
64 | }
65 |
--------------------------------------------------------------------------------
/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_UTILS_H_
2 | #define RUNNER_UTILS_H_
3 |
4 | #include
5 | #include
6 |
7 | // Creates a console for the process, and redirects stdout and stderr to
8 | // it for both the runner and the Flutter library.
9 | void CreateAndAttachConsole();
10 |
11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
12 | // encoded in UTF-8. Returns an empty std::string on failure.
13 | std::string Utf8FromUtf16(const wchar_t* utf16_string);
14 |
15 | // Gets the command line arguments passed in as a std::vector,
16 | // encoded in UTF-8. Returns an empty std::vector on failure.
17 | std::vector GetCommandLineArguments();
18 |
19 | #endif // RUNNER_UTILS_H_
20 |
--------------------------------------------------------------------------------